第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 引用、单聚合事务。
跨聚合:使用领域事件实现最终一致性。
五、思考与练习
为一个电商系统设计聚合,确定聚合根和边界。
为什么"小聚合"是好的设计?
如何处理需要跨聚合的强一致性场景?