What is an Aggregate Root?
Asked Answered
W

3

8

No, it is not a duplication question. I have red many sources on the subject, but still I feel like I don't fully understand it.

This is the information I have so far (from multiple sources, be it articles, videos, etc...) about what is an Aggregate and Aggregate Root:

  • Aggregate is a collection of multiple Value Objects\Entity references and rules.
  • An Aggregate is always a command model (meant to change business state).
  • An Aggregate represents a single unit of (database - because essentialy the changes will be persisted) work, meaning it has to be consistent.
  • The Aggregate Root is the interface to the external world.
  • An Aggregate Root must have a globally unique identifier within the system
  • DDD suggests to have a Repository per Aggregate Root
  • A simple object from an aggregate can't be changed without its AR(Aggregate Root) knowing it

So with all that in mind, lets get to the part where I get confused:

in this site it says

The Aggregate Root is the interface to the external world. All interaction with an Aggregate is via the Aggregate Root. As such, an Aggregate Root MUST have a globally unique identifier within the system. Other Entites that are present in the Aggregate but are not Aggregate Roots require only a locally unique identifier, that is, an Id that is unique within the Aggregate.

But then, in this example I can see that an Aggregate Root is implemented by a static class called Transfer that acts as an Aggregate and a static function inside called TransferedRegistered that acts as an AR.

So the questions are:

  1. How can it be that the function is an AR, if there must be a globaly unique identifier to it, and there isn't, reason being that its a function. what does have a globaly unique identifier is the Domain Event that this function produces.
  2. Following question - How does an Aggregate Root looks like in code? is it the event? is it the entity that is returned? is it the function of the Aggregate class itself?
  3. In the case that the Domain Event that the function returns is the AR (As stated that it has to have that globaly unique identifier), then how can we interact with this Aggregate? the first article clearly stated that all interaction with an Aggregate is by the AR, if the AR is an event, then we can do nothing but react on it.
  4. Is it right to say that the aggregate has two main jobs:
    • Apply the needed changes based on the input it received and rules it knows
    • Return the needed data to be persisted from AR and/or need to be raised in a Domain Event from the AR

Please correct me on any of the bullet points in the beginning if some/all of them are wrong is some way or another and feel free to add more of them if I have missed any!

Thanks for clarifying things out!

Weinstein answered 9/10, 2019 at 22:46 Comment(0)
R
2

Keep in mind that Mike Mogosanu is using a event sourcing approach but in any case (without ES) his approach is very good to avoid unwanted artifacts in mainstream OOP languages.

  1. How can it be that the function is an AR, if there must be a globaly unique identifier to it, and there isn't, reason being that its a function. what does have a globaly unique identifier is the Domain Event that this function produces.

TransferNumber acts as natural unique ID; there is also a GUID to avoid the need a full Value Object in some cases.

There is no unique ID state in the computer memory because it is an argument but think about it; why you want a globaly unique ID? It is just to locate the root element and its (non unique ID) childrens for persistence purposes (find, modify or delete it).

Order A has 2 order lines (1 and 2) while Order B has 4 order lines (1,2,3,4); the unique identifier of order lines is a composition of its ID and the Order ID: A1, B3, etc. It is just like relational schemas in relational databases.

So you need that ID just for persistence and the element that goes to persistence is a domain event expressing the changes; all the changes needed to keep consistency, so if you persist the domain event using the global unique ID to find in persistence what you have to modify the system will be in a consistent state.

You could do

var newTransfer = New Transfer(TransferNumber); //newTransfer is now an AG with a global unique ID
var changes = t.RegisterTransfer(Debit debit, Credit credit)
persistence.applyChanges(changes);

but what is the point of instantiate a object to create state in the computer memory if you are not going to do more than one thing with this object? It is pointless and most of OOP detractors use this kind of bad OOP design to criticize OOP and lean to functional programming.

  1. Following question - How does an Aggregate Root looks like in code? is it the event? is it the entity that is returned? is it the function of the Aggregate class itself?

It is the function itself. You can read in the post:

AR is a role , and the function is the implementation.

