第 2 章:战略设计
学习目标
- 掌握战略设计 vs 战术设计的边界
- 学会用事件风暴(Event Storming)梳理业务
- 画出限界上下文映射图
- 识别跨上下文协作的常见坑
一、战略 vs 战术
DDD 分为两层:
- 战略设计:业务边界划分、上下文映射、跨团队协作(高层)
- 战术设计:实体、值对象、聚合根、领域服务(代码层)
战略错了,战术再精致也救不回来;战术错了,战略再清晰也跑不通。两者先做战略,再做战术。
text
战略层(High-Level)
- 业务全景
- 子域划分
- 上下文映射
- 团队边界
战术层(Code-Level)
- 实体、值对象
- 聚合、领域服务
- 仓储、工厂二、事件风暴(Event Storming)
事件风暴是 Alberto Brandolini 提出的工作坊方法,用便利贴把业务梳理出来。需要 3 类角色:
- 业务人员:懂业务
- 开发:懂技术
- 辅助人员:提问、记录
贴在白板上的便利贴颜色:
| 颜色 | 含义 | 示例 |
|---|---|---|
| 橙色 | 事件(已发生的事) | 订单已创建 |
| 蓝色 | 命令(用户动作) | 提交订单 |
| 黄色 | 补充说明 | 库存不足 |
| 绿色 | 读模型(查询) | 查看订单详情 |
| 粉色 | 外部系统 | 支付网关 |
| 紫色 | 策略 | 满 100 免运费 |
java
// 事件风暴产出 → 领域事件
public class OrderPlacedEvent {
private final OrderId orderId;
private final UserId userId;
private final Money totalAmount;
private final Instant occurredAt;
}
// 事件命名:过去式,描述已发生的事
// OrderPlaced / OrderPaid / OrderShipped / OrderCancelled⚠️ 坑 1:
OrderCreated还是OrderPlaced?团队必须统一一个名字。坚持"过去式 + 业务术语"原则,别混用Create/Place/Submit。
三、识别聚合的边界
事件风暴梳理完之后,沿着事件一致性边界画聚合:
java
// 订单聚合(根 = Order,内含 OrderItem)
public class Order {
private OrderId id;
private List<OrderItem> items; // 聚合内
private OrderStatus status;
private Address shippingAddress;
public void addItem(SkuId skuId, int quantity) {
// 任何对 items 的修改都走 Order,保证一致性
items.stream()
.filter(i -> i.skuEquals(skuId))
.findFirst()
.ifPresentOrElse(
i -> i.increase(quantity),
() -> items.add(new OrderItem(skuId, quantity))
);
}
}
// 库存聚合(独立!)
public class Inventory {
private SkuId skuId;
private Stock stock;
private Stock frozenStock; // 冻结库存(下单未支付)
}订单和库存不是同一个聚合——订单改自己的 items,库存改自己的 stock,跨聚合用领域事件异步协调。
⚠️ 坑 2:把库存塞进 Order 聚合,看似省事,实则后期并发扣库存必踩坑(锁竞争、扩容困难)。聚合边界 = 事务边界 = 一致性边界。
四、上下文映射(Context Map)
限界上下文之间,关系类型有 6 种:
text
[订单] --- ACL(防腐层) ---> [支付] 订单用 ACL 翻译支付模型
[商品] --- Shared Kernel ---> [搜索] 共享同一份索引模型
[用户] --- Customer/Supplier ---> [订单] 上游用户,下游订单
[积分] --- Conformist --------> [订单] 积分系统被订单拽着走java
// 防腐层示例:订单上下文调用支付上下文
@Component
public class PaymentAcl {
@Resource
private PaymentClient paymentClient; // 支付接口
public PaymentResult requestPayment(Order order) {
// 把订单模型 → 支付模型
PaymentRequest req = new PaymentRequest();
req.setOutTradeNo(order.getId().value());
req.setAmount(order.getTotalAmount().toFen()); // 元 → 分
req.setSubject("订单" + order.getId().value());
// 支付回调 → 订单事件
PaymentResponse resp = paymentClient.pay(req);
return new PaymentResult(resp.isSuccess(), resp.getTradeNo());
}
}⚠️ 坑 3:不写 ACL 直接调用外部 SDK。当支付升级到 v2、字段重命名,订单代码全崩。把外部依赖包在 ACL 里,改动收敛到一个文件。
五、团队拓扑(Team Topologies)
康威定律:系统结构 = 团队结构。每个限界上下文对应一个特性团队:
text
订单团队(对外澄清业务)
商品团队(对接运营)
支付团队(对接外部银行)
基础设施团队(提供通用平台)团队边界清晰,减少跨团队沟通成本。一个订单需求要 5 个团队评审,就是上下文划分失败。
六、战略输出物
一份合格的战略设计文档应该包含:
text
1. 业务全景图(战略全景)
2. 子域分类(核心/支撑/通用)
3. 限界上下文列表
4. 上下文映射图(关系类型)
5. 关键聚合清单
6. 团队边界本章小结
| 要点 | 关键 |
|---|---|
| 战略 vs 战术 | 战略做边界,战术做代码 |
| 事件风暴 | 橙事件、蓝命令、黄补充 |
| 聚合边界 | 事务边界 = 一致性边界 |
| 上下文映射 | 6 种关系,落 ACL 防耦合 |
| 团队拓扑 | 一个上下文 = 一个团队 |
动手练习
- 事件风暴:挑一个电商业务(下单),列出 5 个领域事件和对应命令
- 聚合划分:画出"订单/库存/支付"三个聚合的边界,标注哪些字段在内、哪些字段用 ID 引用
- ACL 演示:写一段
PaymentAcl调用第三方支付 SDK,然后把订单的Money转换为支付接口的"分"
下一章:第 3 章:战术设计 →