.NET Core领域模型DDD实现咨询:含孙实体的聚合根设计是否合规?
嘿,我来帮你捋捋这个问题——首先得明确DDD里聚合的核心原则:聚合是数据修改和一致性的边界,所有对聚合内实体的操作都应该通过聚合根来完成,不能直接修改子实体或孙实体,而且聚合要保证内部状态始终符合业务规则。结合这个核心,我们可以从几个关键点来判断你的实现是否合规:
聚合根的职责是否完全落地
你的聚合根有没有成为聚合内所有实体操作的唯一入口?比如,如果外部代码能直接调用子实体的UpdateXXX方法、修改孙实体的属性,那这就违反了DDD的规则——聚合根必须掌控所有状态变更的权限,确保每一次修改都经过业务规则校验。举个合理的例子:如果聚合根是Order,子实体是OrderLine,孙实体是OrderLineItem,那应该是Order提供AddOrderLine(...)或UpdateLineItemQuantity(...)这类方法,外部只能通过聚合根触发修改,而不是直接操作OrderLine或OrderLineItem。聚合边界是否合理
你有没有把过多强关联弱的实体塞进同一个聚合?如果孙实体的业务规则可以独立于聚合根,或者它的生命周期能脱离聚合根存在,那这个聚合可能设计得过大了。DDD里聚合要尽量小,只把那些必须保持强一致性的实体放在一起。比如,如果OrderLineItem的修改不需要影响整个Order的状态一致性,那它可能不该属于Order聚合,而是调整为独立聚合根,或者重新划分聚合边界。实体标识规则是否正确
聚合根必须拥有全局唯一ID,而子实体、孙实体只需要在所属的父级范围内唯一即可——它们的ID不需要全局唯一,因为它们的存在完全依赖于聚合根。比如,OrderLine的ID只要在同一个Order里唯一就行,不用整个系统唯一;OrderLineItem同理,只要在所属的OrderLine里唯一。如果你的子/孙实体用了全局ID,这可能暗示它们被设计成可以独立存在,是典型的DDD设计误区。一致性校验是否由聚合根把控
聚合根必须确保每次操作后,整个聚合的状态都符合业务规则。比如添加OrderLineItem时,聚合根要检查数量是否为正、价格是否合理,或者订单总金额是否符合限制。如果子实体自己处理校验逻辑,而聚合根没有把关,很容易导致聚合内部状态不一致。
一个符合规范的代码示例参考
// 聚合根 public class Order { public Guid Id { get; private set; } private readonly List<OrderLine> _orderLines = new(); public IReadOnlyCollection<OrderLine> OrderLines => _orderLines.AsReadOnly(); private Order() { } // 私有构造,通过工厂方法创建 public static Order Create(Guid id) { return new Order { Id = id }; } public void AddOrderLine(string productName, decimal price) { // 聚合根先做业务校验 if (price <= 0) throw new ArgumentException("价格必须为正数"); var orderLine = OrderLine.Create(productName, price); _orderLines.Add(orderLine); } public void UpdateLineItemQuantity(int lineIndex, int itemIndex, int newQuantity) { // 校验索引合法性 if (lineIndex < 0 || lineIndex >= _orderLines.Count) throw new ArgumentOutOfRangeException(nameof(lineIndex)); var targetLine = _orderLines[lineIndex]; // 通过聚合根调用子实体的内部方法,外部无法直接操作 targetLine.UpdateItemQuantity(itemIndex, newQuantity); } } // 子实体 public class OrderLine { public int Id { get; private set; } // 仅在Order内唯一 public string ProductName { get; private set; } public decimal Price { get; private set; } private readonly List<OrderLineItem> _items = new(); public IReadOnlyCollection<OrderLineItem> Items => _items.AsReadOnly(); private OrderLine() { } public static OrderLine Create(string productName, decimal price) { return new OrderLine { Id = new Random().Next(1, 1000), // 聚合内唯一即可 ProductName = productName, Price = price }; } // 仅允许聚合根调用的内部方法 internal void UpdateItemQuantity(int itemIndex, int newQuantity) { if (itemIndex < 0 || itemIndex >= _items.Count) throw new ArgumentOutOfRangeException(nameof(itemIndex)); if (newQuantity <= 0) throw new ArgumentException("数量必须为正数"); var targetItem = _items[itemIndex]; targetItem.UpdateQuantity(newQuantity); } public void AddItem(int quantity) { if (quantity <= 0) throw new ArgumentException("数量必须为正数"); var item = OrderLineItem.Create(quantity); _items.Add(item); } } // 孙实体 public class OrderLineItem { public int Id { get; private set; } // 仅在所属OrderLine内唯一 public int Quantity { get; private set; } private OrderLineItem() { } public static OrderLineItem Create(int quantity) { return new OrderLineItem { Id = new Random().Next(1, 100), Quantity = quantity }; } // 仅允许父实体调用的内部方法 internal void UpdateQuantity(int newQuantity) { Quantity = newQuantity; } }
如果你的代码和上面的示例逻辑类似——聚合根完全掌控操作入口,子/孙实体的修改只能通过聚合根授权的内部方法,聚合边界合理,状态一致性由聚合根把控——那你的实现就是符合DDD规范的。需要重点排查的常见误区包括:外部直接修改子实体状态、聚合过大、子实体使用全局ID、一致性校验脱离聚合根管控这些点。
内容的提问来源于stack exchange,提问作者Palmi

