聚合是 DDD 中最难的概念之一。你大部分都是对的。我建议用聚合的“成员资格”来表达这个概念比引入术语“子对象”更直接。
是的,一个对象不能是多个聚合的成员。哪一个将是最终的执行者?一个聚合根可以通过删除成员并孤立另一个聚合中的其他成员来轻松使另一个聚合根失效。您是对的,在一个对象似乎需要多个聚合中的成员资格的情况下,该对象必须是一个独立的实体,即它成为新聚合的根。 (它可能有也可能没有其他成员,但如果没有,那么它当然会成为它自己的聚合体。)
是的,确实存在一个聚合来强制不变量。就持久性而言,它也是单个工作单元或单个事务。聚合根最终对其整个成员资格中的所有不变量负责,这一定是因为,例如,不变量的失败可能会导致持久性失败,而聚合负责将聚合维护为持久性工作的可行的单个单元。
然而,这是微妙和困难的部分,最终的责任并不意味着总体也是primary执行者也。就像我们的司法系统一样——法院最终是决定法律问题的最终场所,也是实施最终法治、执行不变量的地方。但实际执行(和合规)发生在系统的许多级别。事实上,在一个秩序良好的社会中,大多数实施法治的活动——执行不变量——应该在你到达法庭之前就发生,你甚至根本不必依赖例行公事。 (尽管在 DDD 中,您可能总是希望聚合根在持久化之前进行最终的不变扫描。)
你的建议就很不一样了,本质上你们整个社会除了法庭之外都被监禁了,而且你似乎还建议其他人甚至不能探视,只能向法庭传递信息,希望法庭能够采取适当的行动。
让我们看看如果您按照建议的路径操作,您的域会发生什么情况。目标是创建一个丰富且富有表现力的领域模型。就有意义的普遍语言而言,您已将工作词汇量减少到仅聚合词根。由于不变量,实体应该由聚合根访问,而且还因为如果设计正确,则实体具有有意义的标识,该标识源自其聚合根上下文中的成员身份。但是您的建议实体在其聚合根之外甚至没有任何类型标识。埃文斯特别指出,这是聚合根的目的的一部分——允许对象通过遍历来获取对成员的引用。但是您无法获得有用的引用,因为另一个对象甚至不知道您的成员类型存在。或者您可以更改名称空间,但如果您不允许遍历,那也好不到哪儿去。现在,您的整个域都知道类型,但永远无法获取对象的类型。
更糟糕的是你的聚合根会发生什么。除了维护聚合完整性之外,聚合根通常应该有其自身存在的理由。但现在这个身份已经不明确了。它因需要为所有各种元素及其属性提供包装方法而变得模糊。你得到的是聚合根,不再具有表现力,甚至没有明确的身份,只是巨大而笨拙的上帝物体。
您的 Order 和 OrderLine 示例就是一个有趣的例子。该订单不代表订单行强制执行订单行所需的某些不变量。在这种情况下,它控制动作以强制执行它自己的不变量。这是控制聚合根的有效操作。然而,更典型的聚合主要涉及对象的创建和/或销毁。
当然,不需要强加一个模型,其中所有状态更改都必须由聚合根自动应用,而不是直接应用。事实上,这通常就是聚合根允许遍历获取对成员的引用的原因 - 因此外部对象可以应用状态更改,但在聚合控制要更改的成员实体的实例生命周期的上下文中。
不仅可见性,而且与更大领域的实际交互通常也是开发丰富且富有表现力的模型的基础。聚合用于控制该访问,但不能完全消除它。
我希望这会有所帮助,这是一个很难讨论的概念。