在我开始学习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
参数,因为没有Order就不能存在OrderItem,但现在其他编码器只能调用新的OrderItem(new Order(),1,2)?
我错过了什么吗?或者只是接受了使用该模型的团队需要了解DDD的基本原理?
更新
感谢@theDmi,@ guillaume31,@ May,因为你们都提供了一些好处。我认为在这一点上非常清楚,存储库的界面应该足以清楚地表明你不能对自己创建的OrderItem做任何事情。将OrderItem的ctor设置为internal也有助于强制执行此限制,但可能不需要。我计划看看有没有内部ctor会发生什么。最终,我接受@ guillaume31的答案的原因是关于双向关系的评论。这很有道理,我过去曾经在EF中遇到过这个问题,所以我喜欢把它保持单边的想法。
答案 0 :(得分:5)
使用DDD时,所有更改系统状态的尝试都会通过存储库运行,因为您需要首先检索要处理的聚合。 因此,即使某人创建的对象在某个实体之外没有任何意义,他们也无法对其进行任何有用的操作。
关于这个问题,DDD甚至优于基于CRUD的系统:它具有很高的可发现性。首先,存储库界面会告诉您可以加载的内容。然后你得到一个聚合,它反过来提供以有意义的方式修改聚合的操作。
答案 1 :(得分:5)
&#34; 如果没有OrderItem
&#34; Order
就不能存在实际上并不是一个不变的。好吧,至少它不是Order
聚合中的不变量。根据定义,不变量只会查看内部一个聚合(或跨越多个聚合)的内容,而不是在聚合外部游荡的内容。
OrderItem构造函数可能应该有一个Order订单 参数,因为OrderItem在没有Order
的情况下不能存在
我不会那样建模,因为
不建议实体之间的双向关系。它可能导致同步问题(A指向B但B指向其他东西),如果可以的话,最好有单向关系。
通过这样做,你的最终目标是限制聚合之外发生的事情,这不是DDD的重点,而且正如其他答案所显示的那样,是可有可无的。 DDD系统中的所有更改都通过聚合和存储库。