领域驱动设计DDD聚合(Aggregate)与聚合根(Aggregate Root)

一、聚合(Aggregate)

1. 定义

聚合是领域驱动设计(DDD)中的一个重要概念。它指的是一组相关对象的集合,这些对象作为数据更改的一个单元进行处理。聚合内部有明确的边界,聚合内的对象之间有强关联,聚合之间只有通过聚合根才能联系。

2. 作用

  • 划分边界:明确哪些对象属于同一个业务操作单元。
  • 保证一致性:聚合内部的数据一致性由聚合根负责维护。
  • 简化外部访问:外部只能通过聚合根访问聚合内部的对象。

二、聚合根(Aggregate Root)

1. 定义

聚合根是聚合内部的唯一入口,是聚合的代表对象。外部只能通过聚合根来访问和操作聚合内的其他对象,不能直接引用聚合内的其他实体或值对象。

2. 作用

  • 负责聚合内的数据完整性和一致性
  • 对外提供操作接口,屏蔽聚合内部的实现细节。
  • 唯一标识聚合

三、举例说明

例子:电商系统中的订单

1. 领域建模

假设有如下业务对象:

  • Order(订单)
  • OrderItem(订单项)
  • Payment(支付信息)

2. 聚合与聚合根的划分

  • 聚合根:Order
  • 聚合成员:OrderItem、Payment
Order(订单)【聚合根】
  ├── OrderItem(订单项)
  └── Payment(支付信息)

3. 访问和操作规则

  • 只能通过Order对象来访问和修改OrderItem和Payment。
  • 外部不能直接操作OrderItem或Payment,比如不能直接删除一个OrderItem,必须通过Order的方法来删除。

4. 代码示例(伪代码)

public class Order {
    private List<OrderItem> items;
    private Payment payment;

    public void addItem(OrderItem item) {
        items.add(item);
    }

    public void removeItem(OrderItem item) {
        items.remove(item);
    }

    public void pay(Payment payment) {
        this.payment = payment;
    }
}

public class OrderItem {
    private String productId;
    private int quantity;
    // 只能被Order操作
}

public class Payment {
    private String paymentId;
    private double amount;
    // 只能被Order操作
}
  • 这里,Order是聚合根,只有通过Order的方法才能操作OrderItem和Payment。

四、设计原则

1. 聚合的大小

  • 聚合不宜过大,否则会导致性能问题和复杂性提升。
  • 只把强一致性要求的对象纳入同一个聚合。
  • 弱一致性对象可放在不同聚合,通过领域事件等方式同步。

2. 聚合根的职责

  • 聚合根负责所有业务规则和数据一致性。
  • 聚合根的方法要保证聚合内部状态的正确性。
  • 聚合根应对外暴露简洁的操作接口,隐藏聚合内部细节。

五、事务边界

  • 事务只跨越一个聚合。即一次业务操作(如下单、支付)只修改一个聚合的数据,避免分布式事务。
  • 如果需要同时修改多个聚合,建议使用最终一致性(如事件驱动、消息队列)而不是强一致性。

例子
用户下单时,订单(Order)聚合和库存(Inventory)聚合分别处理自己的事务,库存的减少可以通过事件异步通知。


六、持久化策略

  • 只保存聚合根对象,聚合内部的实体和值对象通过聚合根持久化。
  • 外部系统只通过聚合根的ID来引用聚合,避免直接引用聚合内部成员的ID。

例子
订单(Order)聚合持久化时,OrderItem和Payment作为Order的子表或嵌套对象一起保存。


七、聚合与其他领域模型的关系

1. 聚合之间的关系

  • 聚合之间通过聚合根的ID进行关联,而不是通过对象引用。
  • 避免聚合之间直接持有其他聚合的对象引用,这样可以减少耦合。

2. 聚合与实体、值对象的关系

  • 聚合根是实体,有全局唯一标识(ID)。
  • 聚合内部可包含其他实体和/或值对象。
  • 值对象没有唯一标识,只通过属性区分。

八、实际应用注意事项

  1. 建模时要识别聚合边界,找出哪些对象需要强一致性,哪些可以弱一致性。
  2. 聚合根的设计要考虑性能,避免聚合根过大导致数据库锁和并发问题。
  3. 聚合内的对象变化都要通过聚合根完成,保证业务规则和数据完整性。
  4. 聚合之间的交互建议用领域事件或服务协调,避免直接操作其他聚合的数据。

