在实施 DDD 时保护聚合根的不变量和子项

Protecting invariants and children of aggregate roots when implementing DDD

在我开始学习 DDD 的过程中,我从一个简单的领域模型开始,随着时间的推移我将逐渐建立它。在这种情况下,我的域是通常的订单 > 订单项目,以便让事情变得简单,并能够在以后添加发票等。这是我目前所拥有的:

public class Order
{
    private readonly IList<OrderItem> _orderItems;

    public Guid Id { get; private set; }
    public bool Completed { get; private set; }
    public DateTime Created { get; private set; }
    public IEnumerable<OrderItem> OrderItems
    {
        get { return _orderItems; }
    }

    public Order()
    {
        Id = new Guid();
        Created = DateTime.UtcNow;
        _orderItems = new List<OrderItem>();
    }

    public void AddOrderItem(int quantity, int unitCost)
    {
        var orderItem = new OrderItem(quantity, unitCost);
        _orderItems.Add(orderItem);
    }

    public void CompleteOrder()
    {
        Completed = true;
    }
}

public class OrderItem
{
    public int Quantity { get; private set; }
    public int UnitCost { get; private set; }

    public OrderItem(int quantity, int unitCost)
    {
        Quantity = quantity;
        UnitCost = unitCost;
    }
}

我最终会将 Quantity 和 UnitCost 变成值对象,但这不是这里的重要部分。正如 DDD 鼓吹的那样,我们总是想保护我们的不变量,但我在其中遇到了一点麻烦。从订单中,您可以通过调用 AddOrderItem() 方法并传递您的数量和单位成本来添加新的 OrderItem。

我现在的问题是如何阻止另一个编码员使用 var orderItem = new OrderItem(1, 2) 创建新的 OrderItem? OrderItem 构造函数可能应该有一个 Order order 参数,因为 OrderItem 不能在没有订单的情况下存在,但同样现在其他编码器可以调用 new OrderItem(new Order(), 1, 2)?

我错过了什么吗?或者只是接受模型团队需要了解 DDD 的基础知识?


更新

感谢@theDmi、@guillaume31、@Matt,你们都提供了一些好的观点。我认为在这一点上已经很清楚了,存储库的接口应该足以表明您不能对它自己创建的 OrderItem 做任何事情。将 OrderItem 的构造函数设置为 internal 也有助于强制执行此限制,但可能不需要这样做。我打算看看有没有内部 ctor 会发生什么。最终,我接受@guillaume31 回答的原因是关于双向关系的评论。这很有道理,我过去曾遇到过这个问题,例如 EF,所以我也喜欢保持单边的想法。

使用 DDD 时,所有尝试通过存储库更改系统状态 运行,因为您需要先检索要处理的聚合。 因此,即使有人创建了在某个实体之外毫无意义的对象,他们也无法用它做任何有用的事情。

关于这个问题,DDD甚至比基于CRUD的系统更有优势:它导致了高可发现性。首先,存储库界面告诉您可以加载什么。然后你得到一个聚合,它反过来提供以有意义的方式修改聚合的操作。

"没有 Order 就不可能存在 OrderItem" 并不是真正的不变量。好吧,至少它不是 Order 聚合中的不变量。根据定义,不变量仅查看 内部 一个聚合(或跨越多个聚合)的事物,而不是在聚合外部徘徊的事物。

The OrderItem constructor should probably have an Order order parameter since an OrderItem cannot exist without an Order

我不会那样建模,因为

  • 不推荐实体之间的双向关系。它会导致同步问题(A指向B但B指向其他东西),如果可以的话最好有单向关系。

  • 通过这样做,您的最终目标是限制聚合外部发生的事情,这并不是 DDD 的真正意义,并且正如其他答案所示,可有可无。 DDD 系统中的所有更改都经过聚合和存储库。