领域驱动设计(DDD)

DDD 战略设计(限界上下文、子域)与战术设计(聚合、实体、值对象、领域事件),及事件风暴实践方法。

领域驱动设计(DDD)

DDD 是一种以业务领域为核心的软件设计方法,通过统一语言连接业务与技术,解决复杂业务系统的建模难题。

1. DDD 核心思想

┌────────────────────────────────┐
│        统一语言 Ubiquitous     │
│           Language             │
└────────────────────────────────┘
          ↓          ↑
┌────────────────────────────────┐
│        领域模型 Domain Model   │
│  ┌─────┐ ┌─────┐ ┌────────┐  │
│  │实体 │ │值对象│ │ 聚合   │  │
│  └─────┘ └─────┘ └────────┘  │
└────────────────────────────────┘

核心:技术与业务使用同一套语言进行沟通,模型直接反映业务概念。

2. 战略设计

子域(Subdomain)

将复杂业务划分为不同类型:

类型特征示例
核心域竞争优势所在,投入最多电商的推荐算法
支撑子域需要定制但不独特订单管理
通用子域可外购或标准化用户认证、通知服务

限界上下文(Bounded Context)

模型一致性的边界。同一个概念在不同上下文可以有不同的含义。

┌─────────────────┐    ┌─────────────────┐
│  商品上下文      │    │  库存上下文      │
│  Product        │    │  Product        │
│  - name         │    │  - sku          │
│  - description  │    │  - quantity     │
│  - price        │    │  - warehouse    │
│  - category     │    │  - location     │
└─────────────────┘    └─────────────────┘
         ↑                    ↑
    「商品」是销售实体      「商品」是库存单位

上下文映射

┌──────────────┐     合作关系      ┌──────────────┐
│  订单上下文   │ ←─────────────→ │  支付上下文   │
└──────────────┘                 └──────────────┘
        │
        │ 上游/供应商
        ↓
┌──────────────┐
│  库存上下文   │
└──────────────┘
关系含义
合作关系任一方失败影响另一方
客户-供应商上游优先满足下游需求
跟随者复用上游模型
防腐层 (ACL)隔离外部模型影响
开放主机定义清晰接口供外部使用

3. 战术设计

实体(Entity)

有唯一标识且通过标识区分的对象,生命周期中属性可变。

public class Order {
    private OrderId id;          // 唯一标识
    private CustomerId customerId;
    private List<OrderItem> items;
    private OrderStatus status;
    
    public void pay() {          // 业务行为
        if (status != PENDING) throw new IllegalStateException();
        this.status = PAID;
    }
    
    public void ship() { ... }
}

值对象(Value Object)

属性决定身份,不可变,无生命周期概念。

public record Money(BigDecimal amount, Currency currency) {
    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) 
            throw new IllegalArgumentException("Currency mismatch");
        return new Money(this.amount.add(other.amount), this.currency);
    }
}

public record Address(
    String province, 
    String city, 
    String street
) {}

聚合(Aggregate)

一致性边界内的实体与值对象集群。

// Order 是聚合根(Aggregate Root)
public class Order {
    private OrderId id;
    private List<OrderItem> items;  // 聚合内实体
    private ShippingAddress address; // 值对象
    
    // 所有修改通过聚合根
    public void addItem(ProductId productId, int quantity, Money price) {
        items.add(new OrderItem(productId, quantity, price));
        recalculateTotal();
    }
}

聚合设计原则:

  • 一个事务只修改一个聚合
  • 聚合根负责维护不变性
  • 聚合间通过 ID 引用(避免直接对象引用)

领域服务(Domain Service)

不属于某个聚合的业务逻辑。

public class PricingService {
    public Money calculateDiscount(Order order, Customer customer) {
        if (customer.isVIP()) {
            return order.getTotal().multiply(0.8);
        }
        return order.getTotal();
    }
}

领域事件(Domain Event)

领域内发生的有意义的事情,实现最终一致性。

public class OrderCreatedEvent {
    private final OrderId orderId;
    private final CustomerId customerId;
    private final Instant occurredOn;
    
    // 用于其他上下文处理后续业务
}

// 发布
public class Order {
    public void create() {
        // ...
        registerEvent(new OrderCreatedEvent(this.id, ...));
    }
}

4. 事件风暴(Event Storming)

