Domain-Driven Design, The Basics
The Domain-Driven Design (DDD) is a software architecture approach that focuses on the core domain of the application. It’s a way to tackle complexity by connecting the implementation to an evolving model of the core business concepts.
One of the most important steps in this process is to gather information from the domain experts. The idea of this post is to show you the fundamental concepts behind this powerful approach.
What is a Domain?
There are several definitions of domain in the literature. The most popular one is the following:
“an area of knowledge or activity; especially one that somebody is responsible for”. Oxford Dictionary,
In this context, the domain is the subject area to which the user applies a program. It is the business you are in.
A quick example of a domain can be:
- For Banking Services: A Bank can have several services like Loans, Credit Cards, Accounts, Employees, etc. The entire banking operation is the domain.
- For E-Commerce: A company can have services like Sales, Inventory, Shipment, and Payments. The entire process of selling goods online is the domain.
Basically, the domain is the field or area in which the company operates.
What is a Subdomain?
A domain is rarely a single, monolithic block. It’s better to break it down into multiple parts, or subdomains. This helps manage complexity.
Types of subdomains:
- Core Domain: This is the heart of the business. It’s what makes the company special and provides a competitive advantage. For a veterinary business, the core domain might be appointments, surgery scheduling, and patient treatment plans. You should invest your best talent here.
- Supporting Subdomain: These are parts of the business that are necessary but not a competitive differentiator. They are typically custom-built because no off-the-shelf solution exists. For our veterinary business, managing the specific stock of specialized animal medicine could be a supporting subdomain.
- Generic Subdomain: These are “solved problems” common across many businesses. Think of identity management (logging in users) or sending emails. The best strategy here is to buy a solution or use an open-source library, not build it yourself.
It’s always necessary to work with a domain expert to understand the domain and its subdomains.
So, in the end, What is Domain-Driven Design?
Domain-Driven Design is a set of principles and patterns that help you build software that handles complex business logic effectively.
In his book Domain-Driven Design, Eric Evans describes DDD as a strategy to distill complex problems into manageable models. The idea is to develop software where the code is based on an evolving business model. This model is the representation of:
- Structure
- Activities
- Processes
- Actors
- Interactions
DDD helps us to:
- Center the business processes in the design and development process. This is achieved by modeling the domain and every subdomain, rather than defining the technological stack at the beginning of the project.
- Break the domain into smaller pieces, called subdomains, to build the best model for the business.
- Be agile-friendly. The model can start small (as an MVP that everyone understands) and evolve with each iteration as the team’s understanding of the domain deepens.
DDD contains two approaches. The strategic approach focuses on the big picture, defining the landscape of the domain with patterns like Bounded Contexts and the Ubiquitous Language. The tactical approach is about the design of the objects within a single model, using components like the Aggregate pattern or the Repository pattern.
In summary, DDD helps us develop applications for complex business domains and represent this process in a flexible model. When using DDD, you get software that is adaptable to the needs of the company, because the business logic lives in a well-organized, isolated place.
From Theory to Practice: Tactical DDD Patterns in Code
We’ve talked about the “tactical approach” and patterns like Aggregates. Now, let’s see what this looks like in actual code. We’ll use a C# example from an application, where users can track movies they’ve watched and want to watch.
The goal here is to create a rich domain model, not an anemic one. An anemic model has objects that are just bags of data with public getters and setters, and all the business logic lives elsewhere (in “service” classes). A rich model encapsulates both data and behavior, ensuring objects are always in a valid state.
The Building Blocks: Entities and Aggregates
- Entity: An object defined not by its attributes, but by a thread of continuity and identity. A
Useris an entity; even if their email changes, they are still the same user because theirUserIdis the same. - Aggregate: A cluster of associated objects that we treat as a single unit for the purpose of data changes. An aggregate has a root and a boundary.
- Aggregate Root: A specific entity within the aggregate that is the single entry point for all modifications to any object within that aggregate. External objects can only hold references to the aggregate root. This protects the business rules (invariants) of the aggregate.
In our example, the User is the Aggregate Root. A user owns their list of movies (UserMovie) and their Comments. To add a comment, you must go through the User object. You cannot bypass it and create a comment directly.
Case Study: “My Movies Checklist” Domain Model
1. Base Entity Class
To centralize the concept of identity, we can create a base class.
1 | // Movies.Domain/Common/Entity.cs |
2. The User Aggregate Root
Notice the private setters and the private list of _userMovies. Changes to the user’s movie list can only happen through methods like AddToWatchlist and MarkAsSeen, which contain the business logic.
1 | // Movies.Domain/Entities/User.cs |
3. The UserMovie and Comment Child Entities
These entities have internal constructors and factory methods. This is crucial: it means they can only be created by other classes within the same project (our domain project), effectively enforcing that the User aggregate root controls their lifecycle.
1 | // Movies.Domain/Entities/UserMovie.cs |
By modeling our domain this way, we have gained clarity, robustness, and maintainability. The business logic is encapsulated where it belongs, and the domain protects its own integrity.
Downsides of DDD
One of the biggest problems of starting with DDD is the learning curve. DDD includes many principles, patterns, and processes that can be difficult to grasp at first. DDD is not the best approach for small or simple projects because it can overcomplicate the development process. For a simple CRUD application, it is likely overkill.
Mapping the Domain into the model: Event Storming
How do you discover the events, commands, and aggregates we just modeled? A powerful technique is Event Storming.
This is a collaborative workshop where technical experts and domain experts work together. Most of the time, technical experts use tools like UML which domain experts might find difficult to understand. The idea is to create a session where both sides share the same language and build a model together.
The Event Storming meeting is a session that uses sticky notes to map out a business process. The idea is to create a detailed list of:
- Domain Events: Something important that happened in the past (e.g.,
MovieMarkedAsSeen). These are orange sticky notes. - Commands: A user’s intention to do something (e.g.,
MarkMovieAsSeen). These are blue. - Actor: The person or system that executes a command. These are yellow.
- Aggregate: The entity that processes a command and produces an event (e.g.,
User). These are larger yellow notes. - Read Model: The data an actor needs to make a decision (e.g., a list of movies to choose from). These are green.
- Policies: Rules that trigger a command in response to an event (e.g., “When a
UserRegisteredevent occurs, then execute theSendWelcomeEmailcommand”). These are purple.
All these elements help to visualize and understand all the pieces involved in a subdomain process, providing a blueprint for your domain model.