Two more concepts which are important patterns of tactical modeling in domain driven design (DDD) are Aggregates and Modules. Both aim to organize the other building blocks of the domain model. Aggregates group multiple Entities and Value Objects together into one cohesive unit. This unit is also the transactional boundary within which we can guarantee certain invariants.
Modules are used to organize the elements of a domain model on an even higher level than aggregates. They provide a different, higher-level view on the model and code where details are hidden. Without those details in the way, cognitive load is reduced and we can reason about connections between groups of domain elements.
The Aggregate Pattern
Aggregates group together entities and value objects into a cohesive unit. The main entity of the aggregate is called an aggregate root. Clients are not allowed to access the other elements of the aggregate directly but only through the root entity. In other words, the root is the API of the aggregate.
Each aggregate denotes a transactional boundary. Within this boundary, we can guarantee certain invariants with validation logic. After every command or a series of commands that we execute on a single aggregate, we validate the state and commit the changes.
Because aggregates form the transactional boundary only objects that need to be absolutely consistent with each other should be in the same aggregate. Generally speaking, they should be kept as small as possible. Maybe we could start with one aggregate per entity. Later when we talk with the domain experts about consistency, a requirement could be discovered to combine multiple entities into one aggregate that can guarantee consistency constraints.
Let’s look at the example of a customer relationship management (CRM) application. One requirement of the CRM is to store customer contacts. Each contact can be reached by multiple communication channels. We already decided that contacts are modeled as an entity because they need an identity. We model communication channels as value objects that don’t need a lifecycle. These two objects naturally form an aggregate where the contact entity is the root.
If we would like to create a new communication channel the classical way would be to create one and associate it with the contact. We would need to know the concrete implementation of the communication channel and how to associate it with the contact.
With the contact aggregate, we are not allowed to manipulate communication channels directly. We ask the contacts aggregate to add a new channel with primitives as parameters. We use the API of the aggregate which is free on how to create the communication channel and link it to the contact.
The Module Pattern
The domain module is a container for the other domain objects. It groups them into highly cohesive units. Objects within the module should be closely related to the domain concept it describes. Between modules, we aim for low coupling to reduce the mental load when working with its elements. By naming the modules according to the ubiquitous language, we can tell the story of the system on a higher level.
The following diagram shows a module of the CRM application. There are many other possible ways to form modules of a CRM through depending on the context and background of the domain.
In the original DDD book [Eva04] Eric Evans described modules as a way to group domain objects on the level of packages or namespaces. Nowadays many languages have a notion of module above the package or namespace structure. Therefore we have more flexibility when designing modules. We can use packages or namespaces to group objects very fine granularly and use the module language construct to model the domain modules.
On an even higher level modules are organized in bounded contexts which is the first strategic modeling pattern that I would like to introduce in the next post. When the bounded context is relatively small and contains only a few aggregates we maybe not need to split them into modules. However, in bigger contexts, they help to further divide the domain into units that we can work with.
Wrap Up / Final Thoughts
The aggregate pattern addresses some common pain points that are widely known and which I observed too in some applications. One is the overuse of getters and setters to change data that is multiple relations away. Aggregates prevent this by only allowing access to the aggregate root and restrict access to the other objects. Another point is the transactional boundary they provide. It prevents transactions over many objects which could lead to poor performance or even deadlocks.
Modules are a concept that I like very much since I first learned about them through Kirk Knoernschield’s book “Java Application Architecture” [Kno12]. They are very helpful In organizing code on a relatively high level. I’m looking forward to modularizing a domain model.
[Eva04] Eric Evans: Domain-Driven Design – Tackling Complexity in the Heart of Software (homepage)
[Kno12] K. Knoernschield: Java Application Architecture – Modularity Patterns with Examples Using OSGi (homepage)