领域事件:微服务解耦的关键
# 领域事件:微服务解耦的关键
领域事件(Domain Event)是我认为 DDD 里最实用的概念之一——即使不搞完整的 DDD,事件驱动的解耦思路也值得每个做微服务的人掌握。
# 一、什么是领域事件
一句话:领域中发生的、会引发后续业务动作的事情。
怎么在需求分析时识别它?听关键句式——业务人员嘴里出现这些话时,八成就是领域事件:
- "如果发生……,则……"
- "当做完……的时候,请通知……"
几个典型例子:
- 缴费完成后 → 触发投保单转保单
- 密码连续输错三次 → 触发账户锁定
- 定时批处理生成缴费通知单 → 触发邮件通知
# 二、为什么用事件而不是直接调用
回顾聚合设计原则里的一条:一次事务只改一个聚合,跨聚合用最终一致性。
直接服务调用(传统 SOA 风格)的问题:
- 调用方必须知道被调方,强依赖
- 跨服务改数据要上分布式事务,性能差、耦合重
- 一个发布方对接 N 个下游时,调用关系爆炸
事件驱动则是"我干完了我的事,广播一声,谁关心谁处理":
- 发布方不关心订阅方是否处理成功,彻底解耦
- 天然支持一个发布方、N 个订阅方——这是直接调用做不到的
- 用最终一致性替代分布式事务,性能好得多
# 三、两种场景,处理方式不同
# 微服务内(聚合之间)
同一个进程里,事务好控制,不一定需要消息中间件,用进程内事件总线(EventBus)即可。但要不要引入事件总线,得权衡开发复杂度和收益——简单场景直接在应用层做服务编排也行(代价是可能引入分布式事务)。
# 微服务之间(重点场景)
跨服务的事件要考虑一整套机制:事件构建 → 持久化 → 发布到消息中间件(Kafka/RabbitMQ)→ 订阅方接收 → 落库 → 处理。
# 四、一个完整案例:保险承保流程
看事件怎么驱动业务在多个微服务之间流转:
投保微服务:生成缴费通知单
│ 事件①「缴费通知单已生成」→ MQ
▼
收款微服务:订阅事件,完成缴费
│ 事件②「缴费已完成」→ MQ ← 注意:订阅方变成了发布方
▼
投保微服务:确认缴费,投保单转保单
│ 事件③「保单已生成」→ MQ
▼
保单微服务:保存保单
│ 后续事件并发广播 → 佣金 / 收付费 / 再保 / 财务微服务
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
整条业务链没有一次同步的跨服务调用,全靠事件推着走。任何一个下游挂了,事件还在 MQ 里,恢复后继续消费,业务闭环不断。
# 五、落地的技术要点
# 1. 事件实体怎么设计
事件至少包含两类数据:
- 基本属性:全局唯一的事件 ID、发生时间、事件类型、事件源
- 业务属性:事件发生那一刻的业务数据快照——事件一旦发生就不可变,所以业务数据适合用序列化值对象保存
实践中会定义一个 DomainEvent 基类统一事件结构,子类按需扩展。
# 2. 事件数据要持久化
持久化的目的:对账、审计,以及 MQ 或订阅方宕机后能恢复业务。两种方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 存本地业务库的事件表 | 本地事务就能保证业务+事件一致 | 事件数据分散 |
| 存共享事件库 | 集中管理 | 跨库要分布式事务,伤性能 |
推荐第一种——业务数据和事件数据在同一个本地事务里落库,然后由定时任务或数据库日志捕获(CDC)把增量事件发到 MQ。这其实就是常说的 Transactional Outbox 模式,避免了"业务落库成功但消息发送失败"的经典难题。
# 3. 完整运行链路(以"缴费通知单已生成"为例)
- 投保微服务的应用服务调用领域服务,创建缴费通知单 + 事件实体
- 仓储将业务数据和事件数据持久化到本地库(同一个本地事务,无分布式事务)
- 定时程序或 CDC 从事件表捞增量,发布到消息中间件
- 收款微服务监听 MQ,拿到事件数据先落本地库
- 调用领域服务完成缴费,事件结束
# 六、小结
- 领域事件 = "发生了某事,需要有后续动作",识别靠听业务人员的"如果……则……"
- 核心价值:切断服务间强依赖,用最终一致性替代分布式事务
- 微服务内用事件总线(可选),微服务间用消息中间件(常态)
- 事件数据先落本地库再异步发 MQ,是保证可靠性的关键一招
结合之前整理的 Kafka 面试题汇总,领域事件就是 MQ 在业务架构层面的"正确打开方式"。
编辑 (opens new window)
上次更新: 2026/10/08, 03:22:29