开发者

DDD / Aggregates in .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 packa开发者_运维知识库ge). 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.


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.


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.


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


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