Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 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.

like image 496
Freek Avatar asked Jul 05 '11 20:07

Freek


1 Answers

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.

like image 150
Sisyphus Avatar answered Nov 08 '22 07:11

Sisyphus