An Aggregate represents a single unit of work, meaning it has to be consistent. You can see how the function honors this. It is a single unit of work that keeps the system in a consistent state.

  1. In the case that the Domain Event that the function returns is the AR (As stated that it has to have that globaly unique identifier), then how can we interact with this Aggregate? the first article clearly stated that all interaction with an Aggregate is by the AR, if the AR is an event, then we can do nothing but react on it.

Answered above because the domain event is not the AR.

4 Is it right to say that the aggregate has two main jobs: Apply the needed changes based on the input it received and rules it knows Return the needed data to be persisted from AR and/or need to be raised in a Domain Event from the AR

Yes; again, you can see how the static function honors this.

You could try to contat Mike Mogosanu. I am sure he could explain his approach better than me.

Roundhouse answered 10/10, 2019 at 11:13 Comment(7)
The thing is that there are rules regarding the AR that conflict with the way Mike Mogosanu implemented the AR. Would you say that his implementation still follows most of the rules thus still being AR? Because if you do check all of the bullet points in the post for AR, the AR itself has to have that unique ID, and not only the event it returns/publishes.Weinstein
Keep in mind that, when the blue book was written, Event Sourcing did not exist and functional programming was used just in academic circles. Mike is using a modern approach tha helps to fit DDD in ES and FP. If you still think that the only way to "have a unique ID" is creating a class with state well... good luck trying to implement DDD in F#.Roundhouse
OK so that was what I wanted to find out, where this conflict originated from. Now I understand and the reason you gave explains the different types of approaches I see. Thanks!Weinstein
If we do not need an AR how would we retrieve this new state that was saved? An AR usually contains some invariants; else we could forego DDD and persist data directly (a la CRUD). For instance, if we had a rule that prevents an asset account from going into credit we may very well register the transfer but when it comes to effecting the transfer it should be prevented if that invariant is not satisfied and we'd need to hydrate the state in order to do so. Perhaps I'm missing something in the implementation since I did not go through the entire post :)Judicative
@EbenRoux, we have AR but in this example it is a static function. Regarding hydrating the AR; you can use persistence queries (i.e. account state: loked) in the application service to get the data from persistence and pass it as argument to the AR funcion or use a domain service to prevent the debbit . But rememer that Mike NEVER says than you have to implement everything and allways with static functions. You should use all the tools you have. In this example Mike use a static function because he does not need more than that. He created the post because his vision could be interesting.Roundhouse
@EbenRoux, also do note that in Mike implementation the AR responsibility is just making the changes to keep the system consistent. Segregating the responsibility of "can not do it" into the domain service. I think this is a good idea for maintainability as you can change the "can not do it" rules without touching the "give me the new consistent system state" for this operation. Why is a good idea? Because the transition of this implementation to FP is almost straightforward wich means is not a design too tied to a specific programming paradigm. And pure functions are always more maintainable.Roundhouse
@jlvaquero: I'm going to have to wrap my head around the thinking when I have some more time. Do you perhaps have a link to some example implementation that demonstrates the technique? It would be interesting to dig into it a bit as there may be something new that I can try.Judicative
P
8

I feel like I don't fully understand it.

That's not your fault. The literature sucks.

As best I can tell, the core ideas of implementing solutions using domain driven design came out of the world of Java circa 2003. So the patterns described by Evans in chapters 5 and 6 of the blue book were understood to be object oriented (in the Java sense) domain modeling done right.

Chapter 6, which discusses the aggregate pattern, is specifically about life cycle management; how do you create new entities in the domain model, how does the application find the right entity to interact with, and so on.

And so we have Factories, that allow you to create instances of domain entities, and Repositories, that provide an abstraction for retrieving a reference to a domain entity.

But there's a third riddle, which is this: what happens when you have some rule in your domain that requires synchronization between two entities in the domain? If you allow applications to talk to the entities in an uncoordinated fashion, then you may end up with inconsistencies in the data.

So the aggregate pattern is an answer to that; we organize the coordinated entities into graphs. With respect to change (and storage), the graph of entities becomes a single unit that the application is allowed to interact with.

