在实施 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 系统中的所有更改都经过聚合和存储库。
在我开始学习 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 系统中的所有更改都经过聚合和存储库。