How to properly define an aggregate in DDD?
Asked Answered
S

4

8

What would be a rule of thumb when designing an aggregate in DDD?

According to Martin Fowler, aggregate is a cluster of domain objects that can be treated as a single unit. An aggregate will have one of its component objects be the aggregate root.

https://martinfowler.com/bliki/DDD_Aggregate.html

After designing aproximatelly 20 DDD projects I am still confused about the rule of thumb when choosing domain objects that would create an aggregate.

Martin Fowler uses order and line-items analogy and I don't think it is a good example, because order+line-items are really tightly bounded objects. Not much to think about in that example.

Lets try with car analogy where CarContent is a subdomain of a car dealer domain.

CarContent would consist of at least one or more aggregate/s.

For example we have this AggregateRoot (i am keeping it as simple as possible)

class CarStructureAggregate
{
     public int Id {get; private set;}
     public ModelType ModelType {get; private set;}
     public int Year {get; private set;}
     public List<EquipmentType> {get; private set;}
}

Alternative could be this (example B)

class CarStructureAggregate
{
     public int Id {get; private set;}
     public ModelType ModelType {get; private set;}
     public int Year {get; private set;}
}

class CarEquipmentAggregate
{
    public int Id {get; private set;}
    public List<EquipmentType> {get; private set;}
}

Car can be created without equipment but it cannot be activated/published without the equipment (ie. this can be populated over two different transactions)

Equipment can be referenced trough CarStructureAggregate in example A or using CarEquipmentAggregate in example B.

EquipmentType could be an enum, or could be a complex class with many more classes, properties.

What is a rule of thumb when choosing between examples A and B? Now imagine that car could have more information such as

  • photos
  • description
  • maybe more data about the engine

and CarStructureAggregate could be an extremely large class

So what is it that makes us split Aggregate into new Aggregates? Size? Atomicity of a transaction (although that would not be an issue since usually aggregates of a same sub domain are usually located on the same server)

Shama answered 9/7, 2018 at 10:58 Comment(0)
C
8

Be careful about having too strong OO mindset. The blue book and Martin Fowler post are a little bit old and the vision it provides is too narrow.

An aggregate does not need to be a class. It does not need to be persisted. Theese are implementation details. Even, sometimes, the aggregate do things that does not implies a change, just implies a "OK this action may be done".

iTollu post give you a good start: What matters is transactional boundary. The job of an aggregate is just one. Ensure invariants and domain rules in an action that, in most of the cases (remember that not always), change data that must be persisted. The transactional boundary means that once the aggregate says that something may, and has, be done; nothing in the world should contradict it because, if contradiction occurs, your aggregate is badly designed and the rule that contradict the aggregate should be part of aggregate.

So, to design aggregates, I usualy start very simple and keep evolving. Think in a static function that recives all the VO's, entities and command data (almost DTO all of them except the unique ID of the entities) needed to check domain rules for the action and returns a domain event saying that something has be done. The data of the event must contain all data that your system needs to persist the changes, if needed, and to act in consequence when the event reach to other aggregates (in the same or different bounded context).

Now start to refactoring and OO designing. Supress primitive obsession antipattern. Add constraints to avoid incorrect states of entities and VO's. That piece of code to check or calculate someting related to a entity better goes into the entity. Put your events in a diet. Put static functions that need almost the same VO's and entities to check domain rules together creating a class as aggregate root. Use repositories to create the aggregates in an always valid state. And a long etc. You know; just good OOP design, going towards no DTO's, "tell, don't ask" premise, responsibility segregation and so on.

When you finish all that work you will find your aggregates, VO's and entities perfectly designed from a domain (bounded context related) and technical view.

Cinerator answered 12/7, 2018 at 7:45 Comment(1)
Thanks for your input. I am pretty much using this technique. f you noticed i kind of splitted CarStructure aggregate into CarStructure and CarEquipment. Since equipment is not tightly bounded to CarStructure i decided to place it in its own aggregate. My question is really about defining transaction boundaries between aggregates when deciding should one aggregate be as big. Lets be honest having one aggregate will always do the trick, but having multiple aggregates is easier to maintain large domains, easier to load the repository, but when changes in domain happen its redesigning timeShama
F
3

Something to keep in mind when designing aggregates is that the same entity can be an aggregate in one use case and a normal entity in another. So you can have a CarStructureAggregate that owns a list of EquipmentTypes, but you can also have an EquipmentTypeAggregate that owns other things and has its own business rules.

Remember, though, that aggregates can update their own properties but not update the properties of owned objects. For example if your CarStructureAggregate owns the list of EquipmentType, you cannot change properties of one of the equipment types in the context of updating the CarStructureAggregate. You must query the EquipmentType in its aggregate role to make changes to it. CarStructureAggregate can only add EquipmentTypes to its internal list or remove them.

Another rule of thumb is only populate aggregates one level deep unless there is an overriding reason to go deeper. In your example you would instantiate the CarStructureAggregate and fill the list of EquipmentTypes, but you would not populate any lists that each EquipmentType might own.

Figurehead answered 10/7, 2018 at 16:10 Comment(4)
Why populate aggregates only one level deep?Shama
It's not a requirement, just a personal rule of thumb. Deeper than one level breaks the Law of Demeter and can make updates complicated. It is also rarely necessary. If you need to access data 3+ layers deep, something is wrong with your approach.Figurehead
you can always update two aggregates from a single unit of work, making it an actual atomic transactionShama
This example is two level deep, so it is complicated to explain populating aggregates 2+ level deep, but i dont agree with you there. Aggregates are aggregates and there is an infinity depth there. Of course such BC would be considered poorly designed, but there is no depth level there..Shama
R
1

I believe, what matters here is transactional boundary.

On one hand, you can't establish it more narrow than it is sufficient for preserving an aggregate's consistency.

On the other hand, you don't want to make it so large to lock your users from concurrent modifications.

In your example, if users should be able to modify CarStructure and CarEquipment concurrently - then I'd stick to implementation B. If not - it would be simpler to use A.

Roseola answered 10/7, 2018 at 4:9 Comment(0)
B
1

in a very simple sentence, I can say:

basically, a business use case that aims to change and consists of one or more relevant entities, value objects, and invariants based on the business in domain-driven design is aggregate. being a model command is important because if you only need to read, you don’t need an aggregate.

Broughton answered 12/6, 2021 at 18:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.