如何在DDD中管理聚合以避免过度耦合?附具体场景示例
避免聚合过度耦合:订单商品项数量限制的优化方案
问题场景
我有一个**订单(Order)聚合,要求其包含的商品项(Item)**数量不能超过100个。最初的实现如下:
原Order聚合代码
public class Order { public List<Item> Items { get; private set; } private readonly int _maxItemCapacity = 100; public Item AddItem(string itemName) { if (Items.Count + 1 > _maxItemCapacity) { throw new Exception("You have reached the maximum limit of items"); } var item = new Item(itemName); Items.Add(item); return item; } public Item GetItem(Guid itemId) { return Items.First(item => item.Id == itemId); } }
原Item代码
public class Item { public Guid Id { get; private set; } public string Name { get; private set; } public Item(string name) { Id = Guid.NewGuid(); Name = name; } }
当前实现的优点是:添加商品项时能在聚合内部完成不变量验证,符合领域驱动设计的原则。但存在明显问题:当需要通过商品项ID获取单个Item时,必须先加载包含所有Item的整个Order聚合——如果订单包含上千个Item,这种方式会带来严重的性能损耗。
我们需要在不加载全部商品项的前提下,让Order保持数量限制的不变量验证,同时避免聚合过度耦合。
可行解决方案
方案1:拆分聚合边界,让Item成为独立聚合
核心思路是调整领域模型的聚合边界,不再让Order持有完整的Item列表,而是只维护关键的统计字段:
- 在Order聚合中新增
ItemCount字段,用于记录当前关联的商品项数量,替代原有的Items.Count做验证。 - 修改Order的
AddItem方法:仅通过ItemCount校验数量上限,验证通过后,直接创建Item并关联到当前Order(Item新增OrderId字段),同时更新ItemCount。 - 获取单个Item时,直接通过Item的Repository根据
Item.Id和Item.OrderId查询,完全不需要加载Order聚合。 - 一致性保障:通过数据库事务或领域事件确保
ItemCount与实际Item数量一致——比如在事务内同时完成Item创建和Order的ItemCount递增,或者创建Item成功后触发事件更新Order的计数。
修改后的核心代码示例:
public class Order { public Guid Id { get; private set; } public int ItemCount { get; private set; } private readonly int _maxItemCapacity = 100; public Order(Guid id) { Id = id; ItemCount = 0; } public Item AddItem(string itemName) { if (ItemCount + 1 > _maxItemCapacity) { throw new Exception("已达到商品项数量上限"); } var item = new Item(Id, itemName); ItemCount++; return item; } } public class Item { public Guid Id { get; private set; } public Guid OrderId { get; private set; } public string Name { get; private set; } public Item(Guid orderId, string name) { Id = Guid.NewGuid(); OrderId = orderId; Name = name; } }
方案2:订单聚合仅维护统计字段,延迟加载Item列表
如果偶尔仍需要加载完整的Order Item列表,可以采用这种折中方案:
- Order聚合保留
ItemCount字段用于数量验证,同时将Items列表改为延迟加载(比如通过ORM的延迟加载特性)。 - 日常添加Item时,仅通过
ItemCount做校验,不需要加载完整的Items列表;只有当确实需要操作全部Item时,才触发列表加载。 - 获取单个Item时,直接走Item的Repository查询,无需加载Order。
这种方案既保留了聚合内的不变量验证,又避免了不必要的大数据量加载。
方案3:用领域服务封装验证逻辑
如果不想调整聚合边界,可以引入领域服务来处理数量验证和Item创建:
- 创建
OrderItemDomainService,内部封装两个核心逻辑:- 根据Order ID查询当前关联的Item数量(通过Repository直接查计数,不加载全部Item)。
- 校验数量上限,通过后创建并关联Item到Order。
- Order聚合不再持有Items列表,仅保留必要的标识字段,
AddItem方法改为调用领域服务完成操作。 - 获取单个Item直接通过Item Repository查询。
方案选择建议
- 如果Item本身有独立的业务逻辑(比如需要单独修改状态、处理库存),优先选方案1,让Item成为独立聚合更符合领域模型设计。
- 如果只是偶尔需要加载完整订单的Item列表,方案2是性价比最高的折中方案。
- 如果不想改动现有聚合边界,方案3可以快速解决性能问题,但需要注意领域服务的职责边界,避免变成“大服务”。
内容的提问来源于stack exchange,提问作者Erik Vasilyan
相关产品推荐
相关产品推荐

