事件结构 3 类型 = 业务事实的不同粒度
解决什么问题:不知道该用什么结构的事件—— 事件结构的选择直接决定下游能否物化完整状态。
业务问题
真实场景
场景 1:电商订单事件的设计选择
选项 A:Unkeyed Event
{"action": "OrderCreated", "timestamp": 1234567890}
→ 消费者只知道"有事发生",无法关联具体订单
选项 B:Entity Event
{"orderId": "123", "action": "OrderCreated", "timestamp": ...}
→ 消费者能关联订单,但不知道完整状态
选项 C:Keyed Event
{"key": "order-123", "value": {"status": "CONFIRMED", "amount": 99.99}}
→ 消费者能完整重建订单状态Bellemare Ch 2 原话:
"Events are typically represented using a key/value format. The value stores the complete details of the event, while the key is used for identification purposes, routing, and aggregation."
失败模式
| 失败模式 | 后果 |
|---|---|
| 事件信息不足 | 消费者需查 DB 补全(紧耦合) |
| 过度携带信息 | 事件臃肿、性能浪费 |
| 缺少 key | 无法路由、无法聚合 |
控制论视角
Ch 4 §4.6:"反馈必须包含受控对象的关键状态" + Ch 15:"测量必须能重建状态"
事件 = 控制律的输出信号
- Key = 路由标识(哪个对象)
- Value = 完整状态(让接收方重建)
- 缺 Key = 无法定位对象(信号无地址)
- 缺 Value = 无法重建(信号不完整)核心概念
事件 3 类型对比
| 类型 | Key | Value | 用途 | 例子 |
|---|---|---|---|---|
| Unkeyed Event | 无 | 简单描述 | 通知/广播 | "系统启动" |
| Entity Event | 实体 ID | 实体变更 | 生命周期追踪 | OrderCreated(orderId=123) |
| Keyed Event | 实体 ID | 完整当前状态 | 状态重建 | Order(123, status=CONFIRMED) |
Unkeyed Event(无 Key)
json
{
"action": "UserLoggedOut",
"timestamp": 1700000000
}适用:
- 全局通知
- 不需要后续关联
- 一次性事件
Entity Event(实体事件)
json
{
"orderId": "123",
"action": "OrderConfirmed",
"confirmedAt": 1700000000
}适用:
- 实体生命周期追踪
- 触发下游处理
- 不携带完整状态
Keyed Event(键值事件)
json
{
"key": "order-123",
"value": {
"orderId": "123",
"status": "CONFIRMED",
"amount": 99.99,
"customerId": "cust-456",
"items": [...],
"confirmedAt": 1700000000
}
}适用:
- 状态重建(物化)
- 事件溯源(Event Sourcing)
- 完整的快照
决策矩阵
业务场景:
1. 事件需要路由到特定下游?
- 是 → 需要 Key(Entity 或 Keyed)
- 否 → Unkeyed 即可
2. 消费者需要重建实体状态?
- 是 → Keyed Event(最完整)
- 否 → Entity Event 即可
3. 事件源是 Event Sourcing?
- 是 → 必用 Keyed Event
- 否 → 看下游需求
4. 业务是简单通知?
- 是 → Unkeyed工程实践
实战 1:电商订单事件流设计
Producer: OrderService
OrderCreated (Entity Event)
OrderConfirmed (Entity Event)
OrderShipped (Keyed Event) ← 含完整订单状态供物流订阅
OrderCancelled (Entity Event)
Consumer 1: InventoryService
监听 OrderCreated → 扣库存
Consumer 2: ShippingService
监听 OrderShipped → 创建运单
需要完整订单信息 → 用 Keyed Event
Consumer 3: AnalyticsService
监听所有事件 → 统计实战 2:避免事件臃肿
反模式:所有事件都是 Keyed
- OrderCreated 也带完整订单(含未来才会有的字段)
- 浪费带宽
正模式:按需选择
- 简单通知 → Unkeyed
- 状态变更 → Keyed
- 触发动作 → Entity反模式 vs 正模式
| 反模式 | 正模式 |
|---|---|
| 所有事件无 Key | 按需选择 Key |
| 所有事件都全量 | 按下游需求选择结构 |
| 事件缺少上下文(时间戳) | 总是带 timestamp |
| 多个事件类型混用 | 每类型明确语义 |
性能指标
| 指标 | 衡量 | 健康值 |
|---|---|---|
| 事件大小 | 平均字节数 | < 1 KB |
| Key 覆盖率 | 带 Key 事件比例 | > 70% |
| 路由效率 | Key-based routing 比例 | > 80% |
| 下游重建率 | 消费者从事件重建状态的比例 | > 60% |
边界判断
何时用 Unkeyed(< 10%)
- 全局通知
- 系统级事件
- 不需要路由
何时用 Entity Event(最常用)
- 实体生命周期追踪
- 触发下游处理
- 简单业务
何时用 Keyed Event(< 30%)
- 状态物化需求
- 事件溯源
- 多下游需要完整状态
何时升级
| 当前 | 升级到 | 触发条件 |
|---|---|---|
| Unkeyed | Entity | 需路由 |
| Entity | Keyed | 下游需重建 |
关键洞察
- Key 决定路由能力——无 Key = 无路由 = 单消费者。
- Value 决定重建能力——值越完整,下游越独立。
- 按需选择——不是所有事件都需要 Keyed,过度设计浪费。
- Keyed Event 是 Event Sourcing 的基础——状态变更 = 完整快照序列。
- 与 cybernetics 同构——事件结构 = Ch 15 测量的完整度,缺 Key = 测量无地址。