DDD / aggregates in .NET.

I read Evans' book on DDD and think about how to implement aggregates in .NET. Currently, I can only come up with one way; isolating aggregates in separate class libraries. This, however, seems a bit overkill (I would prefer to keep all the domain objects in one library), and I wonder if there is another way?

The rationale for 1 lib / aggregate is this: the aggregate root must know all the access to the “sub-objects” for which it is responsible, as well as the aggregate root can return sub-objects as the results of its members, therefore members (necessary for the aggregate root) these sub-objects cannot be made public. Thus, your only option is to make them internal (since they should still be called by the aggregated root). However, by putting all the aggregates in one project, you can still access these members from other domain objects that have received the sub-object. This is undesirable because it allows you to bypass the cumulative root. Separating all aggregates in different libraries, this problem is solved.

Additional Information:

I checked the DDD java sample code and packs each aggregate (including all sub-object classes) into a different package. Members that can only be called from a common root do not have an access modifier (for example: Delivery.updateOnRouting ). In java, members without an access modifier are package-private (available from only one package). So this will be the correct behavior.

. The .NET sample code , however, puts all the domain objects in a single class library, and then makes the corresponding members public. To me this seems wrong.

+6
source share
5 answers

Aggregates are one of the most complex concepts in DDD. You have everything in order. I propose to express the concept in terms of "membership" in the aggregate, more simply than to introduce the term "subobjects".

Yes, an object cannot be a member of more than one aggregate. Which one will be the last performer? One common root can easily annul another, removing members and orphaning other members in a different set. You are right, in scenarios where an object must require membership in several aggregates, this object must be an independent object, that is, it becomes the root of the new aggregate. (He may or may not have additional members, but if he does not, then of course he will become his own totality.)

And yes, it is absolutely true that there is a set for the forced use of invariants. It is also a single unit of work or a single transaction in terms of perseverance. The aggregate root is ultimately responsible for all invariants throughout its membership, this should be because, for example, a failure of the invariant can lead to a failure of perseverance, and the aggregate is responsible for maintaining the aggregate as a viable unit of continuous operation.

However, and this is the subtle and difficult part, the ultimate responsibility does NOT mean that the totality is also fundamental . Similar to what we have in the judicial system, the court is ultimately the final place where the issues of law are determined and where the final rule of law is imposed, the invariant is applied. But actual compliance (and compliance) occurs at many levels of the system. In fact, in a well-ordered society, most actions that impose legitimacy — respecting invariants — must take place long before you go to court, and you should not rely on regular court appeals. (Although in DDD you can always want the aggregated root to perform the final invariant sweep before, for example, persistence.)

What you offer is completely different, in fact, your society as a whole, with the exception of the court, is imprisoned, and you seem to even suggest that others can’t even visit, they can only send a message to the court and hope that it is acting properly.

See what happens to your domain if you follow your proposed path. The goal is to create a rich and expressive domain model. In terms of a meaningful ubiquitous language, you have reduced your working vocabulary only to aggregate roots. It is assumed that the entity must gain access to the aggregated root due to invariants, and also because, if the design is correct, the object has a significant identity, which arises due to its membership in the context of its aggregated root. But your entity proposal does not even have a type identifier outside its aggregated root. Evans definitely says that this is part of the goal of the aggregate root - to allow objects to get references to members by traversal. But you cannot get a useful link because the other object does not even know that your member types exist. Or you can change namespaces, but this is not better if you do not allow traversal. Now your whole domain knows about types, but about types of objects that can never be obtained.

Even worse is what happens to your common root. The aggregate root should usually have its own reason for existence, in addition to maintaining the integrity of the aggregate. But now this identity is no longer clear. This is obscured by the need to have a wrapper method for all the various elements and their attributes. What you get is the aggregate roots that no longer have expressiveness or even a clear identity, just the big bulky objects of God.

An example of your order and OrderLine is an interesting example. The order does not enforce any invariant required by OrderLine on behalf of OrderLine. He controls the action in this case to ensure his own invariant. This is a valid action to control the cumulative root. However, more typical aggregates are mainly associated with the creation and / or destruction of an object.

Of course, there is no requirement for the imposea model, where all state changes should be automatically applied using the cumulative root and never directly. In fact, this is often explained by why the aggregate root allows crawls to get references to members - therefore, an external object can apply a state change, but in the context where the population controls the life cycle of an instance of a member object that changes.

Not only visibility, but also the actual interaction with a larger domain are often fundamental to developing a rich and expressive model. The unit serves to control this access, but not to completely eliminate it.

I hope this helps, it is a difficult concept to discuss.

+6
source

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

More like an overflow lot . The overhead of doing something like this would be cruel; you don’t want the dozens of dozens of projects this method creates in any non-trivial application.

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

I would suggest that this conclusion is too hard to be practical, and that is not what Evans protects. Yes, he says that Agg Root is responsible for creating and accessing other objects inside this root, BUT

First, aggregate roots tend to overlap. If a widget is required in two different roots, for some reason it is needed. That way, you really can't just make this (the widget in this case) accessible to one root, not the other.

Secondly, I think (my interpretation, and I do not have a book!) The idea of ​​Agg-Korn from the point of view of access to the object is more conventional than dogma. Dogma must satisfy customer requests in the context of this root in order to simplify the domain. Moreover, it is a matter of developing an interface for Aggregate Root, and then clients go through this interface to meet their needs. If you can restrict access to objects that (any) clients do not need (using any consolidated root), be sure to do this.

NTN
Berryl

+2
source

Besides the (interesting) theoretical aspect of your question, I think the practical answer to your question is to use namespaces to separate aggregates. In .NET, you put your classes in namespaces. The structure of the namespace is independent of the structure of the project, and you can put several namespaces in one project, which, when compiled, leads to the same assembly.

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

+2
source

I don’t think you want sub-object class definitions to be hidden for other assemblies. You may be right that you cannot refer to them from other common roots, but what if you want to create a view model based on these sub-objects? You will need access to them from other assemblies.

So, in terms of DDD, you can be true, but I don’t think you want your code to reflect this to the extreme that you offer. This will become very impractical code.

0
source

Source: https://habr.com/ru/post/892072/


All Articles