Is it good DDD practice to hide all the aggregate methods behind domain services?
I'm working on a DDD-based system and was researching the best way to handle domain services and keep business logic consistent. During my research, I came across this insightful quote from Steve Smith’s DDD NoDuplicates guide: Note in this approach that the entity has no logic in it and any work with the entity needs to happen through the domain service. A problem with this design is that over time it leads to putting all logic in services and having the services directly manipulate the entities, eliminating all encapsulation of logic in the entities. Why does it lead to this? Because clients of the domain model want a consistent API with which to work, and they don't want to have to work with methods on the entity some of the time, and methods through a service some of the time, with no rhyme or reason (from their perspective) why they need to use one or the other. And any method that starts out on the entity may need to move to the service if it ever has a dependency. This made a lot of sense to me and highlighted a common source of confusion. A friend then suggested a design approach that he believes solves this issue: Make all aggregate methods internal, and ensure all operations go through domain services. This way, the domain service becomes the consistent API that clients use, but the logic still resides inside the aggregate root. The aggregate enforces its own invariants, but only the domain service has access to call those methods. My question is: Is this a valid practice in Domain-Driven Design? Does this help avoid the mentioned difficulties, or just create a different kind of complexity? Are there scenarios where this is the preferred approach, or is it overengineering? Would love to hear thoughts from others who’ve faced this tradeoff in real DDD projects.
I'm working on a DDD-based system and was researching the best way to handle domain services and keep business logic consistent.
During my research, I came across this insightful quote from Steve Smith’s DDD NoDuplicates guide:
Note in this approach that the entity has no logic in it and any work with the entity needs to happen through the domain service. A problem with this design is that over time it leads to putting all logic in services and having the services directly manipulate the entities, eliminating all encapsulation of logic in the entities. Why does it lead to this? Because clients of the domain model want a consistent API with which to work, and they don't want to have to work with methods on the entity some of the time, and methods through a service some of the time, with no rhyme or reason (from their perspective) why they need to use one or the other. And any method that starts out on the entity may need to move to the service if it ever has a dependency.
This made a lot of sense to me and highlighted a common source of confusion.
A friend then suggested a design approach that he believes solves this issue:
Make all aggregate methods internal, and ensure all operations go through domain services. This way, the domain service becomes the consistent API that clients use, but the logic still resides inside the aggregate root. The aggregate enforces its own invariants, but only the domain service has access to call those methods.
My question is:
- Is this a valid practice in Domain-Driven Design?
- Does this help avoid the mentioned difficulties, or just create a different kind of complexity?
- Are there scenarios where this is the preferred approach, or is it overengineering?
Would love to hear thoughts from others who’ve faced this tradeoff in real DDD projects.