Domain-Driven Design Concepts
In this post, I’ll try to explain some high-level concepts of Domain-Driven Design and then I’ll mention my experience with the application of these concepts.
What, Why, When?
Domain-Driven Design is a set of practices to model complex business domain into software better. These practices try to tackle different aspects of complex business domains, like communication, high-level design, team relationships/communications, low-level class designs, etc.
Domain-Driven Design is not bound to Object-Oriented Design. Eric Evans and some others from the Domain-Driven Design community claim that the same concepts can also be applied to functional programming paradigm or actor model. Also, they say that there is no specific dependency on relation databases, you can adapt NoSql, Key/Value or Graph databases.
Domain-Driven Design is not bound to a specific architectural style, Eric Evan’s blue book is mostly about layered architecture but the book’s time was also the time of the layered architecture. You can use layered, hexagonal(port and adapter), event-driven, event-sourcing, CQRS, Microservices architectural styles with Domain-Driven Design concepts.
Alignment is one of the best outputs of applying Domain-Driven Design concepts. You name the things, their meanings, their responsibilities/behaviors, their borders, their limitations. Then you start to talk the same language at a different level(business, application architecture, application detailed design, cross-team communication, etc.) with different stakeholders.
Even your domain change, you rotate/start another domain, you can reach the same alignment in a very short time with the application of the same concepts. Because it becomes like an application of a template.
Domain-Driven Design guides you to focus on strategically important parts of your domain. You try to find the answers to the questions like, “What are the most important things in my business?”, “How can I focus/invest in these parts to increase my business success chance?”, “What should be my technical and organizational investments on these important parts of my business?”, etc.
Domain-Driven Design offers some lower-level software design practices at a tactical level to make your codebase more maintainable(testable, less error-prone, readable, etc.)
Can we apply Domain-Driven design practices to all business domains? Domain-Driven Design generally emphasizes the “Complex Business Domain” concept. It says that these principles and practices are valid to manage a complex business domain. Then, the questions are what’s the measurement of complexity and what happens if I apply these principles and practices to a non-complex business domain?
Complexity can be relative, there is a scorecard approach in Vaughn Vernon’s Implementing Domain-Driven Design book. In this scorecard you get some points according to your business domain’s nature, like whether it mostly consists of CRUD operations(you get 0) or not, the number of business operations, potential to grow in complexity, the change rate of the features, whether your domain is new to you or not, etc.
If you apply Domain-Driven Design to a simple domain then you make your solution unnecessarily complex. And it’s not the goal of the Domain-Driven Design, because Domain-Driven Design tries to simplify your solution. These unnecessary things can be different applications/services, different layers, different classes, etc. Also, applying Domain-Driven Design needs some investments, time, money, etc., so it can be a waste of resources if you apply Domain-Driven Design to a simple domain.
Rise of the Domain-Driven Design Concepts
Domain-Driven Design concepts are not new(2004, Eric Evan’s “Blue Book” was published), these concepts always have been relevant for this long period and the importance of these concepts has increased to parallel to the rise of microservices architecture style. You can find easily Domain-Driven Design concepts as an answer to the questions, “How will we determine our service boundaries?”, “How will we divide our monolith architecture into microservices?”, in many presentations about microservices architecture.
Sam Newman said that “Domain-Driven Design has some great ideas that can help us find our service boundaries. Always, often we work with organizations looking for a microservice migration the very first thing we’ll start with is performing some kind of Domain-Driven Design modeling exercise on the existing monolithic application architecture” in his Monolith Decomposition Patterns presentation at QCon London 2020.
Domain-Driven Design at Three Levels
I’ll try to explain three high-level concepts of Domain-Driven Design. You can think of these as lifecycle components of the software that you build evolutionary with a holistic approach. So, they are not built at one-time, you pass over on these lifecycle components again and again, and they become more mature with the progress of time.
Ubiquitous Language
Like many other areas, one of the biggest problems in software development is communication. Understanding the business needs and delivering the right product with minimum effort. The ubiquitous language concept aims to make this communication frictionless, smooth across the different stakeholders without needing any translation between business jargon and technical jargon with building a shared team language.
So, practice is building a ubiquitous language that every stakeholder uses for the business domain and software domain model. I think this practice as when you make a walkthrough over your software domain model with a product manager, you shouldn’t need much translation, a product manager can see and understand the corresponding part of the business domain in your software domain.
It seems easy at first, but naming is one of the challenging problems in software development. Finding good names for your software components needs a different talent and a good name is a relative thing.
To build this ubiquitous language some of the key things;
- Have some concrete, dynamic documentation about your ubiquitous language,
- Be patient when building and evangelizing this ubiquitous language,
- Talk a lot with your domain expert,
- Don’t forget that if something change in your ubiquitous language then your software model also should change.
Strategic Design
Strategic design principles are basically about breaking the large business domain into smaller parts, divide and conquer with some rules.
As Eric Evans said in his Blue Book;
Yet the entire business model for almost any such organization is too large and complex to manage or even understand as a single unit. The system must be broken into smaller parts, in both concept and implementation. The challenge is to accomplish this modularity without losing the benefits of integration, allowing different parts of the system to interoperate to support the coordination of various business operations.
To understand the Strategic Design, you have to understand the concepts, Domain, Subdomain, and Bounded Context, and then the concept of Context-Map.
Domain and Subdomain
Your Domain is your entire business, and there are different functions in this Domain, these are your Subdomains. You can think of these Subdomains separately. For example, your online learning marketplace Domain can be consist of Subdomains; Product Catalog, Recommendation, Search, Payment, Learning Experience, Authentication, and Authorization, etc. A Subdomain is a logical concept and it’s related directly with the problem space of your business.
You can categorize your Subdomains as Core Subdomain, Supporting Subdomain, and Generic Subdomain.
Core Subdomain is your most important Subdomain, you have to be successful on this Subdomain firstly to make your business successful. For our online learning marketplace example, this can be the learning experience domain.
Supporting Subdomains are somewhat specialized Subdomains for your Core Subdomains. These are also essential for the success of the business but not core. For our online learning marketplace example, these can be Search, Recommendation, Payment.
Generic Subdomains aren’t specialized for your business. These are the necessary solutions for the business. For our online learning marketplace example, this can be Authentication and Authorization.
Bounded Context
After working on the problem space with Subdomains then we have to start to think about our solution space. This solution space includes the necessary software assets and their relationships. We called these individual, autonomous high-level software assets as Bounded Contexts.
As Vaughn Vernon said;
The solution space is one or more Bounded Contexts, a set of specific software models. That’s because the Bounded Context is a specific solution, a realization view, once developed. The Bounded Context is used to realize a solution as software.
So, we use the divide and conquer method at solution space with Bounded Contexts as we did at problem space with Subdomains.
A context is a linguistic context with a product/business vision. A context contains a model, an independence model. A model item has a context-dependent meaning, it’s like an Account model can have different meanings in different contexts.
A Bounded Context is like a conceptual container, it has some determined boundary, it should be clear that what concepts from our problem space(and of course from the Ubiquitous Language) included in a Bounded Context. A Bounded Context should encapsulate its model from outside. A Bounded Context is our solution unit at the highest level, so it can be a service, an application, a module(for example in Java, this module is not like a package, but a module in terms of Java Jigsaw).
When we create a Bounded Context we can take into account some more detailed model items, like aggregates, UX, API perspective, some technical constraints like performance, scalability, etc.
Our sample Bounded Contexts for a Search Subdomain can be Search Coordinator, Language Detection, Query Building, Search Result Ranking, etc.
Context Map
We have some Bounded Contexts, then what’s next? We need a high-level representation of our integrated solution space. There are two aspects here; one of them is how these separated Bounded Contexts are communicating and the other is what’s our organization structure, team organization to work on these Bounded Contexts.
We build a Context Map to visualize the answers to these questions.
Some important properties of a Context Map are;
- Firstly, it should map the existing terrain, if it exists, not future/imagined one,
- So, first understand where you are, and then determine where to go next,
- It is informal and not like an enterprise architecture diagram,
- It visualizes relations both technical and organizational perspective, using the same model,
- It shouldn’t build with lot’s of ceremony and it shouldn’t contain too many details,
- It should be one of our communication tools (deserve to be posted on a wall in a team area.)
A Context Map makes clear that how our Bounded Contexts, so Subdomains, depend on each other. We can see what kind of relationships exist between Bounded Contexts, e.g. upstream, downstream. We can see technical integration types between Bounded Contexts, e.g. Is there any shared models(shared kernel)? Is there any public APIs(open host service)?, Is there translations layers(anti-corruption layer)? etc.
For the organizational part, we can see what are our organizational dependencies and what are their types(partnership, customer/supplier, conformist, etc.).
Don’t forget that nothing can be perfect at first but can be perfect with the progress of time, so like all Agile methods this is also an evolving activity. And we are at the highest level in terms of our solution so we should have some satisfying confidence level.
“All models are wrong, but some are useful.”
Tactical Design
We’ll start to design inside of our Bounded Contexts at the tactical level. The granularity of this level is classes(or functions, actors) and their stereotypes.
One of the important goals of tactical design is creating a knowledge-rich domain model that’s the opposite of anemic domain model. Every software domain model should own it’s own responsibilities as expected in your business domain model, you shouldn’t create only data-structure like software domain model and set of services that own all business logic(Transaction Script).
Domain-Driven Design tactical design stereotypes are Entity, Value Object, Aggregate, Aggregate Root Entity, Factory, Repository, Application Service, Domain Service, and Domain Event. Each of them has some design principles, rules. Let’s look at definitions of some of them.
You use Entities if you care about the individuality and life cycle of a domain concept. Two Entities are always different even their all properties are the same except their Ids. For example for our online learning marketplace domain, the Student domain concept is an Entity, because every Student is unique even their all properties are the same except their Ids. You should manage their lifecycle, they are mutable objects.
If you don’t care about individuality but only properties then you can use Value Objects. For example, Email/Postal Address, Money can be Value Object. You don’t have to manage the lifecycle of these objects, they can be immutable objects, if you want to change, then you can create a new one without changing the existing one.
Aggregate is about the consistency of a set of Entities/Value Objects. It’s like a synonym of transactional consistency. If you have a set of related Entities/Value Objects that you have to keep them consistent then you build an Aggregate. Let’s say you have a business rule for the Instructor domain concept in our online learning marketplace domain, like an Instructor can only create a maximum number of Free Coupons in a period of 1-month. Then you should keep consistency between Instructors and their Free Coupons. You shouldn’t add Free Coupons to Instructors beyond their knowledge. Otherwise, you can break this consistency. Then you should manage these two domain concepts together with the leadership of the Instructor domain concept. This an Aggregate, and Instructor is your Aggregate Root Entity. There are some best practices while designing Aggregates, like change only one Aggregate in one transactional context, keep Aggregate to Aggregate references over Ids, use eventual consistency between Aggregates, keep them small, etc. You can face some exceptional cases to these best practices, especially for the UX perspective.
You can use Application Services to manage application-level functionalities, like transaction management, security, etc. These services don’t contain any business logic.
You can use Domain Services to manage business logic that spreads on many Aggregates.
How are these concepts related to Microservices?
You want Microservices to manage your business domain with autonomous, individually scalable, individually deployable, small(relatively) parts. We call each of these parts as a Microservice. One of the biggest problems to start with Microservices is how do you break your business domain into these small parts.
As we already mentioned, Domain-Driven Design has some concepts that they also focus on this question. These concepts are Subdomain and Bounded Context at Strategic level, and Aggregate at Tactical Level. They are all about breaking your domain into autonomous, cohesive parts.
In a fine-grained level, each of your Aggregates can behave as a Microservice.
In a more coarse-grained level, each of your Bounded Contexts can behave as a Microservice.
There are no strict rules to match these concepts with a Microservice. It depends on your conditions, you may want a Microservice that includes a set of Bounded Contexts.
My Experience
I had one project that built with the Domain-Driven Design concepts from start to finish at my previous job. The project lasted about 1 year with a 7-person development team. It has a modular monolithic architecture. We had 5 or 6 Bounded Contexts. Each of our Bounded Contexts was a separate module. These Bounded Contexts had very sharp, well-defined boundaries and interfaces between them.
That was my first complete Domain-Driven Design experience, we had some good and “could be better” parts for the design and implementation of the project.
The good parts are team alignment and communication for the business and software model, separation of business concepts into Bounded Contexts, using eventual-consistency mechanism across different Bounded Contexts, simplicity/readability/maintainability of our codebase(the most complex class was about 500 lines).
For the “could be better” parts; We broke the Aggregate design rule for one case, like using 2 different repositories for an Aggregate Root Entity and one of its related Entity. We couldn’t use the Domain Service concepts well. Instead of Domain Service, we had some bad practices like trying to manage Hibernate Entities like Spring Beans(via @Configurable annotation) and injecting some Spring service beans to them. This bad practice was really painful, it required using some kind of instrumentation mechanism and it made our development/test process a little harder. We faced lots of non-injected dependency problems. We had a very good unit/integration test coverage, but we didn’t have enough automated tests or architectural fitness functions to prevent some architectural rules, like module boundaries. These were only checked with code reviews manually, and there were some unwanted usages.
Currently, I’m working on another project that we use Domain-Driven Design concepts as well. We haven’t released its first version yet, but it’ soon. Our domain is quite complex, deep and new to all team members. And we don’t have a product manager yet. We are following an evolutionary approach and Domain-Driven Design has some important practices that enlighten our road. Otherwise, it could be more chaotic. Domain-Driven Design helps us to manage our domain with smaller Subdomains, Bounded Contexts, and Aggregates so we can focus more on our complex problems as working with these small separated parts. Team alignment about the business model and software model is getting easier with the progress of time. I think an ideal point for this alignment is like, every team member can find the implementation location of a functional requirement or a non-functional requirement with only one class search :).
Conclusion
I believe that Domain-Driven Design concepts are well covered on the Internet. You can find many resources(books, videos, blogs, code samples, etc.) and resources that have many links to these resources(like one, and many others). I tried to cover some of them at a very high level, if you need more, you can look at these resources.
Concrete experience sharings/workshops/exercises about these concepts can be more valuable. These concepts have some meanings and definitions of course(I accept some of them are tricky), but the software engineering field is quite a tough field to put some standards. So, most of the time these meanings can be relative. And these relative meanings can direct you some good ways, or some bad ways. So, it seems meaningful to me to share our concrete experiences with these concepts. I understood this as like this, applied, and my output was like this. Most of us know well that reading and applying are different things, it’s the transition from theory into practice.
If you are new to these concepts, it worth exploring more. Eric Evan’s Blue Book is not an easy book to read but it’s the source of these ideas. You can find many resources in Eric Evan’s website. Also, Vaughn Vernon’s books are very valuable, they make easy to transition from theory into practice for the Domain-Driven Design concepts.