What is Domain Driven Design (DDD) — Part 1
As with anything DDD is not a silver bullet. It won’t suit everyone and their styles and you don’t need to use all of it. My style tends to be to use the parts that I like and bring them together to create the nightmare that is my development style.
Along with this article, I will highlight the parts that I use along with the difficulty level. The difficulty is my subjective view and should only be used as a guide as to where to start.
So instead of me rambling on more, which I do all too well, let us jump right in.
The objective of DDD is to improve communication and structure between different fields of expertise.
It does this by building a common language that everyone in your team can understand.
There are many areas of DDD but they can be broken down into two sections: Application and Architecture. In part 1 we will be looking into Application and part 2 will follow covering Architecture.
Application
I call this category application as they are all the coding constructs that can be used in an application. By giving them names we can increase communication when describing our efforts, problems, and areas for improvement within a team.
N.B. DDD is an OOP and therefore talks about OOP concepts all the way through. That doesn’t mean that it can’t be applied in a functional programming world. I will be writing an article about how to use DDD using only FP.
Entity
This is probably a term that most software engineers are familiar with. In its simplest form, it is where your data structure is defined and stored within your running application.
The entity should follow OOP standards of having both the logic and data properties within the same class. That is to say that you don’t just have a class full of getters and setters.
This is by far the simplest and most accessible part of DDD. It is also the most transferable. I highly recommend starting here.
Value Object
A value object can be considered as a container for a specific type of data. It allows for validation of the data, taking it from a primitive type to something more standardised.
Value objects are also ALWAYS immutable. If it’s mutable then it isn’t a value object.
You can find a good example from Microsoft: https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/implement-value-objects
However, you don’t need to use value objects to gain other benefits to DDD. And they can be difficult to always fit into your programming at first. It is a good idea to practise with them but not to stick to them strictly at first.
Aggregate
An aggregate is simply the object that collects the value objects together. It is the point that the value objects can be accessed. An aggregate will contain the business logic that joins the value objects.
An example of this could be a payment. The payment object could be the aggregate and within it there could be a money value object and a payment type. The application would interact with these values via the payment object.
There are also root aggregates. These are the base point of your domain entity. That is to say that no other parent entity can exist in the domain. These are the points of entry to the entity and its logic. Your application may have multiple aggregates, that is up to you. But as a minimum, it needs to have one aggregate and that aggregate must be the root. Any others are optional.
There are more advanced uses of aggregates. But I think this forms a good starting point to learn from. Creating aggregates tends to be very easy and will feel fairly natural to OOP developers. The point behind them may take a bit longer to understand but through using them your understanding will follow.
Domain Event
Domain events (DE) are the internal events that occur between domain aggregates. The events allow a change in multiple aggregates from either a domain request or a domino effect change.
DE are therefore possible as both an application construct or architecture depending on how you build your domain. If you are building microservices then DE tends to be created using something like Pub/Sub. If you are building in one service for the domain they can be developed using the observer pattern: https://refactoring.guru/design-patterns/observer
The aim of DE is to decouple changes between two or more aggregates. The aggregates can remain ignorant of each other and the wider system can handle the coupling. This leads to a more stable system. Changes from one aggregate don’t affect other aggregates as long as the DE remain unchanged.
DE difficulty depends on your wider knowledge and how you are building your systems. If you are already used to the Observer pattern and Pub/Sub then you will find them relatively easy. If not, then I suggest learning about them first.
Service
This is another common concept that you have probably seen before. The word service is thrown around a lot in software engineering. So to explain this one I will say what it is and what it isn’t.
A service is a collection of logic for an entity that shouldn’t exist within the entity itself. An example of this is third-party integrations. An entity should not know about its own integration with a third party and how that data is being synced across different domains. The entity should just care about its own data and how it manages it.
A service IS NOT where all of the logic of an entity is stored. As defined in the entity section above, an entity must contain its own logic. If the entity is just getters and setters and the service contains all of the logic then you haven’t used DDD.
It can be hard to see the difference at first. As a rule of thumb, I start with the entity. If the logic can be added to the entity and doesn’t need access to another aggregate to be processed then it should be added to the entity class. If the logic requires additional aggregate or configuration then it should live in the service.
Repositories
This is another widely used concept. A repository is tasked with data querying and persistence. The entity shouldn’t know it is being persisted or if it’s not. The repository handles that process on its behalf.
So any query, indexing, lookup, CRUD system logic should all go into the Repository. Simply put, the entity object cannot have a save or load method. The entity saving and loading itself is known as Active Record and is considered an anti-pattern due to it violating the Single Responsibility Principle: https://www.mehdi-khalili.com/orm-anti-patterns-part-1-active-record
It can be tricky to get used to this design pattern if you are used to Active Record, but I would highly recommend giving it a go. Once you are used to separating your domain logic and your persistence logic you will find you have greater flexibility while reducing bugs within your code.
Final word
And that’s it. That is the basic overview of all the key terms when it comes to application pieces. All you need now is practise. With anything like this, I start by creating a new project or service and trying to add them. I usually take a service type that I know well such as user data or payments, and then try to add these new learnings.
It can be tricky at first but don’t be put off. DDD can be very hard to fully understand. It will require time, and different attempts until you find your way of doing it right.
It can also be useful to check out larger frameworks. There are plenty out there that already use the DDD approach and can be a good way to understand how they connect.
For now, that is all. In the next article, we shall talk about the Architecture side of DDD.
Enjoy and have fun 😁