Domain-Driven Design: part 2
One year ago, we already made a quick presentation of
Domain-Driven Design, in the context of our mORMot
After one year of real-world application of those patterns, it is now time to give more light to DDD.
Let's continue with part 2, which will define Domain-Driven Design high-level model principles.
What do we call Domain here?
The domain represents a sphere of knowledge, influence or activity.
As we already stated above, the domain has to be clearly identified, and your software is expected to solve a set of problems related to this domain.
DDD is some special case of Model-Driven Design.Its purpose is to
create a model of a given domain.
The code itself will express the model: as a consequence, any code refactoring means changing the model, and vice-versa.
Even the brightest programmer would never be able to convert a real-life
domain into its software code.
What we can do is to create an abstraction system that describes selected aspects of a domain.
Modeling is about filtering the reality, for a given use
"All models are wrong, some are useful" G. Box, statistician.
Several Models to rule them all
As first consequence, several models may coexist for a given
reality, depending of the knowledge level involved - what we call a Bounded
Don't be afraid if the same reality is defined several times in your domain code: you should use only one
class in a given context, but you
may have another
class defined in another context, with diverse
attributes or methods.
Just open Google maps for instance, and think how the same reality may be modeled depending on the zoom level, or you current view options. See also the M1, M2, M3 models as defined in Meta-Object Facility. When you define several models, you just need to clearly state the current model you are using.
Even models could be abstracted. This is what DDD does: the code itself is some kind of meta-model, conforming a given conceptual model to the grammar of a given programming language.
The state of the model
Most models express the reality in two dimensions:
- Static: to abstract a given state of the reality;
- Dynamic: to abstract how reality evolves (i.e. its behavior).
In both dimensions, we can clearly understand the purpose of abstraction.
Since it is impossible to model all the details of reality (e.g. describe a physical reality down to atomic / sub-atomic level), the static modeling will forget the non significant details, and focus on the essentials, for a given knowledge level, which is specific to a given context.
Similarly, most changes are continuous in the world, but dynamic modeling will create static snapshots of the reality (called state transitions), to embrace the deterministic nature of computers.
State always brings complexity to the model. As a consequence, our code
should be as stateless as possible.
- Try to always separate value and time in state;
- Reduce statefulness to the only necessary;
- Implement your logic as state machines instead of blocking code or sessions;
- Persistence should handle one-way transactions.
In DDD, Value Objects and Entity Objects are the mean to
express a given system state. Immutable Value Objects define a static
value. Entity refers to a given state of given identity (or
For instance, the same identity (named "John Doe") may be, at a given state, single and minor, then, at another state, married and adult. The model will help to express the given states, and the state transitions between them (e.g. John's marriage).
In DDD, the Factory / Repository / Unit Of Work patterns will introduce transactional support in a stateless approach.
And in situations where a reality does change its state very often, with complex impacts on other components, DDD would model these state changes as Events. It could lead into introducing some Event-Driven Design even or Event Sourcing within the global model.
In order to refine your model, you have two main tools at hand to express the model modularity:
- Partitioning: the more your elements have a separated concern, the better;
- Grouping: to express constraints, elements may be grouped - but usually, you should not put more than 6 or 8 elements in the same diagram, or your model may need to be refined.
In DDD, a lot of small objects have to be defined, in order to properly partition the logic. When we start with Object Oriented Programming, we are tempted to create huge classes with a lot of methods and parameters. This is a symptom of a weak model. We should always favor composition of small simple objects, just like the Unix tools philosophy or the Single Responsibility Principle.
Some DDD experts also do not favor inheritance. In fact, inheriting may be
also a symptom of some coupled context. Having two diverse realities sharing
properties may be a bad design smell: if two or more classes inherit from one
parent class, the state and behavior of the parent class may limit any future
evolution of any of its children. In practice, trying to follow the
Open/Close SOLID principle at
class level may induce
unexpected complexity, therefore reducing code maintainability.
In DDD, the Aggregate Root is how you group your objects, in order to let constraints (e.g. business rules) to be modeled. Aggregates are the main entry point to the domain, since they should contain, by design, the whole execution context of a given process. Their extent may vary during development, e.g. when a business rule evolves - remember that the same reality can appear several times in the same domain, but once per Bounded Context. In other words, Aggregates could be seen as the smallest and biggest extent needed to express a given model context.
Let's continue with part 3, which will define Domain-Driven Design main patterns and principles.