What is Domain-Driven Design?

A general introduction to Domain-Driven Design and its most common terms.

Momme Ritthoff
claimsforce
7 min readOct 17, 2022

--

Domain-Driven Design (DDD) is an approach to modeling complex software. Its overall intention is to help you identify the crucial parts of your product that will have the most impact on business. DDD helps increase productivity, efficiency, and maintainability of complex domains.

It helps prevent the problem of fast-growing software getting out of control and turning into a Big Ball of Mud.

But how does it help?

DDD is all about reducing complexity — dividing and conquering the problem. It requires you to split up the complex domain/problem into smaller subdomains/subproblems that are more manageable.

It encourages you to stop thinking that every piece of the software needs to be designed perfectly.

By applying that mindset you will not only increase the shipping speed of new features (which could also be helpful in gathering feedback in an early development phase), but you will also be able to invest more time into other parts of the software that are most important to your business.

But what you should build with perfection is the Core Subdomains of your business since it will be the heart of the product you’re creating.

Subdomains

DDD separates the problem domain into different kinds of subdomains to help you identify which software parts are most important to the business, hence most important to focus on:

  1. Core Subdomain
  2. Supporting Subdomain
  3. Generic Subdomain

The Core Subdomain is what makes your system special in comparison to others. It will decide if your software will become a success since it provides the most important functionalities of your whole system. This part is what helps your company make money.

A Generic Subdomain is a part of the system that most companies/systems have such as authentication, geocoding, or email service. They are not the core part of your software that makes your system unique. And that’s why you shouldn’t spend time building another copy of such a service on your own. Try to find a package or provider to buy for such functionalities.

Supporting Subdomains are those domains that are neither core nor generic domains. While Generic Subdomains focus on solving generic problems faced by most products, Supporting Subdomains focus on solving problems that are specific to your product and support the Core Subdomains.

To sum it up, the cooperation of all domains is what keeps your system running. But there are some parts that need more focus than others.

The process of identifying those (sub)domains is one of the most difficult tasks within DDD. There is no simple recipe that you can always apply to get it right. Most likely it won’t be the first model that you come up with. It will rather evolve based on your learnings over time.

A good hint for identifying domains that are NOT Core Subdomains is to look for parts that only consist of CRUD operations.

Domains are built around product use-cases that solve an overall product goal — you won’t call a project a domain that just holds a set of utility functions.

Models, Bounded Contexts, and the need for a shared language

For each of those domains that take care of a small part of your system, you need to identify which information is core to that domain so that the domain can be focused. For the building process of models, DDD stresses collaboration within the team and, more importantly, getting advice from real experts in that domain.

Imagine e-commerce software. During the development process, sales and warehouse were identified as two of the core domains of that software.

Example: e-commerce system with their subdomains and models

A „product“ will be a very common term in that system but it would mean different things in different contexts and domain models. In the sales context, you will be interested in attributes like the price per item whereas the warehouse model isn’t concerned with that data point. Instead, it needs to know about the stock. The term „product” seems to be the same thing but it has different meanings in different contexts.

To follow the Single-Responsibility-Principle the model should only contain what is related to a specific context.

Usually, each domain is owned by a separate team. Creating models per domain enables iterations based on current business needs without having cross-team alignments — teams become independent from each other and their work becomes more efficient.

By focusing only on data points that are needed within a certain domain you will create a kind of boundary around your domain that decouples one problem from another one — spoken in DDD terms: you’re creating a Bounded Context.

A Bounded Context enables the whole team that is working within the same domain to build a shared language for short and precise communication with fewer possibilities of ambiguities and misunderstandings.

The models and the naming of their attributes will not only be used in conversations but also directly in your code to connect the problem domain with the software. Make sure you keep the code and business model in sync. To protect the domain layer, keep it separate from technical concerns. Check out Clean Architecture proposed by Uncle Bob to gain helpful insights on achieving that goal.

Avoid Domain Dependencies and Data Sharing

DDD requires you to keep all your domains as independent as possible.

We’ve already stressed that each model should only contain what is necessary for its domain, but sometimes two models of separate domains have overlapping attributes.

Let’s go back to the e-commerce software example where sales and warehouse were identified as core domains. One attribute that is of interest for both domains could be the productType which would determine whether a product is a technical device, clothing, alcohol, or even firework. The sales domain needs to know this information because some specific categories are only permitted to be sold to people who have reached a certain age or can provide a special license. The warehouse context, on the other hand, uses that information to fulfill special requirements for the storage of goods.

Let’s say that the warehouse domain identified the need of having that property earlier than the sales domain.

Now comes the question: Where should the sales domain get that information/data from?

You might want to say: „Oh, the warehouse team has that data point already — we could just get it directly from their database or search for an endpoint that they are offering.“

Don’t do this! This will create a strong dependency between your team and the team which owns that database/endpoint. In the worst case, the owning team isn’t aware that changes on their database/endpoint could cause other parts in the system to crash because they rely on the current implementation.

Instead, your service should find out where the other domain got that data from. Start listening to where that data point originates e.g. the Frontend, and persist it in a separate database that is owned by your own domain.

Where to get the data from? Listen to the origin!

Sometimes one domain depends on certain information and actions, that originated in another domain, for which communication between domains is needed.

Imagine the sales domain was able to sell a product. This action is of interest for the warehouse domain because it needs to adapt the stock of the sold product accordingly.

Ideally, the exchange of that information should not require either of the domains to call an endpoint/database of the other. Instead, the sales domain should emit a ProductSold event on an event bus to which the warehouse domain can listen. By doing it that way you will also keep two domains loosely coupled.

Communication between Domains

Every domain needs to be as independent as possible. That will make the work of every team much more flexible because decisions on internal changes can be done within the team — no need for cross-team alignments. It will also protect the system from crashing entirely due to parts relying on other parts that stopped working.

You could say that a dependency between two services means data-sharing between them. However, by sharing only ids they will just be loosely coupled, which should be the goal.

On the other hand, there might be cases where new features need to be shipped immediately. In that case, taking on technical debt by going with data-sharing as a TEMPORARY solution could be taken into consideration.

But keep in mind: Your domain should never ask any other domain for any data!

Example: context map for the e-commerce system

To organize the boundaries between different domains a context map can be used to display responsibilities and dependencies on a higher level. A context map includes not only the models with their attributes but also events that are shared between domains grouped by domain. This helps the whole team and especially new joiners to understand the implications of changes.

Key Take-Aways

Focus on building the Core Subdomain of the software to perfection for maintainability purposes, but be pragmatic with the other parts — don’t over complicate simple problems.

Let each domain focus only on attributes for a specific model needed in that specific context.

Separate different contexts from each other to fulfill the Single-Responsibility-Principle by avoiding dependencies and data-sharing between domains.

DDD is mostly focused on creating clearly defined boundaries between different concerns and ensuring their separation.

--

--