DDD Tactical Design

It is time to dive into the concept of Domain-Driven Design (DDD). We are going to develop it from the point of view of software architecture.

In this post, we will try to synthesise the concepts and explain the essence of each of them, as well as how they relate to each other. With this personal approach, what I intend to do is to pave the way for you if you are starting with DDD. Surely, there is a wealth of literature on the subject. But by understanding the ultimate purpose of this development approach, you will be able to decide whether it is worth applying it or not.

What is Domain-Driven-Design (DDD)?

First of all, we need to understand where this concept comes from and why it was created. The first time we hear about DDD is in Eric Evans’ book, Domain-Driven Design: Tackling Complexity in the Heart of Software. In it, a revolutionary development approach for the time (2003) is presented. We were coming from a context in which software development, in the 1990s, was understood as a very rigid and structured process. Then, agile methodologies began to emerge that allowed us to generate value in a faster and more flexible way. This is the germ of the birth of DDD, which seeks precisely to take care of and maximise this value.

When we develop software, we usually seek to represent an environment or solve a business problem through programming languages and methodologies. For example, when working on a website or backend, often the first thing we think about is the language or framework we will use.

DDD goes beyond all that: actually, those issues are nothing more than details. They are implementation details, technical aspects that, although important, are not a priority from a business point of view. Surely, these aspects are of great concern to the developer or architect, who, on many occasions, feel that the business actors (product owners or stakeholders) do not understand them due to their low technical level. This has happened to all of us and I must admit that it is part of the developer’s ego.

What the customer really wants is to protect the business logic involved in the software solution we are developing. This is what they are really interested in, both from a stability and an evolutionary perspective. And this is where DDD comes in. This methodology focuses on elevating business logic processes and entities to first class citizens in development.

Main concepts of DDD

When we start researching DDD, one of the first schemes we come across is the famous graph of relationships between the different existing concepts.

DDD schema

The truth is that I have always found this diagram to be more confusing than enlightening. Especially when we are starting to work with this approach. This is because the main readers are developers and their expectation is to find an architecture they can follow. Perhaps this is the most confusing part at first. DDD offers a methodology, but it would not exactly be described as an architecture, although it does include certain architectural aspects. In fact, it integrates very well with hexagonal architectures which we will not discuss here, but I hope to develop in a future post.

We can divide the methodology and its concepts into two blocks. The upper or ‘tactical block’ and the lower or ‘strategic block’.

This first post will be focused on developers, so we will talk about the technical part.

Tactical block

All the concepts are related in one way or another to Model-Driven Design. We could say that this technical block provides us with a ‘recipe’ for modelling 100% of the problem context, using the different concepts that appear here.

Let us proceed to review each of these concepts. To do so, I will use the example of an e-commerce shop and we will see how each concept fits into the modelling.

Entities

Entities are objects that have a unique identity that differentiates them from other instances, even if they share the same attributes. They represent objects with a lifecycle and characteristics that can change over time.

In our example, we can model Customer and Order as entities. Customer would be its own entity. Even if there were two customers with the same first and even last names, each person represents a different customer. Therefore, it will have a unique ‘customer_id’ that differentiates it.

Value Objects

Unlike entities, Value Objects have no identity of their own, they are defined only by their attributes. They are immutable and represent concepts with intrinsic value that can be shared between entities.

In our shop example, the delivery addresses of orders would be a Value Object. This is because an address is unique in itself and we consider that two objects with the same street and number refer to the same place.

This concept is well understood with other known Value Objects. For example, in the case of colours, which are defined by three RGB values. When comparing two colours, we check if their three values match. If they do, we are talking about the same colour, so there is no need for an ‘id_colour’.

Aggregates

In the case of aggregates, we are talking about a set of entities and value objects that behave as a cohesive unit within the domain. An aggregate has an ‘aggregate root’ that controls access and modifications to the elements within it.

In our example, an aggregate could be the Order. Within it we could include entities such as Product, Payment Details or Customer, among others. Any operation on the order, such as adding a new product, would be performed through the root of the aggregate, which in this case is the Order entity.

Services