九、进一步举例:用户与地址

假设有如下业务场景:

  • 用户(User)可以有多个收货地址(Address)。
  • 只有用户(User)能添加、删除自己的地址(Address)。

建模:

  • User为聚合根,Address为User聚合内部的实体。
public class User {
    private String userId;
    private List<Address> addresses;

    public void addAddress(Address address) {
        addresses.add(address);
    }

    public void removeAddress(Address address) {
        addresses.remove(address);
    }
}

public class Address {
    private String addressId;
    private String detail;
    // 只能被User操作
}
  • 外部只能通过User的addAddress/removeAddress方法管理地址,不能直接操作Address对象。

十、复杂业务场景下的聚合建模

1. 聚合分拆与协作

在实际业务中,聚合往往并非孤立存在,可能需要与其他聚合协作。例如:

  • 订单与商品库存
    订单(Order)聚合和库存(Inventory)聚合分别有自己的聚合根。下单时,订单聚合根负责订单创建,库存聚合根负责库存扣减。两者之间通过事件或应用服务协调,而不是直接操作对方的数据。

2. 聚合的边界调整

随着业务发展,聚合边界可能需要调整。例如:

  • 用户与地址
    一开始,地址(Address)作为用户(User)聚合的内部实体。但如果地址需要独立被管理(比如地址可以被多用户共享),可以将地址单独建模为聚合,此时用户聚合根只保存地址聚合根的ID。

十一、聚合在微服务与分布式系统中的应用

1. 微服务划分依据

  • 微服务的边界往往与聚合边界一致。每个微服务负责一个或多个聚合,保证服务内部的一致性,服务之间通过API或事件通信。

2. 分布式事务的避免

  • 由于聚合之间事务不能跨界,分布式系统中推荐采用最终一致性,如事件驱动、补偿机制,而非分布式强一致性事务。

例子:

  • 用户下单后,订单服务创建订单,发送“订单已创建”事件,库存服务监听事件后扣减库存。

十二、聚合与CQRS、事件溯源

1. CQRS(命令查询职责分离)

  • 在CQRS架构中,命令模型通常以聚合为单位,负责业务操作和一致性。
  • 查询模型则可以根据需求优化,不必严格遵循聚合边界。

2. 事件溯源(Event Sourcing)

  • 聚合根的每次状态变化都记录为领域事件,聚合根通过事件重放恢复状态。
  • 聚合根成为事件溯源的聚合状态载体。

十三、常见建模误区

1. 聚合过大

  • 把过多对象放入一个聚合,导致性能瓶颈和并发冲突。
  • 解决:只把强一致性需求的对象纳入聚合,弱一致性对象分离。

2. 聚合根“万能”化

  • 聚合根设计成“超级对象”或“上帝对象”,承担过多职责。
  • 解决:只让聚合根负责业务规则和一致性,辅助职责交给领域服务或工厂。

3. 聚合之间直接引用

  • 聚合之间直接持有其他聚合对象,导致耦合和事务扩展。
  • 解决:只通过聚合根ID或领域事件进行通信。

十四、实战建议

  1. 建模时先识别业务场景中的一致性边界,再确定聚合和聚合根。
  2. 聚合根只暴露必要操作,聚合内部对象不对外暴露。
  3. 聚合之间的关系用ID或事件,避免对象引用
  4. 聚合的持久化以聚合根为单位,存储时聚合根负责聚合内部对象的一致性。
  5. 微服务设计时聚合与服务边界保持一致,服务内部操作只涉及本服务的聚合。

十五、总结

  1. 聚合(Aggregate):表示一组相关对象的集合,是业务操作的边界。
  2. 聚合根(Aggregate Root):是聚合的代表对象,是外部访问聚合的唯一入口,负责聚合的一致性和完整性。
  3. 外部只能通过聚合根操作聚合内部对象,不能直接引用或修改聚合内的其他对象
  4. 聚合是领域对象的集合,有边界。
  5. 聚合根是唯一入口,负责聚合的一致性和业务规则。
  6. 外部只能通过聚合根访问和操作聚合内部对象。
  7. 聚合之间通过ID关联,避免直接引用。
  8. 事务只覆盖一个聚合,跨聚合用事件或服务协调。

创作不易,点赞关注,互通有无!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猩火燎猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值