4. 从事件风暴到领域模型:识别领域事件、命令与聚合根、生成统一语言与限界上下文映射

事件风暴(Event Storming)是一种协作建模技术,它通过高密度的“工作坊”形式,让领域专家与开发团队在同一个画布上快速发现业务全貌。本章将讲解如何把事件风暴的输出(领域事件、命令、聚合)转化为可落地的领域模型,并最终生成统一语言(Ubiquitous Language)与限界上下文(Bounded Context)映射图。

4.1 事件风暴的核心产出物

一次完整的事件风暴工作坊通常会产生以下三类核心元素,它们构成了领域驱动设计(DDD)的原始素材:

元素类型 颜色(标准) 描述 示例
领域事件 橙色便签 业务中已经发生的、不可改变的事实,通常使用“过去时”命名 订单已提交、库存已扣减、支付已完成
命令 蓝色便签 触发领域事件的用户意图或系统指令,通常使用“动词+宾语”命名 提交订单、扣减库存、确认支付
聚合根 黄色便签(大) 一组具有事务一致性的实体和值对象的边界,是命令的执行者 订单聚合、库存聚合、支付聚合
实践提示: 在事件风暴工作坊中,建议先从左到右排列“领域事件”时间线,再在每个事件左侧贴上“命令”,最后在命令上方或旁边贴上“聚合根”。这种“事件→命令→聚合”的逆向推导方式,能有效避免过早陷入技术细节。

4.2 从领域事件到命令:识别触发关系

每个领域事件都对应一个明确的触发者——命令。命令由角色(Actor)或系统定时器发起,经过聚合根的校验与处理后,产生一个或多个领域事件。以下是一个典型的电商下单流程的识别示例:

  1. 用户发起 提交订单 命令
  2. 订单聚合校验商品库存、用户余额后,产生 订单已提交 事件
  3. 库存聚合监听 订单已提交 事件,执行 扣减库存 命令,产生 库存已扣减 事件
  4. 支付聚合监听 库存已扣减 事件,执行 发起支付 命令,产生 支付已发起 事件
关键原则: 一个命令只能由一个聚合根处理,且一个命令通常只产生一个主要的领域事件。如果发现一个命令产生多个不相关的事件,说明聚合边界可能划分过粗,需要拆分。

4.3 聚合根的设计:事务一致性的边界

聚合根是领域模型中最重要的概念之一,它定义了事务一致性的边界。在事件风暴画布上,聚合根通常表现为一个包含多个实体和值对象的“黄色大框”。设计聚合根时,需要遵循以下规则:

  • 强一致性边界: 聚合内部的所有状态变更必须在同一个事务中完成。
  • 外部引用仅通过ID: 聚合根之间不能直接持有对象引用,只能通过全局唯一标识符(ID)关联。
  • 一个聚合一个根: 每个聚合有且只有一个根实体,外部只能通过根实体访问聚合内部。
  • 小聚合原则: 聚合不宜过大,通常一个聚合包含3~5个实体/值对象为佳。
常见误区: 不要将数据库表的一对多关系直接映射为聚合关系。例如“订单”和“订单行”虽然在数据库中是主从表,但在领域模型中,如果订单行数量巨大(如超过1000行),则应将“订单行”设计为独立的聚合,通过领域事件实现最终一致性。

4.4 统一语言(Ubiquitous Language)的生成

统一语言是DDD的基石,它确保领域专家、开发人员、测试人员、产品经理使用同一套术语进行沟通。在事件风暴画布上,每张便签的名称都应该成为统一语言的一部分。生成统一语言的步骤如下:

步骤 活动 产出物
1 收集画布上所有便签的名称 原始术语列表
2 消除同义词和歧义词(如“用户”与“客户”统一为“客户”) 标准化术语表
3 为每个术语编写简短的定义(不超过两句话) 术语定义表
4 将术语表纳入项目Wiki或代码仓库的Glossary文件中 活文档

以下是一个电商领域的统一语言示例片段:

# 电商领域统一语言(Glossary)

## 订单(Order)
- 定义:客户在一次购物中提交的商品购买请求,包含一个或多个订单行。
- 状态:待支付、已支付、已发货、已完成、已取消。

## 订单行(OrderLine)
- 定义:订单中的单个商品条目,包含商品ID、数量、单价。
- 约束:一个订单行只能属于一个订单。

## 库存(Inventory)
- 定义:商品的可销售数量,以SKU为单位。
- 操作:扣减、回滚、冻结。

4.5 限界上下文映射(Context Map)

限界上下文是DDD中划分系统边界的核心工具。在事件风暴画布上,我们可以根据“聚合根”的归属和“领域事件”的订阅关系,识别出不同的限界上下文。常见的上下文关系模式包括:

  • 合作关系(Partnership): 两个上下文互相依赖,需要同步协调。
  • 共享内核(Shared Kernel): 两个上下文共享一部分模型(如共享值对象)。
  • 客户-供应商(Customer-Supplier): 上游上下文为下游上下文提供数据或服务。
  • 防腐层(Anti-Corruption Layer): 下游上下文通过适配器隔离上游上下文的模型变化。
  • 开放主机服务(Open Host Service): 上游上下文通过API对外暴露服务。
  • 分离方式(Separate Ways): 两个上下文完全独立,不进行任何集成。
限界上下文映射图(Context Map) 订单上下文 聚合:订单、订单行 事件:订单已提交 命令:提交订单 库存上下文 聚合:库存、SKU 事件:库存已扣减 命令:扣减库存 支付上下文 聚合:支付单、交易 事件:支付已完成 命令:确认支付 客户-供应商 客户-供应商 图例说明 • 蓝色节点:订单上下文,负责订单生命周期管理 • 绿色节点:库存上下文,负责库存扣减与回滚 • 红色节点:支付上下文,负责支付交易处理 • 箭头方向:数据/事件流向,从上游(供应商)指向下游(客户) • 黄色标签:上下文关系模式,此处均为“客户-供应商”关系 • 每个上下文内部包含:聚合根、领域事件、命令

4.6 从事件风暴到代码模型的映射

当事件风暴画布上的元素被识别并归类到限界上下文后,我们可以将其直接映射为Go工程中的代码结构。以下是一个典型的映射规则:

事件风暴元素 Go代码元素 示例
领域事件 结构体 + 事件接口 type OrderSubmitted struct { OrderID string }
命令 结构体 + 命令接口 type SubmitOrder struct { UserID string; Items []OrderItem }
聚合根 结构体 + 领域方法 type Order struct { ID string; Items []OrderLine; Status OrderStatus }
限界上下文 Go package(模块) package order、package inventory
统一语言 类型别名、常量、注释 type OrderStatus string + 常量定义
工程实践: 在Go项目中,建议每个限界上下文对应一个独立的 internal/ 子目录,例如 internal/order/、internal/inventory/、internal/payment/。每个子目录内部再按 domain/(领域模型)、application/(应用服务)、infrastructure/(基础设施)分层组织代码。

4.7 本章小结

从事件风暴到领域模型的转化,本质上是将“业务事实”(领域事件)逆向推导为“业务意图”(命令),再通过“业务边界”(聚合根)进行封装,最终形成“业务语言”(统一语言)和“系统边界”(限界上下文)。这个过程不仅产出了高质量的领域模型,更重要的是建立了团队对业务的一致理解。在下一阶段,我们将基于这些模型进行分布式事务的架构选型。