问题描述
我一直在阅读 Evans 关于 DDD 的书,并且正在考虑如何在 .NET 中实现聚合.目前,我只能想出一种方法;在单独的类库中隔离聚合.然而,这似乎有点矫枉过正(我更愿意将所有域对象保存在一个库中),我想知道是否有不同的方法?
I've been reading Evans' book on DDD and am thinking about how one should implement aggregates in .NET. Currently, I can only come up with one way; isolating the aggregates in separate class libraries. This, however, seems like a bit of overkill (I'd prefer to keep all domain objects in one library) and I wonder if there is a different way?
1 lib/aggregate 的原因如下:聚合根需要知道对它负责的子对象"的所有访问,聚合根也可以返回子对象作为其成员的结果.因此,这些子对象的成员(聚合根需要)不能公开.所以你唯一的选择是让它们成为内部的(因为它们仍然需要由聚合根调用).但是,通过将所有聚合放在一个项目中,仍然可以从已获得子对象的其他域对象访问这些成员.这是不可取的,因为它允许绕过聚合根.通过分离不同库中的所有聚合,这个问题得以解决.
The reasoning for the 1 lib/aggregate is as follows: The aggregate root needs to be aware of all access to 'sub-objects' it is responsible for, also the aggregate root can return sub-objects as results of its members. Therefore, members (needed by the aggregate root) of these sub-objects can't be made public. So your only option is making them internal (since they still need to be called by the aggregate root). By putting all aggregates in one project, however, it is still possible to access these members from other domain objects that have obtained the sub-object. This is undesirable because it allows one to bypass the aggregate root. By separating all aggregates in different libraries this problem is solved.
一些附加信息:
我已经查看了 DDD java 示例代码 并且它们打包了每个聚合(包括所有子对象' 类)在不同的包中.只能从聚合根调用的成员没有访问修饰符(例如:Delivery.updateOnRouting
).在 java 中,没有访问修饰符的成员是 package-private(可用仅来自同一个包).所以这将是正确的行为.
I've checked out the DDD java sample code and they pack every aggregate (including all sub-objects' classes) in a different package. Members that can only be called from the aggregate root have no access modifier (for example: Delivery.updateOnRouting
). In java, members without access modifier are package-private (available only from the same package). So this would be correct behavior.
.NET 示例代码,然而,将所有领域对象放在一个类库中,然后使相应的成员公开.在我看来,这似乎不正确.
The .NET sample code, however, puts all domain objects in one class library, and then makes the corresponding members public. To me, this seems incorrect.
推荐答案
聚合是 DDD 中最困难的概念之一.你大部分是对的.我建议用聚合中的成员资格"来表达这个概念比引入术语子对象"更直接.
Aggregates are one of the most difficult concepts in DDD. You have most of it right. I suggest expressing the concept in terms of "membership" in an aggregate is more straightforward than introducing the term "subobjects".
是的,一个对象不能是多个聚合的成员.哪一个会是最终的执法者?通过删除成员并孤立另一个聚合中的其他成员,一个聚合根可以很容易地使另一个无效.您是对的,在对象似乎需要多个聚合中的成员身份的情况下,该对象必须是一个独立的实体,即它成为新聚合的根.(它可能有也可能没有额外的成员,但如果没有,那么它当然会成为它自己的聚合.)
Yes an object cannot be a member of more than one aggregate. Which one would be the final enforcer? One aggregate root could easily invalidate another by deleting members and orphaning other members in the other aggregate. You are correct, in scenarios where an object would appear to need membership in multiple aggregates, that object must be an independent entity, i.e it becomes the root of a new aggregate. (It may or may not have additional members, but if it does not then of course it becomes it's own aggregate of one.)
是的,存在一个聚合来强制执行不变量是绝对正确的.就持久性而言,它也是单个工作单元或单个事务.聚合根最终负责整个成员中的所有不变量,这肯定是因为,例如,不变量的失败可能导致持久性失败,并且聚合负责将聚合维护为一个可行的持久性工作单元.
And yes, it is absolutely true an aggregate exists to enforce invariants. It is also a single unit of work or a single transaction in terms of persistence. An aggregate root is ultimately responsible for all the invariants across it's entire membership, it must be because, for example, failure of an invariant could cause persistence to fail, and the aggregate is responsible for maintaining the aggregate as a viable single unit of persistence work.
然而,这是微妙和困难的部分,最终责任并不意味着总体也是主要执行者.就像我们在司法系统中所拥有的一样 - 法院最终是确定法律事务和实施最终法治的最终场所,执行不变量.但实际的执行(和合规)发生在系统的许多级别.事实上,在一个秩序井然的社会中,大多数强加法律规则的活动——执行不变量——应该在你上法庭之前就发生,你甚至根本不必依赖例行上法庭.(尽管在 DDD 中,您可能总是希望聚合根在例如持久化之前进行最终的不变扫描.)
However, and this is the subtle and difficult part, that ultimate responsibility does NOT imply that the aggregate is also the primary enforcer as well. Much like what we have in the judicial system - the court is ultimately the final venue where matters of law are determined and where the final rule of law is imposed, the invariant is enforced. But actual enforcement (and compliance) occurs at many levels of the system. In fact in a well ordered society, most activities that impose the rule of law - enforcement of the invariants - should occur well before you get to the court,and you should not have to rely on routinely going to the court at all even. (Although in DDD you may always want to have an aggregate root do a final invariant sweep before e.g. persistence.)
你的建议完全不同,本质上你除了法庭之外的整个社会都被监禁了,而且你似乎甚至在提议其他人甚至不能访问,他们只能向法庭传递信息,希望它采取适当的行动.
What you are suggesting is quite different, essentially your entire society with the exception of the court is incarcerated, and you seem to even be proposing that others cannot even visit, they can only pass a message to the court and hope that it acts appropriately.
如果您按照您建议的路径进行操作,让我们看看您的域会发生什么情况.目标是创建一个丰富且富有表现力的领域模型.就有意义的无处不在的语言而言,你已经将你的工作词汇减少到只有聚合词根.由于不变量,实体应该被聚合根访问,而且因为如果设计是正确的,则实体具有有意义的身份,该身份来自其聚合根上下文中的成员资格.但是您的提议实体在其聚合根之外甚至没有任何类型标识.埃文斯特别指出,这是聚合根目的的一部分——允许对象通过遍历获得对成员的引用.但是您无法获得有用的引用,因为另一个对象甚至不知道您的成员类型存在.或者您可以更改名称空间,但如果您不允许遍历,那也没有什么好处.现在,您的整个域都知道类型,但知道永远无法获取的对象类型.
Let's look at what happens to your domain if you follow the path you have suggested. The objective is to create a rich and expressive domain model. In terms of the meaningful ubiquitous language, you have reduced your working vocabulary to only the aggregate roots. An entity is supposed to be accessed by the aggregate root because of invariants but also because if the design is correct, the entity has meaningful identity that arises from it's membership within the context of it's aggregate root. But your proposal an entity does not even have any type identity outside of it's aggregate root. Evans specifically say that is part of the purpose of an aggregate root - to allow objects to obtain references to members by traversal. But you cannot obtain a useful reference because another object is not even aware that your member types exist. Or you could alter namespaces but that is no better if you do not allow traversal. Now your entire domain knows about types, but types of objects which cannot ever be obtained.
更糟糕的是你的聚合根会发生什么.除了保持聚合的完整性外,聚合根通常应该有自己的存在理由.但现在那个身份已经不清楚了.它被需要为所有各种元素及其属性提供包装方法所掩盖.你得到的是聚合根,不再具有表现力,甚至没有明确的身份,只是大而笨重的神物.
Even worse is what happens to your aggregate root. An aggregate root should usually have it's own reason for existence, in addition to maintaining the aggregate integrity. But now that identity is no longer clear. It is obscured by the need to have a wrapper method for all the various elements and their attributes. What you get are aggregate roots that no longer have an expressiveness or even a clear identity, just large unwieldy God objects.
您的 Order 和 OrderLine 示例就是一个有趣的例子.订单不代表订单线提供订单线要求的某些不变量的强制执行.在这种情况下,它控制动作以强制执行它自己的不变量.这是对聚合根进行离子控制的有效操作.然而,更典型的聚合主要关注对象的创建和/或销毁.
Your example of the Order and OrderLine is an interesting case in point. The Order is not providing enforcement of some invariant required by the OrderLine on behalf of the OrderLine. It is controlling the action in this case to enforce it's own invariant. That is a valid action to put ion teh control of the aggregate root. However, more typically aggregates mostly are concerned with object creation and/or destruction.
当然不需要强加一个模型,其中状态的所有更改必须由聚合根自动应用,而不是直接应用.事实上,这通常是聚合根允许遍历以获取对成员的引用的原因——因此外部对象可以应用状态更改,但在聚合控制正在更改的成员实体的实例生命周期的上下文中.
There is certainly no requirement to imposea model where all changes in state must automatically be applied by the aggregate root and never directly. In fact this is often why the aggregate root allows traversal to obtain references to members - so an external object can apply a change of state, but in a context where the aggregate controls the instance lifecycle of the member entity being changed.
不仅可见性,而且与更大域的实际交互通常是开发丰富且富有表现力的模型的基础.聚合用于控制该访问,但不能完全消除它.
Not just visibility, but also actual interaction with the larger domain is often fundamental to developing a rich and expressive model. The aggregate serves to control that access but not to eliminate it altogether.
我希望这会有所帮助,这是一个很难讨论的概念.
I hope this helps, it is a difficult concept to discuss.
这篇关于.NET 中的 DDD/聚合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!