The notion of the aggregate root is that the interface between the application and the graph should be one of the members of the graph. So the application shares information with the root entity, and then the root entity shares that information with the other members of the aggregate.

The aggregate root, being the entry point into the aggregate, plays the role of a coarse grained lock, ensuring that all of the changes to the aggregate members happen together.

It's not entirely wrong to think of this as a form of encapsulation -- to the application, the aggregate looks like a single entity (the root), with the rest of the complexity of the aggregate being hidden from view.

Now, over the past 15 years, there's been some semantic drift; people trying to adapt the pattern in ways that it better fits their problems, or better fits their preferred designs. So you have to exercise some care in designing how to translate the labels that they are using.

Panjabi answered 10/10, 2019 at 1:29 Comment(2)
So basically, ones definition of Aggregate Root is not necessarily other definition to it? How would you recommend on going forward with learning those concepts? Any sources you know of that are unbias?Weinstein
Unbias and modern is what I'm hopefully looking to find, reason being that from what I have seen some of these concepts have evolved to a more modern versions of themselvesWeinstein
J
3

In simple terms an aggregate root (AR) is an entity that has a life-cycle of its own. To me this is the most important point. One AR cannot contain another AR but can reference it by Id or some value object (VO) containing at least the Id of the referenced AR. I tend to prefer to have an AR contain only other VOs instead of entities (YMMV). To this end the AR is responsible for consistency and variants w.r.t. the AR. Each VO can have its own invariants such as an EMailAddress requiring a valid e-mail format. Even if one were to call contained classes entities I will call that semantics since one could get the same thing done with a VO. A repository is responsible for AR persistence.

The example implementation you linked to is not something I would do or recommend. I followed some of the comments and I too, as one commenter alluded to, would rather use a domain service to perform something like a Transfer between two accounts. The registration of the transfer is not something that may necessarily be permitted and, as such, the domain service would be required to ensure the validity of the transfer. In fact, the registration of a transfer request would probably be a Journal in an accounting sense as that is my experience. Once the journal is approved it may attempt the actual transfer.

At some point in my DDD journey I thought that there has to be something wrong since it shouldn't be so difficult to understand aggregates. There are many opinions and interpretations w.r.t. to DDD and aggregates which is why it can get confusing. The other aspect is, in IMHO, that there is a fair amount of design involved that requires some creativity and which is based on an understanding of the domain itself. Creativity cannot be taught and design falls into the realm of tacit knowledge. The popular example of tacit knowledge is learning to ride a bike. Now, we can read all we want about how to ride a bike and it may or may not help much. Once we are on the bike and we teach ourselves to balance then we can make progress. Then there are people who end up doing absolutely crazy things on a bike and even if I read how to I don't think that I'll try :)

Keep practicing and modelling until it starts to make sense or until you feel comfortable with the model. If I recall correctly Eric Evans mentions in the Blue Book that it may take a couple of designs to get the model closer to what we need.

Judicative answered 10/10, 2019 at 6:1 Comment(1)
Actually, now checking Mike Mogosanu's post on Domain Services and Application Serviceshe pointed out exactly what you said, that the Transfer is the aggregate but there are also constraints outside of it, which are DS and the AS is the one that orchestractes them all. After I clear (hopefully) all of the confusion I have about the topics of DDD I will certainly start a project to practice those patterns Thanks for commenting!Weinstein
R
2

Keep in mind that Mike Mogosanu is using a event sourcing approach but in any case (without ES) his approach is very good to avoid unwanted artifacts in mainstream OOP languages.

  1. How can it be that the function is an AR, if there must be a globaly unique identifier to it, and there isn't, reason being that its a function. what does have a globaly unique identifier is the Domain Event that this function produces.

TransferNumber acts as natural unique ID; there is also a GUID to avoid the need a full Value Object in some cases.

There is no unique ID state in the computer memory because it is an argument but think about it; why you want a globaly unique ID? It is just to locate the root element and its (non unique ID) childrens for persistence purposes (find, modify or delete it).

Order A has 2 order lines (1 and 2) while Order B has 4 order lines (1,2,3,4); the unique identifier of order lines is a composition of its ID and the Order ID: A1, B3, etc. It is just like relational schemas in relational databases.

