mcpField
D
ddd-domain-event

ddd-domain-event

github.com/yonatankarp开发144 次安装23 周前更新
代码仓库

关于 ddd-domain-event

ddd-domain-event 介绍领域驱动设计中的领域事件建模,帮助开发者判断何时使用事件、如何命名和组织事件,并避免将事件与命令或同步方法调用混淆。

主要功能

  1. 01定义不可变的过去时事件
  2. 02选择供订阅者使用的事件数据
  3. 03协调跨聚合边界的工作
  4. 04状态变更成功后发布事件
  5. 05区分事件与命令

适用场景

  1. 01向其他限界上下文通知业务事件
  2. 02构建投影、审计日志或搜索索引
  3. 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