我应该如何加强聚合根之间的关系和约束?

发布于 2024-11-09 09:37:57 字数 656 浏览 6 评论 0原文

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

在此处输入图像描述

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

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

I have a couple questions regarding the relationship between references between two aggregate roots in a DDD model. Refer to the typical Customer/Order model diagrammed below.

enter image description here

First, should references between the actual object implementation of aggregates always be done through ID values and not object references? For example if I want details on the customer of an Order I would need to take the CustomerId and pass it to a ICustomerRepository to get a Customer rather then setting up the Order object to return a Customer directly correct? I'm confused because returning a Customer directly seems like it would make writing code against the model easier, and is not much harder to setup if I am using an ORM like NHibernate. Yet I'm fairly certain this would be violating the boundaries between aggregate roots/repositories.

Second, where and how should a cascade on delete relationship be enforced for two aggregate roots? For example say I want all the associated orders to be deleted when a customer is deleted. The ICustomerRepository.DeleteCustomer() method should not be referencing the IOrderRepostiory should it? That seems like that would be breaking the boundaries between the aggregates/repositories? Should I instead have a CustomerManagment service which handles deleting Customers and their associated Orders which would references both a IOrderRepository and ICustomerRepository? In that case how can I be sure that people know to use the Service and not the repository to delete Customers. Is that just down to educating them on how to use the model correctly?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

纸短情长 2024-11-16 09:37:57

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

事实并非如此——尽管有些人会出于性能原因进行更改。

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

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

这是否意味着 OrderRepository 必须了解如何创建 Customer 的一些信息?这不是超出了 OrderRepository 应该负责的范围吗...

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

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

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

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

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

如果(我强调如果,因为这是一个特殊的要求)这实际上是一个用例,则表明 Customer 应该是您唯一的聚合根。然而,在大多数现实世界的系统中,我们实际上不会删除具有关联订单客户 - 我们可以停用它们,移动它们他们的订单到合并的客户等 - 但不是彻底删除订单

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

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

我可能不会为与存储库如此密切相关的东西走服务路线。至于如何确保服务被使用......您只需不在 CustomerRepository 上放置公共 Delete 即可。或者,如果删除 Customer 会留下孤立的 Order,您会抛出错误。

First, should references between aggregates always be done through ID values and not actual object references?

Not really - though some would make that change for performance reasons.

For example if I want details on the customer of an Order I would need to take the CustomerId and pass it to a ICustomerRepository to get a Customer rather then setting up the Order object to return a Customer directly correct?

Generally, you'd model 1 side of the relationship (eg., Customer.Orders or Order.Customer) for traversal. The other can be fetched from the appropriate Repository (eg., CustomerRepository.GetCustomerFor(Order) or OrderRepository.GetOrdersFor(Customer)).

Wouldn't that mean that the OrderRepository would have to know something about how to create a Customer? Wouldn't that be beyond what OrderRepository should be responsible for...

The OrderRepository would know how to use an ICustomerRepository.FindById(int). You can inject the ICustomerRepository. Some may be uncomfortable with that, and choose to put it into a service layer - but I think that's overkill. There's no particular reason repositories can't know about and use each other.

I'm confused because returning a Customer directly seems like it would make writing code against the model easier, and is not much harder to setup if I am using an ORM like NHibernate. Yet I'm fairly certain this would be violating the boundaries between aggregate roots/repositories.

Aggregate roots are allowed to hold references to other aggregate roots. In fact, anything is allowed to hold a reference to an aggregate root. An aggregate root cannot hold a reference to a non-aggregate root entity that doesn't belong to it, though.

Eg., Customer cannot hold a reference to OrderLines - since OrderLines properly belongs as an entity on the Order aggregate root.

Second, where and how should a cascade on delete relationship be enforced for two aggregate roots?

If (and I stress if, because it's a peculiar requirement) that's actually a use case, it's an indication that Customer should be your sole aggregate root. In most real-world systems, however, we wouldn't actually delete a Customer that has associated Orders - we may deactivate them, move their Orders to a merged Customer, etc. - but not out and out delete the Orders.

That being said, while I don't think it's pure-DDD, most folks will allow some leniency in following a unit of work pattern where you delete the Orders and then the Customer (which would fail if Orders still existed). You could even have the CustomerRepository do the work, if you like (though I'd prefer to make it more explicit myself). It's also acceptable to allow the orphaned Orders to be cleaned up later (or not). The use case makes all the difference here.

Should I instead have a CustomerManagment service which handles deleting Customers and their associated Orders which would references both a IOrderRepository and ICustomerRepository? In that case how can I be sure that people know to use the Service and not the repository to delete Customers. Is that just down to educating them on how to use the model correctly?

I probably wouldn't go a service route for something so intimately tied to the repository. As for how to make sure a service is used...you just don't put a public Delete on the CustomerRepository. Or, you throw an error if deleting a Customer would leave orphaned Orders.

獨角戲 2024-11-16 09:37:57

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

这有几个优点:

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

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

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

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

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

Another option would be to have a ValueObject describing the association between the Order and the Customer ARs, VO which will contain the CustomerId and additional information you might need - name,address etc (something like ClientInfo or CustomerData).

This has several advantages:

  1. Your ARs are decoupled - and now can be partitioned, stored as event streams etc.
  2. In the Order ARs you usually need to keep the information you had about the customer at the time of the order creation and not reflect on it any future changes made to the customer.
  3. In almost all the cases the information in the value object will be enough to perform the read operations ( display customer info with the order ).

To handle the Deletion/deactivation of a Customer you have the freedom to chose any behavior you like. You can use DomainEvents and publish a CustomerDeleted event for which you can have a handler that moves the Orders to an archive, or deletes them or whatever you need. You can also perform more than one operation on that event.

If for whatever reason DomainEvents are not your choice you can have the Delete operation implemented as a service operation and not as a repository operation and use a UOW to perform the operations on both ARs.

I have seen a lot of problems like this when trying to do DDD and i think that the source of the problems is that developers/modelers have a tendency to think in DB terms. You ( we :) ) have a natural tendency to remove redundancy and normalize the domain model. Once you get over it and allow your model to evolve and implicate the domain expert(s) in it's evolution you will see that it's not that complicated and it's quite natural.

UPDATE: and a similar VO - OrderInfo can be placed inside the Customer AR if needed, with only the needed information - order total, order items count etc.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文