Skip to content
第 2 章 架构 ⏱ 12 分钟阅读

第 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 防耦合
团队拓扑一个上下文 = 一个团队

动手练习 ​

  1. 事件风暴:挑一个电商业务(下单),列出 5 个领域事件和对应命令
  2. 聚合划分:画出"订单/库存/支付"三个聚合的边界,标注哪些字段在内、哪些字段用 ID 引用
  3. ACL 演示:写一段 PaymentAcl 调用第三方支付 SDK,然后把订单的 Money 转换为支付接口的"分"

下一章:第 3 章:战术设计 →

本站基于 VitePress 构建 · 由 StackHub 团队维护