我应该如何加强聚合根之间的关系和约束?
我有几个关于 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.
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
事实并非如此——尽管有些人会出于性能原因进行更改。
通常,您会为关系的一侧(例如,
Customer.Orders
或Order.Customer
)建模以进行遍历。另一个可以从适当的存储库中获取(例如,CustomerRepository.GetCustomerFor(Order)
或OrderRepository.GetOrdersFor(Customer)
)。OrderRepository
会知道如何使用ICustomerRepository.FindById(int)
。您可以注入ICustomerRepository
。有些人可能对此感到不舒服,并选择将其放入服务层 - 但我认为这太过分了。存储库无法相互了解和使用并没有什么特殊原因。聚合根可以保存对其他聚合根的引用。事实上,任何东西都可以保存对聚合根的引用。但是,聚合根无法保存对不属于它的非聚合根实体的引用。
例如,
Customer
无法保存对OrderLines
的引用 - 因为OrderLines
正确地属于Order
聚合上的实体根。如果(我强调如果,因为这是一个特殊的要求)这实际上是一个用例,则表明
Customer
应该是您唯一的聚合根。然而,在大多数现实世界的系统中,我们实际上不会删除具有关联订单
的客户
- 我们可以停用它们,移动它们他们的订单
到合并的客户
等 - 但不是彻底删除订单
。话虽这么说,虽然我不认为这是纯粹的 DDD,但大多数人会在遵循工作单元模式时允许一些宽大处理,在该模式中先删除
Order
,然后删除Customer (如果
Order
仍然存在,则会失败)。如果您愿意,您甚至可以让CustomerRepository
来完成这项工作(尽管我更愿意自己使其更明确)。允许稍后(或不)清理孤立的订单也是可以接受的。用例在这里发挥着重要作用。我可能不会为与存储库如此密切相关的东西走服务路线。至于如何确保服务被使用......您只需不在
CustomerRepository
上放置公共Delete
即可。或者,如果删除Customer
会留下孤立的Order
,您会抛出错误。Not really - though some would make that change for performance reasons.
Generally, you'd model 1 side of the relationship (eg.,
Customer.Orders
orOrder.Customer
) for traversal. The other can be fetched from the appropriate Repository (eg.,CustomerRepository.GetCustomerFor(Order)
orOrderRepository.GetOrdersFor(Customer)
).The
OrderRepository
would know how to use anICustomerRepository.FindById(int)
. You can inject theICustomerRepository
. 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.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 toOrderLines
- sinceOrderLines
properly belongs as an entity on theOrder
aggregate root.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 aCustomer
that has associatedOrder
s - we may deactivate them, move theirOrder
s to a mergedCustomer
, etc. - but not out and out delete theOrder
s.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
Order
s and then theCustomer
(which would fail ifOrder
s still existed). You could even have theCustomerRepository
do the work, if you like (though I'd prefer to make it more explicit myself). It's also acceptable to allow the orphanedOrder
s to be cleaned up later (or not). The use case makes all the difference here.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 theCustomerRepository
. Or, you throw an error if deleting aCustomer
would leave orphanedOrder
s.另一种选择是使用一个 ValueObject 来描述订单和客户 AR、VO 之间的关联,其中将包含 CustomerId 和您可能需要的其他信息 - 名称、地址等(例如 ClientInfo 或 CustomerData)。
这有几个优点:
要处理客户的删除/停用,您可以自由选择您喜欢的任何行为。您可以使用 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:
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.