我应该如何在聚合根之间强制执行关系和约束?

时间:2011-05-24 23:58:15

标签: domain-driven-design aggregate aggregateroot

我对DDD模型中两个聚合根之间的引用之间的关系有几个问题。请参阅下面的典型客户/订单模型。

enter image description here

首先,聚合的实际对象实现之间的引用是否应始终通过ID值而不是对象引用来完成?例如,如果我想要订单客户的详细信息,我需要获取CustomerId并将其传递给ICustomerRepository以获取客户,而不是设置Order对象以直接返回客户正确的客户?我很困惑,因为直接返回客户似乎会使编写代码对模型更容易,并且如果我使用像NHibernate这样的ORM,则设置起来并不困难。但我相当肯定这会违反聚合根/存储库之间的界限。

其次,对于两个聚合根,应该在何处以及如何对删除关系实施级联?例如,假设我希望在删除客户时删除所有关联的订单。 ICustomerRepository.DeleteCustomer()方法应该不应该引用IOrderRepostiory吗?这似乎会破坏聚合/存储库之间的界限?我是否应该有一个CustomerManagment服务来处理删除客户及其相关订单,这些订单将引用IOrderRepository和ICustomerRepository?在这种情况下,我如何确保人们知道使用服务而不是存储库来删除客户。这仅仅是为了教育他们如何正确使用模型吗?

2 个答案:

答案 0 :(得分:9)

  

首先,聚合之间的引用是否应始终通过ID值而不是实际的对象引用来完成?

不是真的 - 尽管出于性能原因,有些人会做出改变。

  

例如,如果我想了解订单客户的详细信息,我需要获取CustomerId并将其传递给ICustomerRepository以获取客户,而不是设置Order对象以直接返回客户正确的客户?

通常,您可以为关系建模1侧(例如,Customer.OrdersOrder.Customer)进行遍历。另一个可以从适当的存储库中获取(例如,CustomerRepository.GetCustomerFor(Order)OrderRepository.GetOrdersFor(Customer))。

  

这是否意味着OrderRepository必须知道如何创建客户?这不会超出OrderRepository应该负责的范围......

OrderRepository会知道如何使用ICustomerRepository.FindById(int)。您可以注入ICustomerRepository。有些人可能会对此感到不舒服,并选择将其放入服务层 - 但我认为这太过分了。存储库无法相互了解和使用,没有特别的原因。

  

我很困惑,因为直接返回客户似乎会使编写代码对模型更容易,并且如果我使用像NHibernate这样的ORM,则设置起来并不困难。但我相当肯定这会违反聚合根/存储库之间的界限。

允许聚合根 保存对其他聚合根的引用。实际上,允许任何东西持有对聚合根的引用。但是,聚合根不能保存对不属于它的非聚合根实体的引用。

例如,Customer无法保留对OrderLines的引用 - 因为OrderLines正确地属于Order聚合根目录上的实体。

  

其次,对于两个聚合根,应该在何处以及如何对删除关系实施级联?

如果(我强调,因为这是一个特殊的要求)实际上是一个用例,它表明Customer应该是你唯一的聚合根。但是,在大多数真实世界的系统中,我们实际上不会删除Customer关联的Order - 我们可能会停用它们,移动它们Order s到合并的Customer等等 - 但不会外出和删除Order

话虽如此,虽然我认为它不是纯DDD,但是大多数人会允许一些工作模式的宽容,删除Order然后Customer(如果Order仍然存在则会失败。如果你愿意,你甚至可以让CustomerRepository完成工作(虽然我更愿意让自己更明确)。允许孤立Order以后(或不)清理孤立的Delete也是可以接受的。用例在这里完全不同。

  

我是否应该使用CustomerManagment服务来处理删除客户及其相关订单,这些订单将同时引用IOrderRepository和ICustomerRepository?在这种情况下,我如何确保人们知道使用服务而不是存储库来删除客户。这仅仅是为了教育他们如何正确使用模型吗?

我可能不会选择与存储库密切相关的服务路线。至于如何确保使用服务......您只是不在CustomerRepository上公开Customer。或者,如果删除Order会留下孤立的{{1}},则会引发错误。

答案 1 :(得分:1)

另一种选择是让ValueObject描述Order和Customer AR之间的关联,VO将包含CustomerId以及您可能需要的其他信息 - 名称,地址等(类似ClientInfo或CustomerData)。

这有几个好处:

  1. 您的AR已解耦 - 现在可以进行分区,存储为事件流等。
  2. 在订单AR中,您通常需要在订单创建时保留您有关客户的信息,而不会反映未来对客户所做的任何更改。
  3. 在几乎所有情况下,值对象中的信息都足以执行读取操作(显示订单中的客户信息)。
  4. 要处理客户的删除/停用,您可以自由选择任何您喜欢的行为。您可以使用DomainEvents并发布CustomerDeleted事件,您可以为其提供将Orders移动到存档的处理程序,或删除它们或您需要的任何内容。您还可以对该事件执行多个操作。

    如果出于某种原因无法选择DomainEvents,您可以将Delete操作实现为服务操作而不是存储库操作,并使用UOW在两个AR上执行操作。

    我在尝试做DDD时遇到过很多这样的问题,我认为问题的根源在于开发人员/建模人员倾向于用数据库术语来思考。您(我们:))自然倾向于删除冗余并规范化域模型。一旦你克服它并允许你的模型进化并暗示领域专家的进化,你会发现它并不复杂,而且非常自然。

    更新:如果需要,可以在客户AR内部放置类似的VO - OrderInfo,只包含所需信息 - 订单总数,订单项目数等。