DDD实战指南:如何通过事件风暴法高效构建领域模型
1. 为什么你需要事件风暴法?
如果你正在开发一个复杂业务系统,可能遇到过这些典型问题:开发团队和业务专家沟通像鸡同鸭讲,需求文档写了厚厚一叠却还是漏洞百出,系统上线后才发现核心业务流程存在致命缺陷...这些问题背后,往往是因为团队缺乏有效的领域建模方法。
事件风暴法就是为解决这些问题而生。我第一次接触这个方法是在一个电商项目,当时我们花了三周时间写需求文档,结果开发到一半发现订单状态机设计存在严重漏洞。后来用事件风暴法重新梳理,只用了一个下午就理清了所有业务流程和异常场景。这种方法最神奇的地方在于,它能让业务专家和开发人员用同一种语言对话。
2. 事件风暴法核心概念详解
2.1 领域事件:业务事实的DNA
领域事件是事件风暴的基石,它记录业务过程中"已经发生"的重要事实。比如在医疗系统中,"处方已开具"、"药品已配送"这类事件。我建议用"主语+过去式动词"的格式命名,比如PatientAdmitted(患者已入院),这样的命名自带业务语义。
实际操作中常见的问题是容易把系统操作当作领域事件。比如有人会写"保存处方记录",这其实是技术实现,真正的领域事件应该是"处方已创建"。记住一个检验标准:这个事件如果打印在业务报表上,业务人员是否能直接看懂?
2.2 命令与策略:业务的决策逻辑
命令代表触发事件的意图,比如"提交订单"命令会触发"订单已创建"事件。策略则是业务规则的体现,比如"当订单金额超过1万元时,需要主管审批"。我在金融项目中就遇到过因为没有明确审批策略,导致大额交易直接通过的严重漏洞。
建议用不同颜色便签区分这些元素:
- 橙色:领域事件
- 蓝色:命令
- 紫色:策略
- 黄色:参与者
- 粉色:外部系统
3. 实战:从零开始组织事件风暴会议
3.1 会前准备:少即是多
很多人以为参会人越多越好,其实不然。我建议核心角色就够了:
- 1-2位真正懂业务的领域专家
- 2-3名核心开发(包括架构师)
- 产品负责人
- 有经验的协调者(这个角色很关键)
物料准备也有讲究:
- 3米以上的白板墙(相信我,小了肯定不够用)
- 多种颜色的便利贴
- 不同颜色的白板笔
- 计时器(严格控制每个环节时间)
3.2 会议六步法
头脑风暴阶段(30分钟):快速收集所有可能的领域事件,不做任何筛选,把想到的都贴到墙上。这时候常会出现业务专家和开发人员争论,协调者要确保每个人都充分表达。
时间线整理(20分钟):把所有事件按发生顺序排列。这时往往会发现业务流程中的断点和缺失环节。比如我们在物流系统就发现缺少"包裹已分拣"这个关键事件。
命令与策略(30分钟):为每个事件补充触发命令和业务规则。这个阶段最容易暴露业务逻辑漏洞,有一次我们发现退款策略居然没有考虑部分退款的情况。
聚合识别(40分钟):把相关事件和命令聚类,形成聚合。比如把所有支付相关的事件归到"支付"聚合。这时候黄色大便利贴就派上用场了。
限界上下文划分(30分钟):这是最考验架构师功力的环节。根据聚合的耦合度和业务内聚性划分边界。我们的经验法则是:如果两个聚合经常需要同步修改,它们可能属于同一个限界上下文。
模型验证(30分钟):用典型用户故事验证模型完整性。比如模拟"用户下单-支付-取消"的全流程,检查是否有缺失的事件或策略。
4. 常见陷阱与应对策略
4.1 业务专家说"系统应该..."
这是最需要警惕的信号。当业务专家开始讨论系统实现而不是业务事实时,要及时引导。我的应对话术是:"您能先告诉我,在现实业务中这个动作发生后,会有什么确凿的事实产生吗?"
4.2 陷入技术实现细节
开发人员常忍不住讨论"这个事件该怎么存储"、"要不要用消息队列"。这时候协调者要坚决制止,建议设立"技术禁言时段",只讨论业务事实。
4.3 过度设计
有一次我们给订单设计了17种状态事件,后来发现80%都用不上。好的领域模型应该符合"奥卡姆剃刀"原则——如无必要,勿增实体。建议先实现最小可行模型,再逐步扩展。
5. 从模型到代码的落地实践
5.1 事件建模示例
// 领域事件示例 public class OrderPaidEvent implements DomainEvent { private OrderId orderId; private PaymentAmount amount; private DateTime paidTime; // 构造函数、getter省略 } // 命令示例 public class PayOrderCommand { private OrderId orderId; private PaymentMethod method; public OrderPaidEvent execute() { // 业务逻辑 return new OrderPaidEvent(...); } }5.2 聚合设计要点
一个典型的聚合应该:
- 维护自身一致性边界
- 通过唯一标识引用其他聚合
- 包含处理命令的业务逻辑
- 产生领域事件
比如订单聚合:
public class Order { private OrderId id; private OrderStatus status; private List<DomainEvent> changes = new ArrayList<>(); public void pay(Payment payment) { if (status != OrderStatus.CREATED) { throw new IllegalStateException(); } apply(new OrderPaidEvent(id, payment.amount(), clock.now())); } private void apply(OrderPaidEvent event) { this.status = OrderStatus.PAID; this.changes.add(event); } }6. 进阶技巧:处理复杂业务场景
6.1 长流程业务建模
对于像保险理赔这样的长流程业务,建议采用"Saga模式":
- 把整个流程拆分为多个本地事务
- 每个事务对应一个领域事件
- 通过事件触发后续事务
- 设计补偿机制处理失败场景
6.2 外部系统集成
对于需要与外部系统集成的场景:
- 明确系统职责边界
- 定义清晰的防腐层
- 使用适配器模式转换数据格式
- 考虑最终一致性而非强一致性
比如支付网关集成:
支付命令 -> [支付网关适配器] -> 支付成功事件 -> 支付失败事件在实际项目中,事件风暴法最大的价值不在于产出完美的模型,而在于创造业务与技术深度对话的机会。记得有个医疗项目,业务专家在建模过程中突然说:"原来我们一直漏掉了医嘱复核这个关键环节!"这种顿悟时刻,才是事件风暴最迷人的地方。
