Event Contract(事件契约)
解决什么问题:异步跨服务调用没有"请求-响应"那一握手,调用方凭什么相信生产者的格式承诺—— Event Contract = 事件的"宪法",告诉所有订阅者"这个事件永远长这样、版本怎么演进、我下架了怎么通知你"。
它是什么
Event Contract(事件契约) 是 L2 设计层关于异步事件格式的强约束契约——它告诉所有订阅者:
- 结构:字段名、类型、是否必填、是否嵌套
- 语义:事件代表什么"已发生的事实",不可修改只能追加
- 版本:怎么标版本、新版本何时算 breaking
- 演化规则:加字段 OK、改字段危险、删字段要带默认
- 下架流程:deprecation 公告、宽限期、强制截止日
EDM Ch 3(Bellemare): "An event contract is the formal agreement between producers and consumers about the structure, semantics, and lifecycle of events. Without a contract, every consumer becomes a point of fragile coupling."
Event Contract ≠ Schema。Schema 是结构定义(机器可读),Contract 是"结构 + 语义 + 演化 + 下架"的完整协议。
V2 视角(V2.7 B7 段 5 "自然演化" + B2 段 4 "Hyrum's Law"):事件是"已发生的业务事实"——事实不可撤销,所以事件一旦发布就是历史,契约的演化必须保证历史可解读性。
关键属性
| 属性 | 值/含义 |
|---|---|
| 不可变性 | 已发布事件的事实不可修改,只能追加新事件 |
| 向后兼容 | 加字段、加可选字段永远兼容 |
| 向前兼容 | 新版消费者能读老事件,老版消费者读新事件要优雅降级 |
| 版本化 | Schema 版本(整数或 semver)+ 文档版本独立 |
| 语义锁定 | 字段名定了 = 业务含义定了,不能改语义不改名 |
| 下架可见 | deprecation 必须公告 + 监控 + 宽限期 |
| 反模式 | 直接改 schema、删字段不带默认、用同一名字改语义 |
| 判错标志 | 多个消费者对"同一事件"理解不一致 / 消费者绕过 contract 直接解析 |
怎么用
场景 1:Avro / Protobuf Schema 注册(Schema Registry)
# schema-registry: order-created.v1.avsc
{
"type": "record",
"name": "OrderCreated",
"namespace": "com.example.orders",
"fields": [
{"name": "orderId", "type": "string"},
{"name": "customerId", "type": "string"},
{"name": "items", "type": {
"type": "array",
"items": {
"type": "record",
"name": "OrderItem",
"fields": [
{"name": "productId", "type": "string"},
{"name": "quantity", "type": "int"},
{"name": "unitPrice", "type": "double"}
]
}
}},
{"name": "createdAt", "type": "long", "logicalType": "timestamp-millis"}
]
}Schema Registry 提供的 4 个核心能力:
- Schema 存储——所有版本、所有命名空间
- 兼容性检查——新 schema 注册时自动检查兼容性
- Schema 演化——版本号自动管理
- 消费者协调——所有消费者按兼容性约束拉取
场景 2:兼容 vs 不兼容的演化规则
V2 B7 段 4 演化 + B2 段 4 Hyrum's Law 视角——事件契约的兼容性规则是硬约束:
✅ 兼容(可同版本演进)
+ 给 OrderCreated 加 notes 字段(optional)
+ 加新的事件类型 OrderCancelled
+ 加嵌套结构(可选)
⚠️ 危险(要协调所有消费者)
~ 给已有字段改类型(int → long)
~ 改字段名
~ 改字段默认值
❌ 不兼容(必须新主版本)
- 删字段
- 改必填为选填(如果业务上原本必填)
- 改字段语义不改名示例——加 notes 字段的兼容演化:
{
"orderId": "ord-001",
"customerId": "cus-123",
"items": [...],
- "createdAt": 1719123456789
+ "createdAt": 1719123456789,
+ "notes": "VIP 客户优先发货" ← 新增 optional,老消费者忽略
}场景 3:版本化策略(EDM Ch 3)
3 种主流版本策略:
策略 1: Schema Registry 自带版本(推荐)
- v1, v2, v3 由 Registry 管理
- 一个 topic 可同时存多个版本
- 消费者按兼容性约束读
策略 2: Semver in topic name
- orders.events.v1
- orders.events.v2
- 升级 = 新 topic + 老 topic 保留 N 月
策略 3: 内容嵌入版本号字段
- 每个事件带 schemaVersion 字段
- 消费者按版本路由V2 B7 演化视角:策略 1 最优——Registry 自动管理兼容性 = 演化阻力最小 = 演化更自然。
场景 4:事件下架(Deprecation)
事件不能删只能停发(已发布事件是历史事实)。下架流程:
第 0 月: 公告 deprecation(spec + 内部 wiki + 邮件)
第 1 月: 监控哪些订阅者还在消费 → 通知他们迁移
第 2 月: 发布 "shutdown 预警",倒计时
第 3 月: 停止生产该事件
第 6 月: 关闭 topic(保留历史只读)SE@G Ch 15 Deprecation 给的纪律: "Mark deprecated → Monitor usage → Provide migration path → Delete"
监控比公告更重要——公告没人看,监控指标会告诉生产方"还有人没切"。
场景 5:事件语义的"事实性"约束
# ✅ 好的事件 = 已发生的事实
event = OrderCreated(
orderId="ord-001",
customerId="cus-123",
items=[...],
createdAt=now() ← 事件发生时间,不可修改
)
# ❌ 坏的事件 = 命令 / 请求 / 状态查询
event = CreateOrder(...) ← 命令不是事件
event = GetOrderStatus(...) ← 查询不是事件
event = UpdateOrder(...) ← 修改不是事件事实性约束:
- 命名 = 过去时(Created / Updated / Cancelled)
- 时间字段 = 事件发生时的时间(不是处理时)
- 事件内容 = 不可变快照(即使后续修改订单,事件内容也是当时状态)
IDDD Ch 8(Vernon): "A Domain Event is a full-time record of a business occurrence. It is not a notification; it is the fact itself."
常见误区
- "Schema = Contract"——错。Schema 是结构,Contract 是"结构 + 语义 + 演化 + 下架"的完整协议
- "事件 = 命令"——错。事件是已发生事实,命令是请求。CreateOrder 是命令,OrderCreated 是事件
- "可以直接改 schema"——错。任何 schema 变更都要走兼容性检查,破坏兼容 = 主版本升级
- "删字段很安全"——错。事件历史里还有这个字段,老消费者依赖这个字段。删字段 = 改历史解读
- "deprecation 发个邮件就够了"——错。邮件没人看,要监控 + 告警 + 倒计时才能保证真下架
- "同一 topic 混合版本"——危险。同 topic 多版本 = 消费者要支持多版本 = 复杂度爆炸。用 Schema Registry 隔离
- "事件可以改状态"——错。事件是事实不是状态。改状态 = 发新事件,不是改旧事件
关键洞察
事件契约不是"我答应你的格式",是"我对所有订阅者的公开承诺 + 我对历史事实的负责"。
一旦发布,事件就是历史——历史不可改,所以契约的演化必须保证历史可解读性。
V2 B7 演化的极端形态——事件流 = 业务的镜像,事件契约 = 镜像的元数据。 镜像的演化 = 元数据的演化。好的事件契约让镜像演化"对历史无损"——这是 EDM Ch 17 给出的"自然演化"的工程基础。
相关链接
- API Design——事件的同步版(请求-响应)
- Interface Contract——同步 RPC 契约
- Hyrum's Law——事件契约被"试探性依赖"的现实
- [V3 涌现之时] · 演化——契约演化的元姿态
- [V3 涌现视域] · 认识自己——契约 = 模型,行为 = 现实
- L3 Schema Evolution Rules——事件 schema 的代码层规则