事件风暴在DDD中的作用大家多多少少都有一定的了解,比如可以在业务、研发、测试等各方建立一套统一语言、能梳理一套全景的业务流程图、能帮助研发人员划定限界上下文、并且还可以指导代码的落地等等,本文从一个实际案例来介绍下DDD中事件风暴的运行过程,通过案例让大家对这种明星级的建模方法有一个更强的体感。
准备工作:颜色就是语言
在开始之前,必须约定便利贴颜色的含义(这是一套标准协议):
-
🟧 橙色 - 领域事件 (Domain Event) :系统中已经发生的事实(核心!必须用过去式)。
-
🟦 蓝色 - 命令 (Command):触发事件的动作/请求。
-
🟨 黄色 - 角色 (Actor):发出命令的人或角色。
-
🟪 紫色 - 策略/规则 (Policy):当事件 A 发生后,触发命令 B 的业务规则("当...就...")。
-
🍠 粉色 - 外部系统 (External System):第三方的支付网关、物流公司等。
-
🟩 绿色 - 读模型 (Read Model):用户做决定时需要看的信息(如商品详情页)。
-
📒 浅黄 - 聚合 (Aggregate):承载业务逻辑的实体对象(DDD 的核心)。
第一步:发散风暴 ------ 寻找"领域事件"(橙色便利贴)
目标:不加过滤地把所有能想到的业务结果都贴出来。
场景:大家围着白板,业务方开始喊,开发开始贴。
-
业务方:"用户下单成功了。" -> 🟧 订单已创建 (OrderCreated)
-
业务方:"然后我们要扣库存。" -> 🟧 库存已锁定 (StockLocked)
-
业务方:"支付成功后。" -> 🟧 支付已确认 (PaymentConfirmed)
-
业务方:"仓库发货了。" -> 🟧 货物已发货 (GoodsShipped)
-
业务方:"用户收到货签收。" -> 🟧 订单已签收 (OrderDelivered)
-
业务方:"超时没支付,订单要取消。" -> 🟧 订单已取消 (OrderCancelled)
专家技巧:
-
必须用过去式(Created, Paid, Shipped)。
-
按照大致的时间顺序,从左到右排列。
第二步:倒序推演 ------ 寻找"命令"与"角色"(蓝色 & 黄色)
目标 :为每一个事件寻找源头。问自己:"是谁,做了什么,导致了这个事件?"
我们从 "订单已创建" 这个事件倒推:
-
事件:🟧 订单已创建
-
命令:是谁触发的?是用户点击了"提交订单"按钮。
- 贴上 🟦 提交订单 (Place Order)
-
角色:是谁点的?
- 贴上 🟨 普通用户 (User)
-
读模型:用户点之前看了什么?
- 贴上 🟩 购物车预览 (Cart View)
我们再看 "货物已发货" 这个事件:
-
事件:🟧 货物已发货
-
命令 :🟦 执行发货 (Ship Goods)
-
角色 :🟨 仓库管理员 (Warehouse Keeper)
第三步:识别"策略"与"外部系统" ------ 复杂度的藏身处(紫色 & 粉色)
目标:识别自动化逻辑和外部依赖。这是业务逻辑最复杂的"胶水"部分。
场景 1:自动化流转
-
现象:用户支付成功后,不需要人去点按钮,仓库系统应该自动生成发货单。
-
逻辑 :当 支付已确认 时,就 生成发货单。
-
操作:
-
🟧 支付已确认
-
🟪 策略:当支付成功,且库存充足,触发发货 (Policy)
-
🟦 生成发货单 (Command)
-
场景 2:外部依赖
-
现象:支付是在支付宝/微信里完成的。
-
操作:
-
🟦 请求支付 (Command)
-
🍠 第三方支付网关 (External System)
-
🟧 支付已确认 (Event)
-
第四步:寻找"聚合" ------ 划定领地(浅黄便利贴)
目标 :这是 DDD 落地最关键的一步。我们要把相关的命令和事件归类,找到负责维护状态的那个"东西"。
我们需要问:"是由哪个对象来执行'提交订单'命令,并产生'订单已创建'事件的?"
-
聚合 1:订单 (Order)
-
命令:提交订单、取消订单、修改地址
-
事件:订单已创建、订单已取消、地址已修改
-
-> 用一张大的📒浅黄纸写上 Order,把上述蓝、橙条包起来。
-
-
聚合 2:库存 (Inventory)
-
命令:锁定库存、释放库存
-
事件:库存已锁定、库存已释放
-
-> 聚合根:Inventory
-
-
聚合 3:支付 (Payment)
-
命令:支付、退款
-
事件:支付已确认、退款已完成
-
-> 聚合根:Payment
-
第五步:划定"限界上下文" ------ 战略拆分
目标:在墙上画圈,决定微服务拆分的边界。
看着墙上的聚合分布,我们发现自然形成了几个聚类:
-
订单上下文 (Order Context):包含 Order 聚合。负责交易流程。
-
库存上下文 (Inventory Context):包含 Inventory 聚合。负责管货。
-
支付上下文 (Payment Context):包含 Payment 聚合。负责管钱。
上下文映射:
-
订单上下文 发出 订单已创建 事件。
-
库存上下文 监听 这个事件,然后执行 锁定库存 命令。
-
这就是微服务之间的事件驱动架构 (EDA) 原型。

