在DDD中如何理解聚合,聚合根

12 阅读3分钟

在领域驱动设计(DDD)中,**聚合(Aggregate)聚合根(Aggregate Root)**是战术设计的核心模式,用于管理领域对象之间的复杂关系和数据一致性。它们是实现高内聚、低耦合领域模型的关键工具。


1. 聚合(Aggregate)的本质

定义

  • 聚合是一组强关联的领域对象(实体、值对象)的集合,它们形成一个一致性边界,所有操作必须通过聚合根进行。
  • 核心目标:确保聚合内部的数据完整性和业务规则(不变条件),避免分散的业务逻辑和无效状态。

关键特性

  • 一致性边界:一个事务只能修改一个聚合(强一致性),跨聚合操作通过最终一致性(如领域事件)实现。
  • 最小化聚合大小:聚合应尽可能小,避免性能问题(如加载大量数据)。
  • 独立生命周期:聚合内的对象共存亡(例如删除订单聚合根会连带删除所有订单项)。

2. 聚合根(Aggregate Root)的作用

定义

  • 聚合根是聚合的入口点,外部只能通过聚合根与聚合内的对象交互。
  • 职责
    • 维护聚合内部的一致性(例如校验规则)。
    • 封装聚合内的复杂操作。
    • 作为聚合的唯一标识(聚合根的ID即聚合的全局唯一ID)。

示例:订单聚合

// 聚合根:Order
public class Order {
    private String orderId; // 聚合的唯一标识
    private List<OrderItem> items; // 聚合内的值对象
    private OrderStatus status;

    // 添加订单项(业务逻辑内聚到聚合根)
    public void addItem(Product product, int quantity) {
        if (status != OrderStatus.DRAFT) {
            throw new IllegalStateException("Cannot modify a confirmed order.");
        }
        items.add(new OrderItem(product.getId(), quantity));
    }

    // 提交订单
    public void confirm() {
        if (items.isEmpty()) {
            throw new IllegalStateException("Order must have items.");
        }
        this.status = OrderStatus.CONFIRMED;
    }
}

// 值对象:OrderItem
public class OrderItem {
    private String productId;
    private int quantity;
}

3. 聚合设计原则

原则1:通过聚合根访问内部对象

  • 错误做法:直接操作聚合内部对象。
    // ❌ 直接修改OrderItem
    OrderItem item = order.getItems().get(0);
    item.setQuantity(10); 
    
  • 正确做法:通过聚合根方法操作。
    // ✅ 聚合根控制逻辑
    order.updateItemQuantity(productId, 10);
    

原则2:聚合内部强一致性

  • 聚合根确保所有操作满足业务规则。
    public class BankAccount {
        private String accountId;
        private BigDecimal balance;
        
        // 提款操作
        public void withdraw(BigDecimal amount) {
            if (balance.compareTo(amount) < 0) {
                throw new InsufficientBalanceException();
            }
            this.balance = balance.subtract(amount);
        }
    }
    

原则3:通过ID引用其他聚合

  • 避免直接持有其他聚合的引用,通过ID关联。
    public class Order {
        private String customerId; // 引用客户聚合的ID
        // 而不是 private Customer customer;
    }
    

原则4:小聚合优先

  • 大聚合会导致性能问题(如加载大量子对象)。
  • 反例:将整个电商系统的用户、订单、商品放在一个聚合中。
  • 正例:拆分为 UserOrderProduct 三个聚合。

4. 聚合根的持久化(Java示例)

使用JPA/Hibernate实现聚合根的持久化:

@Entity
@Table(name = "orders")
public class Order {
    @Id
    private String orderId;

    @Embedded // 值对象映射
    private Address shippingAddress;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "order_id")
    private List<OrderItem> items = new ArrayList<>();

    // 业务方法省略...
}

@Embeddable // 值对象
public class Address {
    private String street;
    private String city;
}

5. 常见问题与解决方案

问题1:如何确定聚合边界?

  • 答案:通过事件风暴找出业务不变条件,将需要强一致性的对象放在同一聚合。

问题2:聚合根是否允许跨聚合修改数据?

  • 答案:不允许直接修改,应通过领域事件或最终一致性(如订单创建后发布 OrderCreatedEvent,触发库存扣减)。

问题3:聚合根是否可以是值对象?

  • 答案:不可以,聚合根必须是实体(具有唯一标识)。

问题4:如何避免聚合的“贫血模型”?

  • 答案:将业务逻辑内聚到聚合根中,避免将逻辑分散到Service类。

总结

  • 聚合是领域模型的一致性单元,聚合根是其访问入口。
  • 设计聚合的关键是业务不变条件最小化边界
  • 在Java中,通过聚合根封装业务逻辑,结合JPA等技术实现持久化。