我对DDD模型中两个聚合根之间的引用之间的关系有几个问题。请参阅下面的典型客户/订单模型。
首先,聚合的实际对象实现之间的引用是否应始终通过ID值而不是对象引用来完成?例如,如果我想要订单客户的详细信息,我需要获取CustomerId并将其传递给ICustomerRepository以获取客户,而不是设置Order对象以直接返回客户正确的客户?我很困惑,因为直接返回客户似乎会使编写代码对模型更容易,并且如果我使用像NHibernate这样的ORM,则设置起来并不困难。但我相当肯定这会违反聚合根/存储库之间的界限。
其次,对于两个聚合根,应该在何处以及如何对删除关系实施级联?例如,假设我希望在删除客户时删除所有关联的订单。 ICustomerRepository.DeleteCustomer()方法应该不应该引用IOrderRepostiory吗?这似乎会破坏聚合/存储库之间的界限?我是否应该有一个CustomerManagment服务来处理删除客户及其相关订单,这些订单将引用IOrderRepository和ICustomerRepository?在这种情况下,我如何确保人们知道使用服务而不是存储库来删除客户。这仅仅是为了教育他们如何正确使用模型吗?
答案 0 :(得分:9)
首先,聚合之间的引用是否应始终通过ID值而不是实际的对象引用来完成?
不是真的 - 尽管出于性能原因,有些人会做出改变。
例如,如果我想了解订单客户的详细信息,我需要获取CustomerId并将其传递给ICustomerRepository以获取客户,而不是设置Order对象以直接返回客户正确的客户?
通常,您可以为关系建模1侧(例如,Customer.Orders
或Order.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)。
这有几个好处:
要处理客户的删除/停用,您可以自由选择任何您喜欢的行为。您可以使用DomainEvents并发布CustomerDeleted事件,您可以为其提供将Orders移动到存档的处理程序,或删除它们或您需要的任何内容。您还可以对该事件执行多个操作。
如果出于某种原因无法选择DomainEvents,您可以将Delete操作实现为服务操作而不是存储库操作,并使用UOW在两个AR上执行操作。
我在尝试做DDD时遇到过很多这样的问题,我认为问题的根源在于开发人员/建模人员倾向于用数据库术语来思考。您(我们:))自然倾向于删除冗余并规范化域模型。一旦你克服它并允许你的模型进化并暗示领域专家的进化,你会发现它并不复杂,而且非常自然。
更新:如果需要,可以在客户AR内部放置类似的VO - OrderInfo,只包含所需信息 - 订单总数,订单项目数等。