You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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,内部封装两个核心逻辑:
    1. 根据Order ID查询当前关联的Item数量(通过Repository直接查计数,不加载全部Item)。
    2. 校验数量上限,通过后创建并关联Item到Order。
  • Order聚合不再持有Items列表,仅保留必要的标识字段,AddItem方法改为调用领域服务完成操作。
  • 获取单个Item直接通过Item Repository查询。

方案选择建议

  • 如果Item本身有独立的业务逻辑(比如需要单独修改状态、处理库存),优先选方案1,让Item成为独立聚合更符合领域模型设计。
  • 如果只是偶尔需要加载完整订单的Item列表,方案2是性价比最高的折中方案。
  • 如果不想改动现有聚合边界,方案3可以快速解决性能问题,但需要注意领域服务的职责边界,避免变成“大服务”。

内容的提问来源于stack exchange,提问作者Erik Vasilyan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 20:50:36