3Implementing Domain-Driven Design

聚合

一致性边界

聚合根一致性边界聚合设计ID引用

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第3章:聚合(Aggregates)

一、导读

1.1 本章学习目标

第3章讲解 DDD 中最重要的战术模式——聚合。聚合定义了一致性边界,是数据修改的基本单位。

本章的核心学习目标包括:

  • 理解聚合和聚合根的概念
  • 掌握聚合设计的原则
  • 学会识别一致性边界
  • 理解聚合间的引用方式

1.2 为什么本章如此重要

聚合是 DDD 中最容易被误解的概念。错误的聚合设计会导致性能问题、并发冲突、分布式事务等严重问题。正确的聚合设计是构建可扩展系统的基础。

1.3 核心问题引导

什么是聚合根?它和普通实体有什么区别?

如何确定聚合的边界?

聚合之间应该如何引用?

最终一致性和强一致性如何权衡?


二、核心概念详解

2.1 聚合和聚合根

java
// 聚合:一组相关对象的集合,作为数据修改的单元
// 聚合根:外部访问聚合的唯一入口

// 聚合根
public class Order {
    private OrderId id;
    private CustomerId customerId;    // 引用其他聚合的 ID
    private List<OrderLine> lines;    // 聚合内部的对象
    private OrderStatus status;
    
    // 只有通过聚合根才能修改内部状态
    public void addLine(ProductId productId, int quantity) {
        // 业务规则验证
        if (status != OrderStatus.DRAFT) {
            throw new IllegalStateException("Cannot modify confirmed order");
        }
        lines.add(new OrderLine(productId, quantity));
    }
    
    public void confirm() {
        if (lines.isEmpty()) {
            throw new IllegalStateException("Order has no lines");
        }
        status = OrderStatus.CONFIRMED;
    }
}

// 聚合内部对象(不是聚合根)
public class OrderLine {
    private ProductId productId;
    private int quantity;
    private Money unitPrice;
    
    // 只能通过 Order 来修改
}

2.2 聚合设计原则

聚合设计原则:

1. 真正的不变量
   - 聚合保护的是真正的业务不变量
   - 不是所有关联都应该在同一个聚合中

2. 小聚合
   - 聚合越小越好
   - 大聚合 = 性能问题 + 并发冲突

3. 通过 ID 引用其他聚合
   - 不要在聚合中持有其他聚合的对象引用
   - 使用 ID 引用,需要时通过仓储加载

4. 一次事务只修改一个聚合
   - 跨聚合的修改使用领域事件
   - 接受最终一致性

2.3 聚合间的引用

java
// 错误:持有其他聚合的对象引用
public class Order {
    private Customer customer;    // 错误!
}

// 正确:使用 ID 引用
public class Order {
    private CustomerId customerId;    // 正确!
    
    // 需要 Customer 信息时,通过应用服务获取
}

// 应用服务协调多个聚合
public class OrderApplicationService {
    public void placeOrder(PlaceOrderCommand cmd) {
        // 加载需要的聚合
        Customer customer = customerRepository.findById(cmd.getCustomerId());
        Order order = new Order(cmd.getCustomerId());
        
        // 执行业务逻辑
        for (OrderLineCommand line : cmd.getLines()) {
            Product product = productRepository.findById(line.getProductId());
            order.addLine(product, line.getQuantity());
        }
        
        // 保存
        orderRepository.save(order);
    }
}

三、实战案例

3.1 案例一:电商订单聚合设计

聚合分析:

Order 聚合:
  聚合根:Order
  内部对象:OrderLine
  不变量:
    - 已确认的订单不能修改
    - 订单必须有至少一行
    - 订单总金额 = 各行金额之和

Product 聚合(独立聚合):
  聚合根:Product
  不变量:
    - 商品名称不能为空
    - 价格必须大于 0

Customer 聚合(独立聚合):
  聚合根:Customer
  不变量:
    - 邮箱格式必须有效
    - 用户名必须唯一

聚合间关系:
  Order → Customer:通过 CustomerId 引用
  Order → Product:通过 ProductId 引用(在 OrderLine 中)

四、本章小结

聚合:一组相关对象的集合,作为数据修改的单元。

聚合根:外部访问聚合的唯一入口。

设计原则:真正的不变量、小聚合、ID 引用、单聚合事务。

跨聚合:使用领域事件实现最终一致性。


五、思考与练习

为一个电商系统设计聚合,确定聚合根和边界。

为什么"小聚合"是好的设计?

如何处理需要跨聚合的强一致性场景?


3/7聚合
0%