Alberto Brandolini 发明的协作建模工作坊。

流程

1. 领域事件(橙色) → OrderCreated, PaymentReceived
2. 命令(蓝色)     → CreateOrder, ReceivePayment
3. 聚合(黄色)     → Order, Payment
4. 策略(紫色)     → 如果支付失败则取消订单
5. 外部系统(粉色) → 支付网关、物流系统

产出物

  • 限界上下文划分
  • 聚合边界
  • 领域事件流
  • 服务拆分候选

5. DDD 分层架构

┌─────────────────────────────────────┐
│  用户接口层(UI/API)                │
│  适配器模式、DTO、输入校验            │
├─────────────────────────────────────┤
│  应用层(Application)               │
│  用例编排、事务控制、事件发布         │
├─────────────────────────────────────┤
│  领域层(Domain)← 核心              │
│  实体、值对象、聚合、领域服务、事件   │
├─────────────────────────────────────┤
│  基础设施层(Infrastructure)        │
│  数据库、消息队列、外部 API 适配      │
└─────────────────────────────────────┘

依赖方向:外层依赖内层,领域层无外部依赖。

6. DDD 与微服务

限界上下文 → 微服务边界
聚合根     → 服务内模块
领域事件   → 服务间通信
防腐层     → API Gateway / Adapter
DDD 概念映射微服务
核心域独立团队维护的核心服务
通用子域共享基础设施服务
限界上下文服务部署边界
领域事件事件驱动架构

总结

层面关键产出工具
战略设计子域划分、限界上下文事件风暴
战术设计聚合、实体、领域服务代码模型
技术实践分层架构、事件驱动框架实现

7. DDD 实施的常见陷阱与应对

DDD 在实践中常被误用或过度设计,以下是几个典型问题及应对策略。

7.1 “贫血领域模型"症候群

这是 DDD 实践中最常见的问题:实体只有 getter/setter,所有业务逻辑散落在 Service 层,领域模型沦为数据容器。

// ❌ 贫血模型
public class Order {
    private Long id;
    private String status;
    // 只有 getter/setter
}

// Service 层充斥业务逻辑
public class OrderService {
    public void cancelOrder(Long orderId) {
        Order order = orderRepo.findById(orderId);
        if (!"PENDING".equals(order.getStatus())) {
            throw new IllegalStateException("Cannot cancel non-pending order");
        }
        order.setStatus("CANCELLED");
        orderRepo.save(order);
    }
}

修正方向:将状态校验、计算等逻辑下沉到实体中,让 Order 自己知道自己的业务规则。

7.2 聚合粒度过大

初学者常将整张领域大图塞进一个聚合,导致事务范围膨胀、并发冲突严重。正确的原则是一个事务只修改一个聚合,聚合间通过异步事件最终一致。

// ❌ 错误:订单聚合包含物流、支付等跨域信息
public class Order {
    private List<OrderItem> items;
    private Payment payment;        // 不应在同一聚合
    private Shipment shipment;      // 不应在同一聚合
}

// ✅ 正确:订单聚合只包含订单自身的核心信息
public class Order {
    private List<OrderItem> items;
    private Money totalAmount;
    // 支付和物流通过领域事件异步关联
}

7.3 统一语言流于形式

“统一语言"需要持续投入:每次需求评审都要确认术语定义,每次发现"这个词我们理解不一样"都要更新词汇表。建议维护一份在线的领域词汇表(Glossary)。

7.4 何时不应使用 DDD

场景建议方案原因
CRUD 为主的简单应用事务脚本 / MVC领域复杂度低,DDD 过度设计
技术验证原型(MVP )快速迭代,后期再建模初期需求不稳定
纯数据报表系统SQL + ETL无复杂业务规则
团队缺乏 OOP 经验先补基础再引入 DDDDDD 依赖扎实的面向对象设计能力

DDD 适用于业务逻辑复杂、领域模型会随业务发展持续演进的系统。对于简单应用,DDD 的建模范式反而会增加不必要的认知负担。

DDD 的价值不在于严格按照名词定义编码,而在于培养以业务领域为核心思考问题的能力。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「架构」更多文章

  1. 韧性工程与错误预算:从 SLO 到故障演练
  2. 微前端架构:组合、隔离与独立部署
  3. 数据网格(Data Mesh):领域数据产品与去中心化治理