Domain Driven Design
Domain Driven Design (DDD) is a set of principles and modeling techniques that can help with the design of complex software systems. This is especially relevant to software that needs to express complex business logic as code.
DDD is independent of programming paradigms and even system architecture. The architecture and tech stack should be determined by DDD and not the other way around.
Why invest in DDD?
You can either have good design or bad design but not no design. Effort not invested in good design ends up being paid back as tech debt years later when the whole application needs to be rebuilt because it has turned into a big ball of mud. A system designed by applying the principles of DDD will be better maintainable over the long term and is also more flexible to changing business needs.
This is the high level design that breaks down the complex domain of a business into manageable parts.
To effectively express the business problem that the software is addressing, the same language should be shared between business people and technical people. This is known as ubiquitous language.
Using a ubiquitous language has several benefits:
- The business logic is directly represented in code. This avoids communication gaps between business and IT.
- Talking in terms of specifics of the business instead of using general terms avoids wrong assumptions during development.
- Product owners or business analysts can write executable text-based tests in the BDD style which can serve as a proof that the software behaves as the business expects.
Developers should put effort into asking more questions to the domain experts about business processes even if they are quite silly and the developers have experience in developing solutions for other companies operating in the same domain.
A bounded context encloses a semantic context within a boundary. The ubiquitous language inside one bounded context differs from that in another bounded context. For example, the same words might mean different things in different bounded contexts. They are like European countries or states in India - each having its own language.
Bounded contexts are in the problem space when doing domain modeling but they can translate into independent software artifacts in the solution space. Each bounded context should be developed by one team. One team might develop software in multiple bounded contexts but multiple teams should never work on one bounded context.
Domain Driven Architecture Models
This is a non-exhaustive list of architecture models that can work with DDD.
- Event Driven Architectures incl. Event Sourcing
- Ports and Adapters
- Reactive systems using the Actor Model
A bounded context might practically manifest as one microservice or one node in an actor system etc. For monolithic applications, a package (like in Java) can act as a bounded context.
A subdomain exists in one bounded context. A subdomain represents a single logically separable domain model. Each subdomain might have its own domain experts who specialize in it.
The subdomain that represents the most important competency of your business is known as a Core Domain. This is usually developed in-house.
A Supporting Subdomain isn't the primary reason why a business exists but it helps support the business processes. This can be out-sourced.
A Generic Subdomain solves a general (usually administrative problems like HR, accounting etc.) for the business. From a strategic perspective, Buy is usually better than Build for this kind of subdomain.
Different subdomains have different ubiquitous languages but they still have to communicate with each other. Context Mapping is an exercise in connecting together subdomains enclosed in bounded contexts. This establishes relationships between teams and how they communicate with each other.
There are several kinds of context mappings possible:
- Shared Kernel
- Customer-Supplier / Producer-Consumer
- Open-Host Service
- Anti-Corruption Layer
- Published Language
- Separate Ways
- Big Ball of Mud
We should try to avoid being in Conformist, Shared Kernel, Separate Ways and Big Ball of Mud relationships with other teams.
These relationships can be practically implemented using various methods like RPC with SOAP, ReST or messaging. Whichever method is used, it is important to translate the data back into the ubiquitous language of our own domain immediately after receiving it to avoid breaking context boundaries.
Tactical Design deals with the design of components inside the bounded contexts.
Entities and Value Objects
An Entity models one individual concept in the system. Entities are not simply the sum of their attributes but have unique identifiers. This is in contrast with Value Objects which are fully defined by their attributes. For example, an employee in a payroll system might be an entity but an address is a value object.
Note: Value Objects have nothing to do with Objects from Object Oriented Programming.
An Aggregate is a hierarchical collection of entities and value objects with an Aggregate Root at the top. Aggregates form a transactional consistency boundary. Aggregates should be composed carefully to avoid having to do distributed transactions.
In actor systems, you might send a message to the aggregate root which sends further messages to its children to complete the transaction. However, this transactions shouldn't require involvement from another aggregate. Here we use the term "transaction" to emphasize atomicity and consistency of the operation, but it needn't involve writing to a database. It is a good practice to send messages between aggregate roots only for transactional operations.
A domain event is a notification about a changed state in a subdomain in the system. It is always stated in the past tense.
Note: Events should not be confused with Commands though they might be implemented similarly (e.g. by using messaging).
The order of occurence of events is important to reconstruct the state of the system. This is especially important in systems using event-sourcing since the event store is the source of truth and system state is often reconstructed by applying events in order. Events usually include a timestamp for this purpose. Make sure that the clocks on all the nodes in your distributed system are synchronized!
A consumer of a domain event might translate the event into its ubiquitous language and then perform some operations which might involve sending domain events to other subdomains.
Though principles of DDD are supposed to be practiced on an everyday basis while working in a project team, the practices are most valuable during the inception of a new project. Getting the design right at this stage avoids a lot of trouble over the long term.
This is an exercise involving both the domain experts and developers where business processes or user activities are mapped over time to domain events. This might start with a high-level overview of user activities and then zooming in to see the details involved in each activity and translating them to domain events.
A long wall and sticky notes are ideal for this kind of activity. In a remote setting, virtual whiteboard tools that can simulate this can be used.
- Domain Driven Design Distilled
- Domain Driven Design Quickly
- Life beyond distributed transactions
- Reactive Messaging Patterns using the Actor Model