.NET 中的 DDD / 聚合

发布于 2024-11-19 00:02:50 字数 830 浏览 5 评论 0原文

我一直在阅读 Evans 关于 DDD 的书,并且正在思考应该如何在 .NET 中实现聚合。目前,我只能想出一种方法;将聚合隔离在单独的类库中。然而,这似乎有点矫枉过正(我更愿意将所有域对象保留在一个库中),我想知道是否有不同的方法?

1 lib/aggregate 的推理如下:聚合根需要知道对其负责的“子对象”的所有访问,并且聚合根还可以返回子对象作为其成员的结果。因此,这些子对象的成员(聚合根所需的)不能公开。因此,您唯一的选择是将它们设置为内部(因为它们仍然需要由聚合根调用)。然而,通过将所有聚合放入一个项目中,仍然可以从已获取子对象的其他域对象访问这些成员。这是不可取的,因为它允许人们绕过聚合根。通过分离不同库中的所有聚合,这个问题就得到了解决。

一些附加信息:

我已经查看了 DDD java 示例代码,它们打包了每个聚合(包括所有子聚合)对象的类)在不同的包中。只能从聚合根调用的成员没有访问修饰符(例如:Delivery.updateOnRouting)。在java中,没有访问修饰符的成员是package-private(可用仅来自同一包)。所以这将是正确的行为。

然而,.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?

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.

Some additional info:

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.

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

神妖 2024-11-26 00:02:50

聚合是 DDD 中最难的概念之一。你大部分都是对的。我建议用聚合的“成员资格”来表达这个概念比引入术语“子对象”更直接。

是的,一个对象不能是多个聚合的成员。哪一个将是最终的执行者?一个聚合根可以通过删除成员并孤立另一个聚合中的其他成员来轻松使另一个聚合根失效。您是对的,在一个对象似乎需要多个聚合中的成员资格的情况下,该对象必须是一个独立的实体,即它成为新聚合的根。 (它可能有也可能没有额外的成员,但如果没有,那么它当然会成为它自己的聚合。)

是的,确实存在一个聚合来强制不变量。就持久性而言,它也是单个工作单元或单个事务。聚合根最终对其整个成员资格中的所有不变量负责,这一定是因为,例如,不变量的失败可能会导致持久性失败,而聚合负责将聚合维护为持久性工作的可行的单个单元。

然而,这是微妙和困难的部分,最终责任并不意味着总体也是主要执行者。就像我们在司法系统中所拥有的那样——法院最终是确定法律问题、实施最终法治、执行不变量的最终场所。但实际执行(和合规)发生在系统的许多级别。事实上,在一个秩序良好的社会中,大多数实施法治的活动——执行不变量——应该在你到达法庭之前就发生,你甚至根本不必依赖例行公事。 (尽管在 DDD 中,您可能总是希望在持久性之前有一个聚合根进行最终的不变扫描。)

您所建议的是完全不同的,本质上,除了法庭之外,您的整个社会都被监禁了,而且您似乎甚至被监禁了。建议其他人甚至不能探访,只能向法院传递信息,希望法院采取适当行动。

让我们看看如果您按照建议的路径操作,您的域会发生什么情况。目标是创建一个丰富且富有表现力的领域模型。就有意义的普遍语言而言,您已将工作词汇量减少到仅聚合词根。由于不变量,实体应该由聚合根访问,而且还因为如果设计正确,则该实体具有有意义的标识,该标识源自其聚合根上下文中的成员身份。但是您的建议实体在其聚合根之外甚至没有任何类型标识。埃文斯特别指出,这是聚合根的目的的一部分——允许对象通过遍历来获取对成员的引用。但是您无法获得有用的引用,因为另一个对象甚至不知道您的成员类型存在。或者您可以更改名称空间,但如果您不允许遍历,那也好不到哪儿去。现在,您的整个域都知道类型,但永远无法获取对象的类型。

更糟糕的是你的聚合根会发生什么。除了维护聚合完整性之外,聚合根通常应该有其自身存在的理由。但现在这个身份已经不明确了。它因需要为所有各种元素及其属性提供包装方法而变得模糊。你得到的是聚合根,不再具有表现力,甚至没有明确的身份,只是巨大而笨拙的上帝物体。

您的 Order 和 OrderLine 示例就是一个有趣的例子。该订单不代表订单行强制执行订单行所需的某些不变量。在这种情况下,它控制动作以强制执行它自己的不变量。这是控制聚合根的有效操作。然而,更典型的聚合主要涉及对象的创建和/或销毁。