第六步:从墙面到代码 ------ 落地映射
现在,墙上的便利贴可以直接映射为 Java 代码了。这就是 DDD 的威力。
|-------|----------|------------------------------------------------------|
| 便利贴颜色 | 业务概念 | 代码映射 (Java 示例) |
| 🟧 橙色 | 领域事件 | public class OrderCreatedEvent { String orderId; } |
| 🟦 蓝色 | 命令 | public void placeOrder(Command cmd) { ... } (应用服务方法) |
| 📒 浅黄 | 聚合根 | public class Order { ... } (实体类) |
| 🟪 紫色 | 策略 | OrderPolicyService 或 领域事件监听器 (@EventListener) |
| 🟩 绿色 | 读模型 | CQRS 中的 Query 端 DTO (如 OrderDetailDTO) |
代码落地演示
假设墙上有一组:
-
🟦 命令:ChangeShippingAddress (修改收货地址)
-
📒 聚合:Order
-
🟧 事件:ShippingAddressChanged
对应的代码:
codeJava
// 1. 命令对象 (对应蓝色)
public class ChangeAddressCommand {
private String orderId;
private Address newAddress;
// getter/setter
}
// 2. 聚合根 (对应浅黄)
public class Order {
private OrderId id;
private Address address;
private OrderStatus status;
// 对应蓝色命令的行为方法
public void changeAddress(Address newAddress) {
// 业务规则校验
if (this.status == OrderStatus.SHIPPED) {
throw new DomainException("已发货订单不能修改地址");
}
this.address = newAddress;
// 3. 发布事件 (对应橙色)
DomainEventPublisher.publish(new ShippingAddressChangedEvent(this.id, newAddress));
}
}
// 4. 应用服务 (连接层)
@Service
public class OrderAppService {
@Resource private OrderRepository repo;
public void handleChangeAddress(ChangeAddressCommand cmd) {
Order order = repo.findById(cmd.getOrderId());
order.changeAddress(cmd.getNewAddress()); // 调用聚合行为
repo.save(order);
}
}
总结
事件风暴的价值在于 :
它把隐晦的、藏在业务专家脑子里的逻辑,显性化地变成了墙上的"便利贴流程图"。
- 橙色定义了结果。
- 蓝色定义了API。
- 浅黄定义了核心模型。
- 画圈定义了微服务边界。
做完这一场风暴,你的系统架构图、核心类图、API 列表基本上就自动生成了 80%。这就是领域驱动设计最性感的地方。
欢迎关注,一起交流、一起进步~