13

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

在此处输入图像描述

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

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

4

2 回答 2

11

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

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

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

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

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

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

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

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

例如,Customer不能持有对OrderLines- 的引用,因为OrderLines它正确地属于Order聚合根上的实体。

其次,应该在哪里以及如何对两个聚合根执行级联删除关系?

如果(我强调如果,因为它是一个特殊的要求)实际上是一个用例,则表明它Customer应该是您唯一的聚合根。然而,在大多数现实世界的系统中,我们实际上不会删除与sCustomer相关联Order的 a——我们可能会停用它们,将它们Order的 s 移动到合并的 s 中Customer,等等——但不会彻底删除Orders。

话虽如此,虽然我不认为它是纯 DDD,但大多数人会在遵循工作单元模式时允许一些宽大处理,即删除Orders 然后删除 s Customer(如果Orders 仍然存在,则会失败)。如果您愿意,您甚至可以让CustomerRepository工作完成(尽管我更愿意自己更明确地说明)。Order允许稍后(或不)清理孤立的 s 也是可以接受的。用例在这里发挥了重要作用。

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

对于与存储库密切相关的东西,我可能不会走服务路线。至于如何确保使用服务……您只是不要DeleteCustomerRepository. 或者,如果删除 aCustomer会留下孤立Order的 s,则会引发错误。

于 2011-05-25T00:40:03.260 回答
2

另一种选择是有一个 ValueObject 描述订单和客户 AR 之间的关联,VO 将包含 CustomerId 和您可能需要的其他信息 - 名称、地址等(例如 ClientInfo 或 CustomerData)。

这有几个优点:

  1. 您的 AR 已解耦 - 现在可以进行分区、存储为事件流等。
  2. 在订单 AR 中,您通常需要保留在创建订单时所拥有的有关客户的信息,而不是反映未来对客户所做的任何更改。
  3. 在几乎所有情况下,值对象中的信息都足以执行读取操作(显示订单中的客户信息)。

要处理客户的删除/停用,您可以自由选择您喜欢的任何行为。您可以使用 DomainEvents 并发布 CustomerDeleted 事件,您可以为该事件拥有一个处理程序,将订单移动到存档,或删除它们或您需要的任何内容。您还可以对该事件执行多个操作。

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

在尝试做 DDD 时,我看到了很多这样的问题,我认为问题的根源在于开发人员/建模人员倾向于用 DB 术语进行思考。您(我们 :))具有消除冗余和规范域模型的自然倾向。一旦你克服了它并允许你的模型进化并让领域专家参与它的进化,你会发现它并没有那么复杂而且很自然。

更新:如果需要,可以将类似的 VO - OrderInfo 放置在 Customer AR 中,仅包含所需的信息 - 订单总数、订单项目计数等。

于 2011-05-25T08:06:45.300 回答