Key strategic and tactical considerations to take when building a new product with the domain-driven design concepts in mind
I have just finished reading Learning Domain-Driven Design by Vlad Khononov. It’s quite a short book (c. 300 pages) aiming to teach beginners all about domain-driven design.
I wanted to check. I understood the concepts in the book by trying to put them into practice. I hope you will learn something by reading this too! This is how I got on…
The idea of domain-driven design is to use the business requirements you are building for as the basis for your architecture.
The business team that is asking you to build the product are the ‘domain experts’ and the area of business they work in is the ‘domain’.
There is a lot of talk of using a ‘ubiquitous language’, which means rather than using coding words that non-techies might not understand, you name your classes, methods and variables and discuss stuff using words that would be used by the business team to describe the product.
There are lots of different technical concepts that are covered in the book on how to implement domain-driven design and I will try to explain some of them in this article. I am going to do this by working through the thought process behind the building of an imaginary product as an example.
My idea for a product is something that allows you to connect with other people to play sport. I am going to call it”Sports Connect“.
Here are some requirements that I have just made up:
- I want users to be able to message other people who are interested in playing the same sport and who are of a similar standard.
- I want users to be able to rate the experience of playing with the other person after they have finished.
- I want the user to be able to log in and out.
- I want sports coaches to be able to see players ratings and standard level in their sport in their area so that they can scout them for teams and message them in the app.
- I want sports teams to be able to manage fixtures and training schedules and players picked for the team for each fixture and send notifications to the players in the teams.
- I want teams in leagues to be rated based on past performance and for fixture predictions to be available.
Before you start building anything, the first thing to think about is what ‘type’ of different subdomains you have. This is a domain-driven design concept. Here’s a bit of an explanation.
Domains and sub-domains
The domain of my product is a kind of sports-player marketplace and sports team management. For domain-driven design, we are really interested in what the subdomains are.
The subdomains are the different parts of the domain, and they are divided into different types.
1. Core subdomains
The ‘core subdomains’ are the most important type of subdomain and are what makes the business money and stand out from its competitors. In my example above I think the core subdomains are:
- The analysis that finds out other players who are based nearby, interested in the same sport and of a similar standard
- Working out the rating for a sports player
- Sports coaches can pick players for the team and put in their positions
- Rating of sports teams
- Score predictions in sports leagues
- A really user friendly website with a great design
These are relatively complex bits of logic that are likely to change in requirements frequently based on the business requirements. The core subdomains are things that should be built in-house by your best developers. In this case by me in my bedroom!
2. Generic subdomains
‘Generic subdomains’ are the next type of subdomain. They are typically complicated and hard to implement but do not provide any competitive edge. These kind of subdomains are usually bought in from 3rd party since it’s a waste of time building these yourself.
In my example, I think the generic subdomains are:
- The messaging system for users to reach out to each other
- The logging in and out system
3. Supporting subdomains
The final type of subdomain is the ‘supporting subdomain’. This is something that doesn’t provide any competitive advantage but is really easy to implement eg entering and getting data from CRUD interfaces. For this product, my supporting subdomains are:
- Entering and storing the user’s personal data
- Entering and storing the rating that users give to each other
- The team fixtures and training schedules
Ok great! We have our subdomains figured out. We have to think about these separately and how they fit into our solution. Let’s now learn about bounded contexts.
Bounded context is another concept of domain-driven design. The way I understand it is it’s a kind of scope in which the ‘ubiquitous language’ (ie the language of the domain) applies.
If two different areas of the business have the same word for something with a different meaning, then this is a good sign that these are two different bounded contexts.
In my example, I might find that in the context of the functionality used by individual players to find people to play with, the term Player has a totally different meaning to the term Player used by sports coaches when managing their team.
Therefore, it might be sensible to have a bounded context for individual players and another for team management.
In general, the smaller the bounded context the better it is as it is more flexible. There is a bit of a pay off though as making the contexts too small can lead to unnecessary complexity when integrating.
In practice, only one team of developers should work on a single bounded context (but the same team might work on more than one bounded context).
That is all the stuff we will consider from a ‘strategic’ design point of view. Now let’s look at ‘tactical’ design.
Time to get into the nitty gritty of how our code is going to look.
Within each subdomain that we defined above, we should have a think about how we want to design the architecture. Each will have its own architectural design based on its complexity.
The book has a flow diagram that helps to show when you might want to use each type of architecture.
In case you were confused on the terminology, ports and adapters architecture is also called clean architecture.
The idea is to match the architecture of the subdomain with its complexity — for the generic subdomains, which have basic data access functionality, it will probably make sense to design it with a simple ‘Active Record’ type architecture.
This will allow you to perform CRUD operations but without worrying about writing SQL scripts for example.
For core subdomains — the juicy parts of the product — the business logic is probably going to be more complicated. You can see from the flowchart that it is a good idea to use clean architecture or CQRS.
In this article I am going to have a go at building something with a domain model so that I can practise the domain-driven design principles (of course!). A domain model lends itself well to a clean architecture so I will crack on with that since it is something I am familiar with.
You can see from the flowchart that the book I read covers event sourcing and event sourcing domain models as well… but as it’s my first attempt at using these concepts I will keep things simple and use a more straightforward domain model. I will leave practising event sourcing and CQRS for another day!
In one of my core subdomains, I want to match players with each other who play the same sport and are a similar standard.
I will create a Player model. When thinking about the domain model, there are a few important things to think about.
A value type is something that becomes something else if one of its properties are changed.
An example in the Player model might be the Address type. It will have a first line, second line, city and postcode. If any of those things change then the address becomes a new different address.
An entity type is something that you expect to change over time and needs some kind of ID to be able to distinguish it from other objects.
Player model itself will be an entity type as the data for the player might change over time. The player will be identifiable by an ID number.
As a general rule, if you can break an entity type down with value types then you should. Here is a rough first draft of my Player model:
The Player class is an Entity but the properties are all Value types. If any of the properties of any of the value types were to change then it would give a new version of that object.
An aggregate is another concept of domain-driven design. It is arguably the most important one and the hardest to get right.
It is a single or group of entity models that link together and whose behaviors impact each other.
A really important part of an aggregate is that the logic has to validate all incoming changes and ensure that the changes are in line with the business rules for the entity.
Everything external to the entity is only allowed to read the aggregate’s state. That is why there is a private setter.
The state of the aggregate is only changed by excuting methods of the aggregate’s public interface.
Here I have adapted my
Player model to make it into a
All of the properties have a private setter. I have added methods to compare players and if they are a “match” add them to the list, and also methods to update the rating and address of the player.
These “commands” (as they are known in domain-driven design) are called via the aggregate’s public interface.
If you are not familiar with clean architecture, I have built a mini example project in C# previously here — it could be useful to have a quick read of that as I won’t go into too much detail of clean architecture here.
This layer has all the business logic for the solution. In my domain layer, I will have my
Player aggregate that I showed you before.
For the purposes of this article, I’ve created just a couple of use cases that will be used by my application:
- Adding a new player; and
- Comparing a player with another and adding it to the player matches list if it is a match
You can see that the code in my use case classes implements the commands in the Player aggregate.
Player model is totally responsible for updating the state of the
Player entity. A player cannot be modified by logic outside of the entity due to the private setters.
Here is the Add Player use case code:
This code calls the
Create command in the
Player aggregate. It then adds the
Player to the
Here is the code for the compare player use case:
This code uses the
ComparePlayer command in the
Player aggregate to update the
PlayerMatches list with a new player if the conditions for being a match are met.
I haven’t bothered to build a presentation layer for this example but the beauty of clean architecture is that any kind of presentation type can be used without disturbing any of the core business logic in the code we’ve written so far.
Ultimately for this app, we would want to build out a snazzy UI either for the web or mobile.. but we don’t need to make any decisions on that at this stage since the business logic is totally independent of the choice of the presentation layer.
So there you have it! A rather long but nice article about the key strategic and tactical considerations to make when building a new product with the domain-driven design concepts in mind.
I have talked you through how to work out the different types of subdomains and the types of architectures that should be used for each type.
I have also had a go at making a very simple pared-back version of one of the core subdomains of the product using a domain model and clean architecture.
I hope that you found this article useful to give you a bit of background knowledge of domain-driven design and to see an example of how it might look in practice!