关于 ddd-domain-event
ddd-domain-event 介绍领域驱动设计中的领域事件建模,帮助开发者判断何时使用事件、如何命名和组织事件,并避免将事件与命令或同步方法调用混淆。
主要功能
- 01定义不可变的过去时事件
- 02选择供订阅者使用的事件数据
- 03协调跨聚合边界的工作
- 04状态变更成功后发布事件
- 05区分事件与命令
适用场景
- 01向其他限界上下文通知业务事件
- 02构建投影、审计日志或搜索索引
- 03协调跨独立事务的工作
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ yonatankarp/ software-design-skills --skill ddd-domain-event安装后在 Claude Code 中输入/ddd-domain-event确认已加载。
详细介绍
一句话概述
领域事件是一条不可变记录,表示发生了一件对业务有重要意义的事——使用过去时,以通用语言命名,并携带下游监听方做出响应所需的数据。
何时使用此技能
- 跨越聚合边界——两个聚合必须协作,但它们位于不同的事务中。
- 告知其他限界上下文,此上下文中发生了一件值得关注的事。
- 构建读模型、投影或下游集成(审计日志、分析、搜索索引)。
- 表示业务本身会提及的生命周期转换:订单已下达、付款已结算、退款已发放、货物已离开仓库。
何时不使用此技能
- 状态变更仅发生在一个聚合内部,且没有监听方——直接调用方法即可。
- 所谓的“事件”实际上是命令(要求执行某项操作)。命令和事件不同:
PlaceOrder是命令,OrderPlaced是事件。 - 交互必须在单个事务中同步完成——这不是事件,而是方法调用。
核心内容
领域事件使用过去时命名:OrderPlaced、PaymentSettled、RefundIssued、ShipmentDispatched。事件名称本身就是通用语言的一部分——如果领域专家会说“我们结算一笔付款”,那么该事件就是 PaymentSettled。
事件是不可变且自描述的。事件发布后,永远不会改变;订阅方可以存储、重放或转发事件。事件携带 ID 和订阅方做出响应所需的最少数据,通常包括聚合标识符、时间戳和少量重要字段。
只有在发出事件的聚合成功完成状态变更之后,才会发布事件。通常会将事件与该状态变更一同以事务方式发布(通过 outbox 模式、事务性事件总线),确保订阅方看到事件,当且仅当状态变更实际提交时。
订阅方位于其他聚合或其他上下文中,并以异步方式响应。聚合内部的状态变更不需要事件——事件的存在正是为了跨越边界。
无论是否采用 CQRS 或事件溯源,领域事件都很有用。你可以通过普通关系型存储、outbox 表和发布器来发送事件;不需要对任何内容进行事件溯源。
判断启发
- 如果领域专家用名词描述“发生了什么”(一次结算、一次批准、一次交付),那么它很可能是值得建模的事件。
- 如果事件的唯一消费者就是发出它的同一个聚合,那么就去掉该事件,直接调用方法。
- 必须使用过去时。如果你发现自己在写
UpdateOrderEvent,那你建模的是命令,而不是事件。
反模式
- 使用祈使式事件名称(
UpdateOrder、ProcessRefund)——这些是命令。 - 事件携带完整的实体关系图,过于臃肿——订阅方不应该需要通过事件追踪引用。
- 在一个事务内部用事件控制同步流程——事件明确用于跨越边界。
- 在状态变更提交之前发布事件——事务回滚时,订阅方会看到虚假事件。
另请参阅
ddd-aggregate— 事件是跨越聚合边界进行协作的标准方式。ddd-context-mapping— 领域事件通常充当上下文之间的发布语言。ddd-deeper-insight— 将隐含的“发生了什么”概念明确表示为事件,是 Part III 中的经典做法。
参考资料
- Evans,Domain-Driven Design(2003),第 5 章在领域服务的语境中简要提及事件;后来 Vernon 的 Implementing Domain-Driven Design(2013)给出了经典论述。
评论
支持 Markdown