DDD之用事件风暴重构“电商订单履约”(11)

事件风暴在DDD中的作用大家多多少少都有一定的了解,比如可以在业务、研发、测试等各方建立一套统一语言、能梳理一套全景的业务流程图、能帮助研发人员划定限界上下文、并且还可以指导代码的落地等等,本文从一个实际案例来介绍下DDD中事件风暴的运行过程,通过案例让大家对这种明星级的建模方法有一个更强的体感。

准备工作:颜色就是语言

在开始之前,必须约定便利贴颜色的含义(这是一套标准协议):

  • 🟧 橙色 - 领域事件 (Domain Event) :系统中已经发生的事实(核心!必须用过去式)。

  • 🟦 蓝色 - 命令 (Command):触发事件的动作/请求。

  • 🟨 黄色 - 角色 (Actor):发出命令的人或角色。

  • 🟪 紫色 - 策略/规则 (Policy):当事件 A 发生后,触发命令 B 的业务规则("当...就...")。

  • 🍠 粉色 - 外部系统 (External System):第三方的支付网关、物流公司等。

  • 🟩 绿色 - 读模型 (Read Model):用户做决定时需要看的信息(如商品详情页)。

  • 📒 浅黄 - 聚合 (Aggregate):承载业务逻辑的实体对象(DDD 的核心)。


第一步:发散风暴 ------ 寻找"领域事件"(橙色便利贴)

目标:不加过滤地把所有能想到的业务结果都贴出来。

场景:大家围着白板,业务方开始喊,开发开始贴。

  • 业务方:"用户下单成功了。" -> 🟧 订单已创建 (OrderCreated)

  • 业务方:"然后我们要扣库存。" -> 🟧 库存已锁定 (StockLocked)

  • 业务方:"支付成功后。" -> 🟧 支付已确认 (PaymentConfirmed)

  • 业务方:"仓库发货了。" -> 🟧 货物已发货 (GoodsShipped)

  • 业务方:"用户收到货签收。" -> 🟧 订单已签收 (OrderDelivered)

  • 业务方:"超时没支付,订单要取消。" -> 🟧 订单已取消 (OrderCancelled)

专家技巧

  1. 必须用过去式(Created, Paid, Shipped)。

  2. 按照大致的时间顺序,从左到右排列。


第二步:倒序推演 ------ 寻找"命令"与"角色"(蓝色 & 黄色)

目标 :为每一个事件寻找源头。问自己:"是谁,做了什么,导致了这个事件?"

我们从 "订单已创建" 这个事件倒推:

  1. 事件:🟧 订单已创建

  2. 命令:是谁触发的?是用户点击了"提交订单"按钮。

    • 贴上 🟦 提交订单 (Place Order)
  3. 角色:是谁点的?

    • 贴上 🟨 普通用户 (User)
  4. 读模型:用户点之前看了什么?

    • 贴上 🟩 购物车预览 (Cart View)

我们再看 "货物已发货" 这个事件:

  1. 事件:🟧 货物已发货

  2. 命令 :🟦 执行发货 (Ship Goods)

  3. 角色 :🟨 仓库管理员 (Warehouse Keeper)


第三步:识别"策略"与"外部系统" ------ 复杂度的藏身处(紫色 & 粉色)

目标:识别自动化逻辑和外部依赖。这是业务逻辑最复杂的"胶水"部分。

场景 1:自动化流转

  • 现象:用户支付成功后,不需要人去点按钮,仓库系统应该自动生成发货单。

  • 逻辑 支付已确认 时,就 生成发货单。

  • 操作

    1. 🟧 支付已确认

    2. 🟪 策略:当支付成功,且库存充足,触发发货 (Policy)

    3. 🟦 生成发货单 (Command)

场景 2:外部依赖

  • 现象:支付是在支付宝/微信里完成的。

  • 操作

    1. 🟦 请求支付 (Command)

    2. 🍠 第三方支付网关 (External System)

    3. 🟧 支付已确认 (Event)


第四步:寻找"聚合" ------ 划定领地(浅黄便利贴)

目标 :这是 DDD 落地最关键的一步。我们要把相关的命令和事件归类,找到负责维护状态的那个"东西"

我们需要问:"是由哪个对象来执行'提交订单'命令,并产生'订单已创建'事件的?"

  1. 聚合 1:订单 (Order)

    • 命令:提交订单、取消订单、修改地址

    • 事件:订单已创建、订单已取消、地址已修改

    • -> 用一张大的📒浅黄纸写上 Order,把上述蓝、橙条包起来。

  2. 聚合 2:库存 (Inventory)

    • 命令:锁定库存、释放库存

    • 事件:库存已锁定、库存已释放

    • -> 聚合根:Inventory

  3. 聚合 3:支付 (Payment)

    • 命令:支付、退款

    • 事件:支付已确认、退款已完成

    • -> 聚合根:Payment


第五步:划定"限界上下文" ------ 战略拆分

目标:在墙上画圈,决定微服务拆分的边界。

看着墙上的聚合分布,我们发现自然形成了几个聚类:

  1. 订单上下文 (Order Context):包含 Order 聚合。负责交易流程。

  2. 库存上下文 (Inventory Context):包含 Inventory 聚合。负责管货。

  3. 支付上下文 (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%。这就是领域驱动设计最性感的地方。

欢迎关注,一起交流、一起进步~

相关推荐
变与不变8066 分钟前
Debug 调试与排错规范
前端·javascript
欢迎来到祖安!41 分钟前
企业微信 API 接口能力介绍:支持消息收发、图片发送、表情互动、联系人管理等多场景应用
java·前端·数据仓库·spring cloud·企业微信
dadaobusi4 小时前
学习:RV lkvm SBI
java·学习·spring
HEJOO96 小时前
深入理解 Java volatile 关键字:原理、用法与常见误区
java·开发语言·spring
曹牧6 小时前
Java:no content to map due to end of input
java·开发语言
子非鱼a6 小时前
【WEB】multiSQL
前端
阿维的博客日记6 小时前
Example 类全场景实战指南
java·mybatis
两点王爷7 小时前
SpringBoot 项目集成 GeoServer 源码,实现地图服务发布
java·spring boot·后端
Javatutouhouduan7 小时前
Java如何速通性能优化难题?
java·java面试·jvm调优·后端开发·java程序员·java八股文·java性能优化
lemon_sjdk7 小时前
DOM 节点
java·前端·javascript