Getting Started With Domain-Driven Design (DDD)

Korbinian Schleifer
comsystoreply
Published in
7 min readSep 2, 2024
Photo by Maarten Deckers on Unsplash

I recently visited the Domain Driven Design Europe conference in Amsterdam with some colleagues. One of them struggled with understanding what DDD actually is. This reminded me of my own learning journey since DDD introduces many new terms, methodologies and patterns, which makes it hard to learn domain-driven design.

This blog post provides an easy introduction to the main concepts presented in Eric Evans’ influential book: Domain-Driven Design: Tackling Complexity in the Heart of Software.

Domain-driven design offers an approach to building better software by closely aligning business requirements with code.

Domain and Model

To build great software, you have to understand what the software is all about. What problem is the software solving? In which business? What is the intended user base?

The domain is the problem space and business area your software operates in.

You can’t write a logistics program without any knowledge about logistics. In fact you can, but it is very likely that your program won’t be correct and will contain a lot of bugs.

To understand the domain and build that knowledge you create a model. What is a model? The model is an abstraction of the real world you build your software on. The model leaves out unnecessary details so you can focus on the important stuff (whatever that is). Your model can take many different forms. It can be documentation, requirements, diagrams, user stories, stickies, knowledge in your head etc.

The model includes all the knowledge that is relevant for solving the business problem.

How do you build this model? The model is what you build together with business experts. Who are these business experts? The people that know most about the domain (that’s why we also call them domain experts). It’s the supply chain manager, the medical director or the financial analyst. Domain experts are the people who want the software to be built.

Most importantly, you should create a common understanding together with the domain experts. You need to agree on what to build and you should also speak the same language while doing so.

Ubiquitous Language

To understand what should be built you need to speak the same language as the domain experts. Since domain experts oftentimes don’t know anything about software you should leave out technical terms from these discussions. You ask the domain expert what should be built and they answer.

The language of the domain experts is made up of words that form the main concepts of the software. For a logistics application these might be routes, ships, trucks and vehicles.

A vehicle might be different from a truck. Vehicle might be the term that actually includes ships and trucks. So when you talk about a vehicle make sure that everyone understands that this can include ships, trucks or any other future mode of transportation.

So the language that you use should be clear and concise. Everyone in your team and the domain experts should have the same understanding of the language.

You should also use this language everywhere. In your model, in your requirements, in your documentation, and in your code. Since this language should be universal for your project and you use it everywhere it is called ubiquitous.

The ubiquitous language is the universal language of the project.

Or simply, the language that you use to communicate and understand what should be built that you use everywhere.

However, your understanding and agreement on the different words and terms might change throughout your project. Either the world changed or you learned something new that you didn’t know before. What should you do in this case? You should update your model to reflect the new insights. This also means updating the code whenever there is a change of language.

This sounds all good and nice, but you might be thinking: “Why should I care?”. A shared language limits the likeliness of miscommunication. Why is miscommunication bad? Domain expert says X and developer understands Y. Y is what gets implemented into the code. Domain expert is unhappy since they asked for X but actually got Y. Domain expert goes on to blame the developer for not implementing X.

Bounded Contexts

After talking to the domain experts for some time you notice that the logistics domain is quite complex. So just putting everything into code would make the code hard to work with in the long term, so you decide to split things up into areas. Tada 🎉, you now have bounded contexts.

Bounded Contexts are all about splitting large models into parts that are easier to deal with.

Consider the following diagram. You might decide to split up your program into three bounded contexts. One context for order management, another one for managing shipping operations, and another one for managing trucking operations.

The vehicle inside the order management is quite generic. So in the context of order management we might not care if the actual shipping will happen by truck, ship or both. Also notice that in the order context we refer to a shipment. Likewise in the shipping and trucking context we use a different term which is cargo. Shipment and cargo might refer to the same “thing” in the real world, however we use different terms that are appropriate within the context. So each context defines its own model and language.

Contexts should be independent from each other, so changes can be made without affecting other areas of the system. That’s the reason why we need to define clear boundaries. This makes the whole system more maintainable and easier to work with.

Entities, Value Objects & Services

Now we have talked a lot about what DDD is, but how would you actually implement all this in code? This is where Entities, Value Objects and Services become relevant.

Entities have an identity, which in simple terms means that they can be distinguished from each other. Code-wise that means that entities have an id property. Entities also have functions that operate on the data of the entity. In our logistics domain we might model a customer as an entity, since we need to be able to distinguish customers from each other.

Value Objects are just a summary of related data fields. Think about just passing some data around in your application. Naturally, these kinds of objects don’t need to have an id. Cargo might be a good fit for this, since we don’t really care about distinguishing one cargo from another (saying this even sounds a bit weird). We care way more about the contents of cargo, the actual data that is inside a cargo value object.

Services are how you wire everything up together. I usually think of use cases. Most times these use cases are too complicated to be put inside of an entity or they require multiple entities to work together.

A problem that I often see in codebases is that people start to move all functionality to Services. You end up with lifeless Entities that only contain data and a bunch of services that are called EntityService. Strictly speaking you shouldn’t do that because the best place to put that functionality is the Entity itself. This also makes your codebase easier to manage and reduces complexity.

Aggregates

Aggregates encapsulate multiple objects to create more complex structures. Aggregates are used to keep invariants, which is a fancy way of saying “things that shouldn’t change”. Those are rules that your application should always enforce no matter what.

An Aggregate is a cluster of associated objects that we treat as a unit for the purpose of data changes.

Let’s make this clear with an example. In our logistics application we might want to create an order aggregate to manage our orders. Admins will be able to create orders and create order items. However order items can only be created over an order since order items that don’t belong to any order don’t make any sense. So our order items won’t be accessible by anything that is not inside the order aggregate boundary.

We also want to enforce that the maximum weight of any order is 40.000kg, since that is the maximum a single truck can transport. This is the invariant. Once an Admin tries to add an order item that would break the invariant rule we will reject the request to keep our order data consistent.

From the example, we learned a couple of more things:

  • Aggregates have a root object (Order) and a boundary
  • The aggregate root is a single entity that is part of the aggregate
  • The root is the only object that outside objects are allowed to access and interact with

Conclusion

Domain-driven Design (DDD) is a method to build software systems that are closely aligned with their domain. This makes these systems less complex and improves the maintainability.

DDD also offers many methodologies to enhance the communication between domain experts and developers to limit the likelihood of miscommunication. If you want to learn more, I recommend you check out the Domain-Driven Design Starter Modeling Process:

Do you want to find out what other tools we at Comsysto Reply apply and what services we offer? Visit our website: https://www.comsystoreply.de/en/services

--

--