This component encapsulates business logic that cannot be attributed to an entity. That is, business logic can be found in both entities and services. Entities encapsulate certain business logic related to a ‘state’, while services encapsulate specific domain-related behaviours.

For example, we could have a service that is able to query whether it is feasible to send an order to an address.

Repositories

Repositories are responsible for the persistence of aggregates. They act as an abstraction that encapsulates the database access logic, allowing the retrieval and storage of domain entities and aggregates. In addition, they contain the logic necessary for serialisation and deserialisation of such entities and aggregates.

Repositories do not always have to access a database, although they do in most cases. A repository is in charge of retrieving some kind of data, but we do not need to care how it does it. So it could make a request to an HTTP microservice or retrieve a file from disk.

We should have a repository to be able to retrieve all our persisted data. So we would have one repository for Orders, another for Customers, and so on.

Factories

Design patterns are tools that help us create complex domain objects, especially aggregates, in a consistent and logical way.

For example, we could have a factory that helps us create an Order by passing it a Customer and a set of Products. These objects can then be modified. However, if there is some checking logic during the creation process, such as checking if the customer has outstanding payments, we could encapsulate that logic in this space.

Domain Event

A Domain Event is a notification about a change of state in the domain that is relevant to other parts of our system. It reflects moments or actions that are significant to the business and that, of course, have already occurred. These events are defined as top-level objects. They usually include contextual information about the change such as when it occurred, the context in which it occurred, and other details.

Going back to our example, the creation and payment of an Order could generate a ‘payment done’ event. This could be picked up by another part of our system to send an email to the customer thanking them for the purchase.

It’s important to note this concept, as it is one of the most interesting and undoubtedly key to the whole methodology. Domain events allow us to effectively decouple the triggers of the events from the possible current or future stakeholders in those events.

Layered architecture

Finally, tactical design is related to this concept. It is not a tangible instrument to model the problem in our software development.

This concept refers to a set of organisational rules that we must implement in our code. It is not a detailed architecture, but a category of architectures that we can call ‘layered architectures’. These architectures tell us to structure our code in layers, promoting independence of responsibility and change between them. As mentioned above, one such layered architecture is the hexagonal architecture, which fits very well with this methodology.

We can divide our code into three layers.

Domain layer

This is the core layer and contains the pure business logic, concepts and domain-specific rules. 

Here we find entities, values, aggregates and domain events.

This layer is independent of the infrastructure and any persistence implementation. The idea is that the domain is a faithful representation of the business logic, without being tied to the technology.

Application layer

This layer is responsible for coordinating the operations of the domain layer in response to specific actions or commands that may come to it from outside.

It contains the application services, which orchestrate the interaction between domain objects and ensure the consistency of the business logic. It does not contain business logic per se, but flow and control logic.

Infrastructure layer

Provides the capacity for low-level aspects, performing actions such as persistence, messaging and communication with other systems.

Here we can find the repositories that interact with the database to persist entities, those that interact with other microservices, etc.

These repositories contain the technical details of how mappings to the database, or other integrations, are performed.

Presentation layer

This layer is the interface through which users interact with the system.

Here we find the controllers, views and APIs that expose the application services to the outside.

Basically, it acts as a proxy calling the application services to perform operations and respond to user interactions.

Conclusion

We have learned what DDD is in a simple way, developing the fundamental concepts found in this methodology. More than technical rules, DDD invites to understand and model the business problem in a methodical and precise way.

Domain-Driven Design is a tool that helps us to build software that is aligned with the business, easy to maintain and that can grow and adapt over time. It is an approach that allows us to structure and better understand both the problem and the solution, integrating business logic and technology without making one dependent on the other. Although it is a path that requires a change of mindset, in the long term it can make a big difference in the quality and stability of your projects.

If you want to see an example of building this tactical design in Python, I invite you to visit this Git repository in which I try to comply with this methodology + Hexagonal Architecture.

So much for today’s post. If you found it interesting, we encourage you to visit the Software category to see similar articles and to share it in networks with your contacts. See you soon!
Óscar García
Óscar García
Articles: 28