9

我第一次尝试 DDD,但我遇到了聚合设计的问题。

我的应用程序包含 3 个实体;图、节点、链接。这些实体中的每一个都有一个可以由用户修改的名称属性(我认为这使得“名称”不适合作为实体 ID)。图表包含节点的集合,节点具有传出链接的集合(出于此问题的目的,忽略传入链接是安全的)。每个节点一次只能与一个图关联(但可以在图之间移动),同样,每个链接在任何给定时间只能与一个节点关联(但可以移动)。

我试图强制执行的不变量是所有实体名称在其父集合中都是唯一的。使用上述架构,不变量在实际集合上,所以我决定集合所有者(图和节点)都应该是聚合根。

我遇到的问题是我现在如何在 Node 上强制执行名称不变?在 Link 上很容易,因为它隐藏在 Node AR 中,因此 Node 可以确认所有 Link 重命名/移动都不会破坏这个不变量。但据我所知,没有什么可以阻止直接重命名 Node 可能会破坏不变量。最终一致性在这里不是可接受的选择,这必须是真正的系统不变量。

我正在考虑的方法是让 Node.Rename() 实际上强制执行不变量,但我担心这涉及查看其父 Graph 以检查重命名是否有效。这“感觉”不太对 - 感觉 Graph 应该是强制执行此命名空间约束的那个,而 Node 应该对此一无所知。

我希望这是有道理的,我期待听到人们的想法。

编辑:上面介绍的域模型是整个域的简化子集。太复杂了,所有实体都无法在单个 AR 中保存......

4

2 回答 2

21

正如您在评论中已经得出的结论,唯一的聚合根应该是 Graph。

聚合和聚合根之间是有区别的。在您的示例中,Graph 和 Node 都是聚合,但负责管理整个聚合的对象是 Graph。所以这是根。

了解对象是否是聚合根的最简单方法是问自己:

将这个对象与其父对象分离是否有意义?

如果答案是否定的,那么它可能不是聚合根。例如,当单个节点不是父图的一部分时,它可能几乎没有用处。这就是为什么您通常只有聚合根的存储库;以防止您访问不属于其相应聚合根的对象。

现在讨论不变量。你说(强调我的):

所有 [Node] 名称在其父 [Graph]中都是唯一的

你基本上就在那里回答了你的问题。在单个节点的上下文中,说它的名称是唯一的是没有意义的。但是在 Graph 的上下文中它确实如此,因为它是Graph 的不变量,而不是 Node。所以 Graph 负责保护这个不变量

至于“神聚合根”,从全球业务角度来看,拥有单一聚合根的情况并不少见。但是 DDD 的一个重要方面是识别系统内的不同上下文。您的系统可能有一个包含许多 Graph 对象的高级根。在这个管理图表的高级上下文中,您可能甚至对图表中的低级 Link 对象都不感兴趣。

根据上下文对域对象建模很重要。这是我在过去几个月里意识到的最重要的事情之一。大多数人了解 DDD 是因为存储库,或者可能是因为实体和值对象,但这些并不像有界上下文那么重要。

尽管Something只有一个业务概念,但拥有多个模型来表示Something的这个概念是完全可以的,每个上下文只有一个实现。一种实现可能是聚合根,而另一种实现只是更大聚合的一部分,这一切都取决于上下文。

常见的软件口头禅是关于代码重用、DRY 等,所以起初让多个类代表同一个业务概念是错误的。但是一旦我能够放下这种感觉并意识到每个实现都有自己的责任,它让事情变得容易多了:)

于 2013-07-31T19:36:40.460 回答
6

我发现这个问题的解决方案来自采用 CQRS 方法。我在这里找到了一个很好的 CQRS 介绍。

在我的“写”模型中;Graph、Node 和 Link 都是 Aggregate Roots,但名称完全由父集合管理。因此,在写入模型中,节点不知道自己的名称是什么(这意味着名称更新必须通过拥有的 Graph)。在相应的“读取”模型中,名称直接与节点相关联(因为这对显示很有用)。

这个解决方案的优点是它允许我保持我的 AR 很小,但是由于“名称”信息保存在父集合中,我在维护交叉聚合不变量方面没有问题。

于 2013-08-06T15:09:56.643 回答