首页 > 解决方案 > 打破 DDD 聚合根引用规则

问题描述

根据我的阅读,DDD 中的建议是聚合根不应包含对另一个聚合根的引用。最好只保留对 ID 的引用。我想弄清楚我的案子是否有理由违反规则。

以会计系统为例,假设您有一张发票作为聚合根。该发票具有行和付款,它们可以是根下的实体。但随后发票也被分配给买方和供应商。行项目还链接到可以有预算的帐户。买方、供应商、预算,它们本身都是实体,需要管理并有自己的一套规则。但它们也会影响处理发票的业务规则。当然,我可以创建一个域服务并使用它来单独加载东西,但这不只是让我的域对象的性能降低并且更加贫乏吗?

如果我可以持有对实体的引用,我可以运行一个查询并获取我需要的所有数据(我将在 .NET 中使用 Entity Framework Core)。如果我持有对外键的引用,那么我需要为我需要的每个其他聚合运行调用。然后我的域对象不再像它自己那样丰富,因为如果没有一些外部编排器(域服务),它就无法处理它需要的所有业务规则。

我想知道的另一件事是,这些项目根本不会被聚合根修改,并且在该上下文中基本上是只读的,这是否意味着我可以使用更有限的模型将它们放在同一个聚合中(严格最小),然后他们也将有自己的聚合根(例如,我有两个预算实体,一个在发票根下,一个在预算根下)。我的想法是 DDD 并不真正关心底层存储,所以这似乎是一个有效的选择。如果买方/供应商/预算的实体是发票聚合根下的实体,它们也可能会大大简化,而它们的聚合根版本会更复杂,具有更多属性、业务逻辑等。

标签: domain-driven-designaggregateroot

解决方案


DDD 中的建议是聚合根不应持有对另一个聚合根的引用。最好只保留对 ID 的引用。我想弄清楚我的案子是否有理由违反规则。

不,聚合的目的是封装业务逻辑和以“事务”方式实现此业务逻辑所需的数据。您不能允许在根控制之外修改聚合的该部分。如果您的聚合 A 需要聚合 B 来完成其工作,您可以说聚合 B 是聚合 A 的一部分。鉴于聚合 B 可以自行更改(这就是聚合的原因),这意味着聚合 A 的一部分是在其根控制之外进行更改。对不起,那是一个复杂的句子...

有多种原因可能会导致您发现自己需要的聚合比另一个聚合的 Id 更多:

一种可能性是您根本不需要它们。为什么您的发票需要的不仅仅是买家 ID 和供应商 ID。Invoice 执行什么业务逻辑需要买方和供应商的详细信息?如果没有,唯一的原因是能够显示买家和供应商信息(或打印 PDF),那么这不是聚合的关注点。对此的两个解决方案是UI 组合持久读取模型

另一种可能性是您实际上需要由另一个聚合生成的一些数据,而不是聚合本身。例如,您的发票将需要产品价格来计算总金额,但它不需要产品名称、描述和图片。它不需要产品的当前价格。它需要购买时的价格。对于这些场景,您可以将 DTO 传递给聚合的方法,例如,AddInvoiceLine将期望具有InvoiceLineDtoInvoice 所需的属性。一个Use CaseApplication Service operation将生成此 DTO,从一个或多个位置获取数据(例如,部分来自用户输入,部分来自数据提供者)。

所以,我认为你的最后一段是正确的方向,但你首先需要弄清楚,为什么你首先处于这个位置。


推荐阅读