My english bad, so i will give two examples. Let's drop some even important things from DDD in this examples, the main thing is the essence of the matter.
How to do it right from the point of view of DDD?
We have two aggregate roots, the Seller
and the Advert
. The Seller
can edit the Advert
in these examples:
1.
If the models should reflect the real business logiŃ. Then it is the Seller
who must change Adverts
. I.e client layer invoke methods changeAdvertName()
and changeAdvertCost()
of aggregate Seller
. By the way this gives such an advantage as access check. As we can see Seller
can modify only own Adverts
. This is the first option as possible.
//Client layer call seller.changeAdvertName(name)
//AR Seller
class Seller{
adverts
changeAdvertName(advertId, name){
adverts[advertId].changeName(name)
}
changeAdvertCost(advertId, cost){
adverts[advertId].changeCost(cost)
}
}
//AR Advert
class Advert{
name
cost
changeName(name){
this.name = name
}
changeCost(cost){
this.cost = cost
}
}
2.
Another variant, client layer can invoke directly methods changeName
and changeCost
from aggregate Advert
. I saw this implementation many times.
//Client layer call advert.changeName(name)
//AR Advert
class Advert{
name
cost
changeName(name){
this.name = name
}
changeCost(cost){
this.cost = cost
}
}
What do you think about these options? Are they both valid for DDD
implementation? Which one is more correct and logical from the point of view of DDD?
Thank you!
An important idea in domain driven design is the notion of a consistency boundary - an aggregate is a boundary around state that can be modified in isolation - without looking at any state outside of the aggregate.
The main benefit is that the client code doesn't need to worry about managing the consistency rules; that responsibility lives in the aggregate.
An additional benefit is that modifications to one aggregate don't need to block on modifications to another.
Nesting aggregate roots, by having one aggregate hold a reference to another, makes something of a mess of that idea; a thread that is trying to modify the Advert may interfere with a different thread that is trying to modify the Seller of the Advert.
There's fundamentally nothing wrong with having multiple entities within a single aggregate. For example, you could reasonably combine the Seller entity and the Advert entity into a single Seller aggregate, and enforce your consistency guarantees by making all changes to an Advert go through the Seller. It's important, however, to recognize that in this design the Advert is not, itself, an aggregate root.
There's also nothing wrong with having the Advert be its own aggregate root, handling its own consistency rules, while the Seller lives in a different aggregate.
In this simple example, where the seller is just deferring changes to the advert, it makes sense to keep them separate from one another, so that different Adverts of the same seller can be modified concurrently.
If there was some critical domain invariant that spanned multiple adverts, then you might need to pull them all into a single collection, which might live within the seller aggregate.
This really isn't specific to DDD; it's more a reflection of "object oriented" programming (as understood by Java, etc). Behavior - which is to say, changes of state - occur by sending a message to the entity that manages that state.
The object oriented idiom is not actually a good match with English grammar. We normally write "Seller modifies advert" -- a subject-verb-object form. But in the grammar of object oriented programming, objects change their own state in response to imperative tense messages (commands).
List.addItem(...)
- we aren't modifying the list, we are sending the list a command that says: "modify your own state".Similarly, the seller isn't modifying the state of the advert; she is sending a message describing the way the advert should change, and it's up to the advert to do that.
And that's deliberate: it means that the Seller can collaborate with the Advert without needed to know anything about the Advert's implementation, which means that we can replace that implementation any time we want without breaking Seller.