I have been introducing several domain driven design patterns to the teams that I work with over the last while. In doing so I have been struck by the repetition of a core principle for many of the DDD patterns.
An example – the aggregate pattern
The aggregate pattern focuses on the business domain, attempting to answer the desire for a software developer to work with fully formed domain objects that interact with each other in a meaningful way.
The aggregate pattern defines the aggregate as a combination of objects that interact as a single unit. The aggregate is only interacted with via the root of the aggregate. This ensures that the aggregate root can maintain the integrity of the whole aggregate.
If all interactions with the aggregate are via the root aggregate this means that we can control the implementation of the aggregate behind the interface of the root aggregate. The aggregate should be designed as a cohesive unit and is decoupled from the rest of the system via the aggregate root’s interface. The aggregate root’s interface is the API to the aggregate.
The complexity is in keeping the aggregate small while useful. Tension exists when the aggregate gets bigger. The aggregate interface can become complex due to the constraint that all access must occur through the root aggregate. This tension may push one towards designing smaller interoperable aggregates rather than allowing a large ball of mud to be formed.
Just as a design choice could be to have unidirectional models; it may also be an interesting design choice to build an aggregate with unidirectional models – with the root aggregate being the model that knows about (potentially) all the child models. This may be useful.
My personal preference is to try to keep things unidirectional for as long as it is sensible, but as soon as it isn’t sensible allow connections in both directions. The aggregate root is controlling the access to the objects in the aggregate. Allowing objects in the aggregate to know about each other bi-directionally increases the complexity of the aggregate as a whole but, assuming a reasonably small aggregate, the principle of small contained messes is still supported behind the interface exposed by the aggregate root.
Circling back to the core principle for many of the DDD patterns – the factory pattern, the repository pattern and the aggregate pattern all define mechanisms for creating a well-defined interface and hiding the implementation details behind the interface.
The factory pattern provides a creational interface to build an object. We get well formed objects from it and don’t need to know how they were formed.
The repository pattern provides an interface that abstracts away object storage / query. We ask it a question and get objects back. We tell it to persist something, and it happens. We do not need to worry how.
The root aggregate in the aggregate pattern provides an interface that abstracts away the implementation of the aggregate.
These patterns make code simpler and reduce complexity by clearly defining what should go behind the interface and what should not. These patterns allow the caller to not worry about the implementation details behind the interface. All of these patterns support the idea of smaller messes. If the implementation behind the interface is a little messy, it doesn’t matter, as long as it can be refactored safely later and the caller is not influenced at all.
Decouple the interface that the outside world uses from the implementation underneath. And know why the code is placed behind the interface. Design a cohesive unit behind the interface. This is also how TDD encourages code to be written, assuming you can design the interface well.
When discussing domain driven design versus other design ideas and using test driven design to build software, there are often questions around which pattern to use or how they should interact. These questions are often attempting to get black and white answers to a complex contextual problem that probably has many shades of grey in it.
What has struck me most about introducing DDD after introducing TDD and emergent / evolutionary design is that the core principle is to contain the messes behind the interfaces. Have good interfaces and decouple them. And ensure the implementation behind the interface is cohesive and can change freely as needed. That is the core of software design and most patterns. How do I change later? How do I keep in control of the code so that when change comes (and it will come! Especially in unexpected ways) it is not accidentally impactful on the rest of the system.
Focus on embracing change
Worry less about the patterns, than what the patterns are trying to teach you. Use the patterns, understand them, they are a language that can be used effectively among developers. But the patterns are not the end game, the changeable system that they encourage is.