← 返回题目列表

微服务中的领域事件应该如何建模?

困难 第 25 / 25 题 更新于 2026/10/02
微服务领域事件事件驱动Schema
学习 AI 实战项目

简化版

领域事件描述已经发生、对领域有意义的事实,应由拥有该聚合的服务发布,并以过去式命名。事件要稳定表达业务语义,而不是复制数据库行或伪装成远程命令。

详细版

事件信封包含 eventId、eventType、aggregateId、aggregateVersion、occurredAt 和 Schema 版本,payload 只放消费者理解事实所需的数据。生产端用 Outbox 保证状态与事件同事务,消费端以 eventId 幂等,并按聚合版本处理重复、乱序和缺口。

事件一旦发布就是历史事实,修正应发布新事件;兼容演进优先新增可选字段,语义大改使用新事件类型或版本。不要把所有内部字段公开,否则服务的数据库结构会变成跨团队契约。

完整版教学

一、事件和命令的边界

OrderPaid 是事实,可以有零到多个订阅者;SendCoupon 是要求某个接收者执行的命令。命令失败需要明确责任,事件发布者不应知道所有响应者。把命令叫事件会掩盖耦合。

记忆钩子:事件用过去式说“发生了什么”,不替消费者决定“必须做什么”。

二、从不变量找事件

事件通常产生在聚合成功完成状态迁移后。订单从 CREATED 到 PAID 才产生付款事实;一次数据库字段更新不一定有业务意义,也不应机械发事件。

command -> validate invariant -> commit state + outbox -> publish event

三、事件信封怎么设计

字段作用边界
eventId去重与追踪重试保持不变
aggregateId分区与归属不放随机值
aggregateVersion顺序校验由权威写入分配
occurredAt业务发生时间区别发布时间
schemaVersion解析路由不代表聚合版本

例如同一订单 v12 先于 v11 到达,消费者不能让旧事件覆盖新状态;可忽略 v11 或按业务要求修复缺口。

四、Payload 放多少

只放 ID 会迫使所有消费者回调生产者,形成同步耦合;复制完整数据库行又会泄露内部结构和敏感字段。应围绕事实提供稳定、最小但足够的数据,并明确哪些字段是快照。

价格、币种这类历史事实通常应随事件保存;可随时变化且由其他域拥有的数据不应冒充权威值。

五、可靠发布与消费

先提交业务再发消息会漏事件,先发再提交会产生不存在的事实。Outbox 将状态与待发布记录写入同一本地事务,后台至少一次投递;消费者仍需唯一约束或幂等状态机。

Exactly-once Broker 语义不能覆盖外部数据库和第三方副作用,因此业务幂等仍不可省。

六、演进与治理

已发布事件不可原地改写。新增字段提供默认语义,枚举保留 UNKNOWN;删除字段至少等待消息保留、归档和 DLQ 重放窗口。金额从元改分属于语义变化,不能只改字段说明。

事件目录要记录 owner、Schema、示例、保留期和消费者。上线前运行兼容检查与消费者契约测试,避免未知订阅者被静默破坏。

事件名称与语义一旦公开就不应复用;已废弃的 OrderPaid 不能日后被赋予“付款申请已提交”的新含义。

目录还要记录数据分类,防止新增消费者把含个人信息的事件复制到不允许的区域。

七、常见误区与追问

  • 误区:表发生变化就应发领域事件。 技术变更不一定是领域事实。
  • 误区:事件越大越能避免查询。 会暴露内部模型并增加兼容成本。
  • 误区:事件发布成功等于业务完成。 各消费者仍可能积压或失败。
  • 误区:随机 eventId 能保证顺序。 唯一性和顺序是两种属性。
  • 追问:事件能撤销吗? 历史不能抹去,应发布纠正或补偿事实。
  • 追问:消费者缺少 v6 却收到 v7 怎么办? 有界等待、查快照或进入修复队列。
  • 追问:谁能发布事件? 拥有该事实和不变量的服务。

八、加强记忆

领域事件沿“权威聚合产生事实—稳定信封—Outbox 可靠发布—幂等版本消费—兼容演进”设计。用过去式和业务语义防止命令伪装,用最小充分 payload 控制耦合,并把乱序、重放和 Schema 生命周期当成长期契约治理。