3. 业务领域建模
事件风暴(Event Storming)是一种协作式、基于工作坊的领域建模技术,由 Alberto Brandolini 提出。它通过让业务专家与开发团队共同识别领域事件,快速勾勒出业务全貌,并以此为基础发现聚合(Aggregate)、聚合根(Aggregate Root)与限界上下文(Bounded Context),从而科学地确定微服务模块边界。本章将系统讲解事件风暴的核心步骤、产出物及其在模块边界拆分中的具体应用。
3.1 事件风暴的核心元素与建模流程
事件风暴使用不同颜色的便利贴(或等价数字符号)代表不同的建模元素,按时间顺序从左到右排列。下表总结了核心元素及其在建模中的含义:
| 元素 | 颜色(参考) | 含义 | 示例 |
|---|---|---|---|
| 领域事件 | 橙色 | 业务中已发生的、不可改变的事实 | “审批单已提交”、“付款已确认” |
| 命令 | 蓝色 | 触发领域事件的用户意图或系统指令 | “提交审批单”、“确认付款” |
| 聚合 | 黄色 | 一组具有事务一致性的实体与值对象的集合 | “审批单聚合”、“付款聚合” |
| 外部系统/角色 | 粉色 | 触发命令或接收事件的参与者 | “申请人”、“审批人”、“ERP系统” |
| 策略/规则 | 紫色 | 决定何时触发命令的业务规则 | “金额超过10万需总经理审批” |
| 读模型 | 绿色 | 用户执行命令前需要查看的数据视图 | “待审批列表”、“审批单详情” |
典型的事件风暴工作坊流程包含以下步骤:
- 发散: 邀请业务专家与开发团队,在长卷纸上从左到右贴出所有能想到的领域事件(橙色)。
- 时间线排序: 将领域事件按业务发生的时间顺序排列,形成一条完整的事件流。
- 识别命令与角色: 在每个领域事件左侧贴上触发它的命令(蓝色)和发起命令的角色(粉色)。
- 发现聚合: 将紧密围绕同一组事件和命令的领域概念聚合为黄色便利贴,并命名聚合根。
- 划定限界上下文: 根据聚合之间的关联强度与业务语义,将聚合分组到不同的限界上下文中。
实践提示: 事件风暴工作坊建议控制在 6-10 人,包含至少 2 位业务专家。时间控制在 4-6 小时,避免过度疲劳导致建模质量下降。如果业务复杂,可以分多次进行,每次聚焦一个核心子域。
3.2 从领域事件到聚合根:以审批流为例
我们以企业采购审批流程为例,演示如何通过事件风暴识别聚合根。下图展示了该流程中核心的领域事件与命令关系:
在上图中,我们识别出“审批单”作为聚合根。它封装了以下实体与值对象:
- 实体: 审批单(聚合根)、审批记录(每次审批操作的记录)。
- 值对象: 金额、审批状态、审批意见。
聚合根“审批单”负责维护事务一致性:当“审批通过”命令执行时,聚合根会同时更新审批单状态并创建一条审批记录,这两个操作必须在同一个事务中完成。
3.3 确定模块边界:从聚合到限界上下文
识别出聚合后,下一步是将聚合分组到限界上下文中。限界上下文是领域驱动设计中模块划分的核心单元,每个限界上下文对应一个独立的微服务或模块。分组原则如下:
| 分组原则 | 说明 | 示例 |
|---|---|---|
| 业务语义内聚 | 属于同一业务能力域的聚合放在一起 | “审批单聚合”与“审批规则聚合”同属审批域 |
| 事务边界 | 需要强事务一致性的聚合应放在同一上下文中 | “审批单”与“审批记录”必须同上下文 |
| 通信频率 | 频繁交互的聚合优先放在同一上下文 | “审批单”与“通知聚合”交互频繁,可考虑同上下文 |
| 变更原因 | 因相同业务原因而变化的聚合放在一起 | 审批流程规则变更只影响审批域,不影响财务域 |
以下是一个企业采购系统的限界上下文划分示例:
3.4 模块边界拆分的最佳实践与常见误区
最佳实践:
- 先大后小: 先识别出粗粒度的限界上下文,再逐步细化内部聚合。避免一开始就陷入细枝末节。
- 以业务语言命名: 限界上下文和聚合的名称必须来自业务专家使用的术语,如“审批上下文”而非“流程管理模块”。
- 事件驱动边界: 如果两个聚合之间只能通过事件异步通信,它们很可能属于不同的限界上下文。
- 持续演进: 模块边界不是一成不变的。随着业务发展,可以通过事件风暴定期重新审视边界。
常见误区:
- 技术驱动拆分: 按数据表或技术分层(如Controller/Service/DAO)来划分模块,而非按业务能力。
- 过度拆分: 将每个实体都作为一个独立微服务,导致分布式事务爆炸和通信复杂度飙升。
- 忽略聚合边界: 在同一个事务中跨聚合更新数据,破坏了聚合的事务一致性原则。
- 缺乏业务验证: 建模完成后未与业务专家确认事件流和聚合定义,导致实现偏离业务需求。
3.5 从事件风暴到代码:聚合的代码骨架
识别出聚合根后,我们可以将其映射为代码。以下是一个“审批单”聚合根的 Java 代码骨架示例:
// 聚合根:审批单
public class ApprovalOrder {
private ApprovalOrderId id; // 值对象
private String applicantId;
private Money amount; // 值对象
private ApprovalStatus status; // 枚举
private List<ApprovalRecord> records; // 实体集合
// 命令:提交审批单
public void submit() {
if (this.status != ApprovalStatus.DRAFT) {
throw new IllegalStateException("只有草稿状态的审批单才能提交");
}
this.status = ApprovalStatus.PENDING;
// 发布领域事件:审批单已提交
DomainEventPublisher.publish(new ApprovalOrderSubmitted(this.id));
}
// 命令:审批通过
public void approve(String approverId, String comment) {
if (this.status != ApprovalStatus.PENDING) {
throw new IllegalStateException("只有待审批状态的审批单才能审批");
}
this.status = ApprovalStatus.APPROVED;
ApprovalRecord record = new ApprovalRecord(approverId, comment, LocalDateTime.now());
this.records.add(record);
// 发布领域事件:审批已通过
DomainEventPublisher.publish(new ApprovalOrderApproved(this.id));
}
// 命令:驳回
public void reject(String approverId, String comment) {
if (this.status != ApprovalStatus.PENDING) {
throw new IllegalStateException("只有待审批状态的审批单才能驳回");
}
this.status = ApprovalStatus.REJECTED;
ApprovalRecord record = new ApprovalRecord(approverId, comment, LocalDateTime.now());
this.records.add(record);
DomainEventPublisher.publish(new ApprovalOrderRejected(this.id));
}
}
注意,聚合根通过发布领域事件来通知其他限界上下文,而不是直接调用外部服务。这种设计保证了聚合内部的事务一致性,同时实现了上下文之间的松耦合。
3.6 本章小结
事件风暴是一种高效的协作建模技术,能够帮助团队从业务事件出发,逐步识别出聚合根、实体和限界上下文,从而科学地确定模块边界。关键要点包括:
- 以领域事件为起点,按时间线组织业务流。
- 聚合根是事务一致性的保证,每个聚合根封装一组紧密相关的实体和值对象。
- 限界上下文是微服务模块划分的直接依据,每个上下文内部保持强一致性,上下文之间通过事件实现最终一致性。
- 模块边界应基于业务语义而非技术实现,并随着业务发展持续演进。