This is part of a series, start here!
From Eric Evans 2003 book, this approach to software design aims to couple the design of a system to the business domain it operates in.
This is to say, a system design should reflect the business logic for which it was created.
He broadly separates Tactical from Strategic design. In this post, weâll go over some concepts from the former.
Layers
Broadly speaking, four main layers are considered in this architecture:
- User Interface: More or less equivalent to Boundaries in EBI.
- Application: Partly in charge of the role of the Interactor, specifically related to use case orchestration.
- Domain: In line with the Entities from EBI
- Infrastructure: Simply in charge of persistence, messaging, and such.
If âEBI architectureâ doesnât ring a bell, you might want to start here.
Entities / Value Objects
Concrete representations of very basic Domain concepts.
They differ on mutability and identity.
Entity
An employee might change their role within the company, that doesnât make it a different employee.
Apart from their name, youâll likely identify them by some sort of ID, so a change in their attributes doesnât change their identity.
So we would say an employee is an Entity in our system.
Value Object
A phone number on the other hand does not change. Or rather, if it does, we are talking about a different phone number.
It wouldnât really make sense for a phone number to have an ID: The object is identifiable by its attributes.
Thus, things like phone numbers, email addresses, etc. are Value Objects (VO).
Rich Domain
While in general, Entities have IDs and VO donât, not all cases are so clear-cut as these.
The context and Domain will dictate which to use in a given situation.
Itâs also important to remember that neither should be anemic: they should encapsulate as much logic as reasonably possible (usually all logic regarding their individual behavior).
Aggregates
Conceptual elements made up of multiple Entities and/or VO, which only have meaning or make sense together.
The concrete representation of this element is the Aggregate Root. This serves as a gateway to the rest of the elements enclosed within the Aggregate (Entities and VO).
The Aggregate Root should be the only way of accessing those elements, especially when modifying their state.
Itâs not hard to imagine such a structure getting out of hand. Prevent this from happening by:
- Keep them as small as possible.
- Allow for easy promotion from Entity to Aggregate Root, in case one of them grows significantly.
- Aggregates should relate to one another by ID or directly through Services or Events to maintain scalability.
All logic pertaining multiple Aggregates should be delegated to a Domain Service.
Services
Stateless objects that perform Domain-specific operations that escape the boundary of the Aggregate. Based on their scope, there are two kinds:
- Domain services: Executes logic that does not fit nicely within an Aggregate. Orchestrates interactions between multiple Aggregate Roots.
- Application services: Orchestrates a Use Case, using Repositories, Domain Services, Aggregate Roots but always within its own Module.
To be clear, the scope should grow the further away we go from VO:
If communication is needed between Modules, the Application Services should talk to one another Without accessing other Domain objects from different Modules.
Domain Events
A decoupled way for different parts of the system to indirectly interact with one another.
These usually materialize into a Pub/Sub structure:
Publisher
There are at least two ways of approaching who should be in charge of event publishing:
- Aggregate Roots publish changes in their state directly.
- Aggregate Roots register these events for the Application Service to publish.
Subscriber
Event subscribers look a bit like controllers, just limited in scope within our Domain.
They both ingest the primitive types of their respective input and use them to run the relevant use case.
Where controllers receive requests, subscribers receive events, but in essence you can think of their role as equivalent in practice, just with different scopes and implementation.
So for example, a generic Subscriber interface signature might look something like:
Where the implementation looks like:
CustomEvent
might, for example, implement or extend from DomainEvent
.
Repositories
They abstract concerns about data storage and other infrastructure.
Ideally, there will be one Repository per Aggregate Root, and it should only be called by the relevant Application Service/s as part of a use case orchestration process.
They usually take the form of a domain leaning interface with concrete implementations based in the specific infrastructure at hand.
This is more or less borrowed from Ports and Adapters.
More to come
By now this is probably sounding like a big ball of jargon with not much of an architecture behind it, no real intention or plan.
Weâll go over the Strategic side if DDD in a later post.