当然,不需要强加一个模型,其中所有状态更改都必须由聚合根自动应用,而不是直接应用。事实上,这通常就是聚合根允许遍历获取对成员的引用的原因 - 因此外部对象可以应用状态更改,但在聚合控制要更改的成员实体的实例生命周期的上下文中。

不仅可见性,而且与更大领域的实际交互通常也是开发丰富且富有表现力的模型的基础。聚合用于控制该访问,但不能完全消除它。

我希望这会有所帮助,这是一个很难讨论的概念。

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.

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.

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.

那小子欠揍 2024-11-26 00:02:50

我只能想出一种方法;
将聚集体隔离在单独的
类库。然而,这似乎
就像有点矫枉过正

更像是很多矫枉过正。做这样的事情的开销将是残酷的,您不希望这种方法在任何重要的应用程序中创建数十个项目。

I can only come up with one way;
isolating the aggregates in separate
class libraries. This, however, seems
like a bit of overkill

More like a lot of overkill. The overhead of doing something like this would be brutal, you do not want dozens upon dozens of projects, which this method create in any non-trivial application.

泪意 2024-11-26 00:02:50
Therefore, members (needed by the aggregate root) of these sub-objects can't be made public.

我认为这个结论过于僵化,不切实际,而且也不是埃文斯所提倡的。是的,他确实说聚合根负责创建和访问该根内的其他对象,但是

首先,聚合根往往会重叠。如果两个不同的根需要一个小部件,无论出于何种原因,它都是必需的。因此,您确实不能只使其(在本例中为小部件)可供一个根使用,而另一个根则不可用。

其次,我认为(我的解释和我没有出版这本书!)关于对象访问的聚合根的想法更多的是一种惯例而不是教条。教条是在该根的上下文中满足客户端请求,以简化域。更重要的是为聚合根开发一个接口,然后让客户通过该接口来满足他们的需求。如果您可以限制对(任何)客户端不需要的对象的访问(使用任何聚合根),请务必这样做。

HTH,
贝里尔

Therefore, members (needed by the aggregate root) of these sub-objects can't be made public.

I would suggest that this conclusion is too rigid to be practical, and isn't something Evans is advocating. Yes, he does say that the Agg Root is responsible for creation and access of other objects within that Root, BUT

Firstly, aggregate roots tend to overlap. If a widget is required in two different roots, for whatever reason, it's required. So you really can't just make it (the widget, in this case) available to one Root and not the other.

Secondly, I think (my interpretation and I don't have the book out!) the idea of an Agg Root with respects to object access is more one of convention than dogma. The dogma is to satisfy client requests within the context of that root so as to simplify the domain. Its more a matter of developing an interface for the Aggregate Root, and then have clients go through that interface to satisfy their needs. If you can restrict access to objects that (any) clients don't need (using any Aggregate Root), by all means do so.

HTH,
Berryl

安静被遗忘 2024-11-26 00:02:50

除了您问题的(有趣的)理论方面之外,我认为您问题的实际答案是使用名称空间来分隔聚合。在 .NET 中,您将类放在命名空间中。命名空间结构独立于项目结构,您可以将多个命名空间放入单个项目中,该项目在编译时会生成单个程序集。

您可以使用它将所有域类放入一个程序集中,并且它们之间仍然保持分离。

Apart from the (interesting) theoretical aspect of your question, I think a practical answer to your question is to use namespaces to separate aggregates. In .NET, you put your classes within namespaces. The namespace structure is independent of the project structure and you can put multiple namespaces in a single project that on compilation results in a single assembly.

You can use this to put all your domain classes in a single assembly and still have separation between them.

秋凉 2024-11-26 00:02:50

我认为您不想为其他程序集隐藏“子对象”的类定义。您可能是正确的,您不应该能够从其他聚合根引用它们,但是如果您想基于这些子对象创建表示模型怎么办?您需要从其他程序集访问它们。

因此,从 DDD 的角度来看,您可能是正确的,但我认为您不希望您的代码将这一点反映到您建议的极端程度。这将成为非常不切实际的代码。

I don't think you want to make class definitions of "sub-objects" hidden for other assemblies. You might be correct that you should not be able to reference them from other aggregate roots, but what if you want to create a presentation model based on these sub-objects? You'll need access to them from other assemblies.

So from a DDD perspective you might be correct, but I don't think you would want your code to reflect this to the extreme you suggest. This would become highly impractical code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文