Domain driven design 在实现DDD时保护聚合根的不变量和子项

Domain driven design 在实现DDD时保护聚合根的不变量和子项,domain-driven-design,Domain Driven Design,在我开始学习DDD的过程中,我从一个简单的领域模型开始,随着时间的推移,我将逐步建立这个模型。在本例中,我的域是通常的订单>订单项,以保持简单,并能够在以后添加发票等。以下是我到目前为止的内容: public class Order { private readonly IList<OrderItem> _orderItems; public Guid Id { get; private set; } public bool Completed { get;

在我开始学习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;
    }
}
公共类秩序
{
私有只读IList_orderItems;
公共Guid Id{get;private set;}
公共bool已完成{get;private set;}
已创建公共日期时间{get;private set;}
公共IEnumerable订单项
{
获取{return\u orderItems;}
}
公共秩序()
{
Id=新Guid();
Created=DateTime.UtcNow;
_orderItems=新列表();
}
公共无效AddOrderItem(整数数量、整数单位成本)
{
var orderItem=新的orderItem(数量、单位成本);
_添加(orderItem);
}
公共无效完成顺序()
{
完成=正确;
}
}
公共类OrderItem
{
公共整数数量{get;私有集;}
公共整数单位成本{get;私有集;}
公共订单项目(整数数量、整数单位成本)
{
数量=数量;
单位成本=单位成本;
}
}
我最终会将数量和单位成本转化为价值对象,但这不是这里的重要部分。正如DDD所鼓吹的,我们总是想保护我们的不变量,但我有一个小问题。从订单中,可以通过调用AddOrderItem()方法并传递数量和单位成本来添加新的OrderItem

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

我错过什么了吗?还是仅仅接受模型团队需要了解DDD的基本原理


更新


谢谢@theDmi、@guillaume31、@Matt,因为你们都提供了一些好的观点。我认为在这一点上很清楚,存储库的接口应该足够清楚,您不能对自己创建的OrderItem执行任何操作。将OrderItem的ctor设置为internal也有助于强制执行此限制,但可能不需要。我计划看看有没有内部ctor会发生什么。最终,我接受@guillaume31答案的原因是对双向关系的评论。这很有道理,例如,我过去在EF中遇到过这个问题,因此我也喜欢将其保持为单边的想法。

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

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

“没有
订单,
订单项
就不可能存在。”。好吧,至少它在聚合中不是一个不变量。根据定义,不变量只查看一个聚合内的(或跨多个聚合的范围),而不是在聚合外徘徊的对象

OrderItem构造函数可能应该具有订单 参数,因为没有订单,OrderItem不能存在

我不会那样做,因为

  • 不建议在实体之间建立双向关系。它可能会导致同步问题(A指向B,但B指向其他对象),如果可以,最好使用单向关系

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


类构造函数可以是包保护的,或者甚至可以在
顺序中定义类。然而,即使一个程序员在内存中创建一个新的
OrderItem
,它会产生什么影响?由于没有
OrderItemRepository
,它不能自己持久化。为什么不将OrderItem类的构造限制在内部范围内?域库边界之外的任何人都无法创建OrderItem,因此,OrderItems的创建必须由聚合根处理,这是它应该做的。您将如何对这种关系建模?是的,确实,在聚合之外做事情实际上不会影响系统,但事实上,有些人可以编写不符合域的代码,这不是必须避免的事情吗?如果没有订单就创建OrderItem没有任何意义,那么我为什么要首先创建它?如果有一种中立的、非侵入性的方式来强制执行“没有订单就不能存在”规则,那么你肯定应该这样做。唉,我认为治疗(双向关系)比疾病更糟糕。毕竟,你的团队中的程序员不应该忽视DDD规则。有时你甚至必须保护代码不受团队成员的影响!真实的故事!我认为应该总是有某种组织过程来控制这些——代码审查、结对编程等等。现在,如果一些人久而久之被证明不受团队文化和习俗的影响,也许