消息颗粒度设计方法
总结
- 消费方多且需求各异:瘦消息 + 查询(只传事件 ID,消费方自己查数据)
- 消费方固定、需要快照:消息携带数据(按消费方需求裁剪字段)
- 不推荐"三粒度分级",边界模糊维护成本高
- 团队消息格式乱可以引入 CloudEvents 规范统一
1. 先说结论
消息设计有两个方向,按场景选:
| 方案 | 适用场景 | 核心思路 |
|---|---|---|
| 瘦消息 + 查询 | 事件驱动架构,消费方需求各异 | 消息只传事件标识,消费方自己查数据 |
| 消息携带数据 | 消费方场景固定,不想二次查询 | 按消费方需求裁剪字段 |
不推荐"三粒度分级":按字段数量切消息类(粗/中/细)边界模糊,维护三套 DTO 成本高,消费方还是要猜该用哪个。
2. 瘦消息 + 查询(推荐)
消息只携带事件类型和业务 ID,消费方收到后按需调用接口拿完整数据。这是 DDD 事件驱动的标准做法。
{
"orderId": "123456",
"event": "ORDER_PAID",
"occurredAt": "2026-03-14T10:00:00Z"
}
// 消费方:收到消息后自己查需要的数据
@KafkaListener(topics = "order-events")
public void onOrderPaid(OrderEvent event) {
// 按需查询,要什么取什么
Order order = orderClient.getOrder(event.getOrderId());
// 处理业务逻辑...
}
优点:
- 消息结构稳定,字段变更不影响消息本身
- 不同消费方按各自需求查数据,互不干扰
- 消息体小,传输快
缺点:
- 消费方需要额外的查询调用,有网络开销
- 如果消费方很多且都要查,数据库压力大
3. 消息携带数据
消费方场景固定时,直接把需要的字段塞进消息,省去二次查询。
// 按消费方实际需要的字段设计,不多不少
public class OrderPaidMessage {
private String orderId;
private Long userId;
private BigDecimal amount;
private String paymentMethod;
private Date paidAt;
// 只放消费方真正用到的字段
}
什么时候用:
- 消费方明确且固定(比如专门的通知服务、对账服务)
- 消费方对实时性要求高,不能接受二次查询的延迟
- 消息数据是快照,需要记录当时的状态(比如价格、库存)
注意版本兼容:
- 新增字段用可选字段,给默认值
- 废弃字段保留一段时间再删,不要直接删
- 字段类型变更要走新字段,不要改老字段
4. 用 CloudEvents 规范统一消息格式
如果团队消息格式比较乱,可以参考 CloudEvents 规范,统一元数据结构:
{
"specversion": "1.0",
"type": "com.example.order.paid",
"source": "/order-service",
"id": "A234-1234-1234",
"time": "2026-03-14T10:00:00Z",
"datacontenttype": "application/json",
"data": {
"orderId": "123456",
"amount": 99.00
}
}
Kafka、AWS EventBridge、阿里云 EventBus 都支持这个格式,方便跨系统集成。
5. 怎么选?
- 消费方多且需求各异 → 瘦消息 + 查询
- 消费方固定,需要快照数据 → 消息携带数据
- 团队消息格式混乱 → 引入 CloudEvents 规范统一