So you need that ID just for persistence and the element that goes to persistence is a domain event expressing the changes; all the changes needed to keep consistency, so if you persist the domain event using the global unique ID to find in persistence what you have to modify the system will be in a consistent state.

You could do

var newTransfer = New Transfer(TransferNumber); //newTransfer is now an AG with a global unique ID
var changes = t.RegisterTransfer(Debit debit, Credit credit)
persistence.applyChanges(changes);

but what is the point of instantiate a object to create state in the computer memory if you are not going to do more than one thing with this object? It is pointless and most of OOP detractors use this kind of bad OOP design to criticize OOP and lean to functional programming.

  1. Following question - How does an Aggregate Root looks like in code? is it the event? is it the entity that is returned? is it the function of the Aggregate class itself?

It is the function itself. You can read in the post:

AR is a role , and the function is the implementation.

An Aggregate represents a single unit of work, meaning it has to be consistent. You can see how the function honors this. It is a single unit of work that keeps the system in a consistent state.

  1. In the case that the Domain Event that the function returns is the AR (As stated that it has to have that globaly unique identifier), then how can we interact with this Aggregate? the first article clearly stated that all interaction with an Aggregate is by the AR, if the AR is an event, then we can do nothing but react on it.

Answered above because the domain event is not the AR.

4 Is it right to say that the aggregate has two main jobs: Apply the needed changes based on the input it received and rules it knows Return the needed data to be persisted from AR and/or need to be raised in a Domain Event from the AR

Yes; again, you can see how the static function honors this.

You could try to contat Mike Mogosanu. I am sure he could explain his approach better than me.

Roundhouse answered 10/10, 2019 at 11:13 Comment(7)
The thing is that there are rules regarding the AR that conflict with the way Mike Mogosanu implemented the AR. Would you say that his implementation still follows most of the rules thus still being AR? Because if you do check all of the bullet points in the post for AR, the AR itself has to have that unique ID, and not only the event it returns/publishes.Weinstein
Keep in mind that, when the blue book was written, Event Sourcing did not exist and functional programming was used just in academic circles. Mike is using a modern approach tha helps to fit DDD in ES and FP. If you still think that the only way to "have a unique ID" is creating a class with state well... good luck trying to implement DDD in F#.Roundhouse
OK so that was what I wanted to find out, where this conflict originated from. Now I understand and the reason you gave explains the different types of approaches I see. Thanks!Weinstein
If we do not need an AR how would we retrieve this new state that was saved? An AR usually contains some invariants; else we could forego DDD and persist data directly (a la CRUD). For instance, if we had a rule that prevents an asset account from going into credit we may very well register the transfer but when it comes to effecting the transfer it should be prevented if that invariant is not satisfied and we'd need to hydrate the state in order to do so. Perhaps I'm missing something in the implementation since I did not go through the entire post :)Judicative
@EbenRoux, we have AR but in this example it is a static function. Regarding hydrating the AR; you can use persistence queries (i.e. account state: loked) in the application service to get the data from persistence and pass it as argument to the AR funcion or use a domain service to prevent the debbit . But rememer that Mike NEVER says than you have to implement everything and allways with static functions. You should use all the tools you have. In this example Mike use a static function because he does not need more than that. He created the post because his vision could be interesting.Roundhouse
@EbenRoux, also do note that in Mike implementation the AR responsibility is just making the changes to keep the system consistent. Segregating the responsibility of "can not do it" into the domain service. I think this is a good idea for maintainability as you can change the "can not do it" rules without touching the "give me the new consistent system state" for this operation. Why is a good idea? Because the transition of this implementation to FP is almost straightforward wich means is not a design too tied to a specific programming paradigm. And pure functions are always more maintainable.Roundhouse
@jlvaquero: I'm going to have to wrap my head around the thinking when I have some more time. Do you perhaps have a link to some example implementation that demonstrates the technique? It would be interesting to dig into it a bit as there may be something new that I can try.Judicative

© 2022 - 2024 — McMap. All rights reserved.