DDD - 强制使用小聚合根的不变量

时间:2013-07-31 10:27:25

标签: domain-driven-design aggregateroot invariants

我第一次尝试使用DDD,而且我遇到了聚合设计问题。

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

我试图强制执行的不变量是所有实体名称在其父集合中是唯一的。使用上面描述的体系结构,不变量在实际集合上,因此我认为集合所有者(图形和节点)都应该是聚合根。

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

我正在考虑的方法是让Node.Rename()实际强制执行不变量,但我担心的是这涉及查看其父Graph以检查重命名是否有效。这并没有感觉到#39;对 - 感觉Graph应该是强制执行此命名空间约束的那个,而Node根本不应该知道它。

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

编辑:上面介绍的域模型是整个域的简化子集。对于在一个AR中持有的所有实体来说太复杂了.......

2 个答案:

答案 0 :(得分:19)

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

聚合和聚合之间存在差异。在您的示例中,Graph和Node都是聚合,但负责管理整个聚合的对象是Graph。所以这就是根源。

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

  

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

如果答案为否,那么它可能不是聚合根。例如,当一个Node不是父Graph的一部分时,它可能没用。这就是为什么你通常只有聚合根的存储库;防止自己访问不属于相应聚合根目录的对象。

现在转向不变量。你说(强调我的):

  

所有[Node]名称在其父[Graph]

中是唯一的

你基本上在那里回答了你的问题。在单个节点的上下文中,说它的名称是唯一的没有意义。但是在Graph的上下文中,它确实是因为它是Graph 的不变量,而不是Node。因此,图表负责protecting this invariant

对于'神聚合根',从全球业务角度来看,拥有单个聚合根并不罕见。但DDD的一个重要方面是识别系统内的不同上下文。您的系统可能具有包含许多Graph对象的高级根。在管理图形的高级环境中,您可能甚至对图中的低级链接对象不感兴趣。

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

尽管Something只有一个商业概念,但完全正确有多个模型代表Something的概念,每个上下文都有一个实现。一个实现可能是聚合根,而另一个实现只是更大聚合的一部分,所有这些都取决于上下文。

常见的软件咒语是关于代码重用,DRY等等,所以起初认为有多个代表相同业务概念的类是错误的。但是,一旦我能够放下这种感觉并意识到每个实施都有自己的责任,它就会让事情变得如此简单:)

答案 1 :(得分:6)

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

在我的'写'模型中;图形,节点和链接都是聚合根,但名称完全由父集合管理。因此,在写模型中,节点不知道它自己的名称是什么(意味着名称更新必须通过拥有的图形)。在相应的“读取”模型中,名称直接与节点关联(因为这对于显示非常有用)。

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