You might already have read my previous articles in Domain-driven Design, "Implementing Domain-driven Design: Important Blocks of Model-driven Design," and "Domain-driven Design: Aggregates with Ruby." This article will focus on bounded contexts and context maps.
To distill a complex domain, you need to understand the problem space deeply. Complex problem domains will contain lots of information, but all information always is not useful; you must find a way to distill relevant information from the problem domain. If you want to make sure your team modeled a domain correctly, you must be sure about misunderstanding and ambiguity in crucial concepts of your project. Dividing and conquering the base on Knowledge Crunching is a common strategy to deal with this complexity by the division of the problem space into comprehensible units.
Knowledge Crunching can be done only through communication with the people who know the domain the most (such as the Domain Expert). The important step that you should not neglect is making sure you and the owners are talking about the same thing with same terminology and meaning. Ubiquitous Language must be the result of your knowledge crunching. You and the business owner(s) must have the same understanding about the different aspects of the domain. This should be repeated to share your understanding with the domain expert and see his feedback as much as you ensure that you have a useful model for the current use cases of a system.
To use knowledge crunching, you need to know the model. First, you need to discover the Domain Model. Here is a definition by Martin Fowler:
|A domain model is a visual representation of conceptual classes or real-world objects in a domain of interest.|
In Fowler's definition, the term conceptual classes refers to vocabularies, ideas, things, or objects in your domain that can be illustrated by sketches, symbols, or definitions. Craig Larman, in his Applying UML and Patterns (3rd edition) book, introduces two techniques that help you to find the conceptual classes of your domain:
- Use a conceptual class category list.
- Identify noun phrases.
You may read Larman's book for the further step of domain modeling.
|All models are wrong, but some are useful. -George E. P. Box|
Domain-driven design helps us to find useful and right models. A domain can be interpreted in different ways and there is no canonical interpretation. Eric Evans created a draft document named the Model Exploration Whirlpool. This document helps us to interpret a model continuously, based to a great extent on feedback from the domain model. Another good technic to domain modeling and knowledge crunching is analyzing patterns, which I will not cover in this article.
What Are Sub-domains?
|Wherever something is wrong, something is too big. -Leopold Kohr|
A domain is the problem space that should be solved by designing a software. At first sight, having a single model seems tempting. But, a single model in your domain will lead you to a big ball of mud. Concepts from one area of the model can be confused with similar-sounding concepts from another area of the system. That's why Domain-Driven Design focuses on the divide and conquer principle to tackle complexity.
Large problem domains can be decomposed into sub-domains to manage complexity and to separate the important parts from the rest of the system. We have three kinds of sub-domains:
- Core Domains: Core Domains must have a fundamental competitive advantage in the system and it should be the reason for the success of the system. Let's say it is the most important part.
- Generic Domain: This is not the core, but the core depends on it. Examples are an e-mail sending service, an accounts package, or a report suite.
- Supporting Domains: These "extra" domains help to support your core.
What Are Bounded Contexts?
Sometimes, an entity in the domain can be classified as a single entity. This idea may sound good, but what if your entity represents multiple definitions in different concepts? Every team or department will have their own definitions and localize that definition. "Product" is a classic example. If you use a single entity for this, you actually violate SRP and that is not the end of story. A shared class will bring too much overhead and become excessively costly. You may think about another approach, such as having a different model for a different concept. But, it's an anti-pattern because a change in one sub-domain can effect the other unrelated models. Indeed, it's a "God" object.
A bounded context clarifies, encapsulates, and defines the specific responsibility to the model. It ensures the domain will not be distracted from the outside. Each model must have a context implicitly defined within a sub-domain, and every context defines boundaries. This kind of boundary is based on concrete technical implementation and it must not have linguistic ambiguity because, first and foremost, it's a linguistic boundary.
The Difference Between Bounded Contexts and Sub-domains
If you know Model and Domain Model and their difference, you have paved half of the path to success. As I described earlier, Model refers to the problem to be addressed with a software effort. But, Domain Model is an idea of a domain taking what's necessary to satisfy requirements. A bounded context for a sub-domain is same as domain model for the domain.
In domain-driven design, we expect full alignment between a sub-domain and its corresponding bounded context. But Reality, however, isn't always so forgiving. The bounded context has its technical things that may not correspond to sub-domains—specially for binding to legacy software or third parties.
How to Discover Bounded Context
|Finding service boundaries are really damn hard... There is no flowchart! -Udi Dahan|
The first step is finding the core use case and searching for keys in the domain that I described earlier. You need to enforce linguistic boundaries to protect the validity of a domain term. In this case, Words and phrases are very important. If a search for one has a different meaning in the same model, the model should be split into at least two bounded contexts.
Be careful about data (uniqueness, ownership, and flow) in your system. Sometimes, data in different bounded contexts may have hard dependency and you have to satisfy these dependencies. It's better to collect related data that are dependent on a single bounded context.
Sometimes, the domain expert will define boundaries for you. The different domain experts may have different attitudes, and it may give you a sign of new, clean bounded context. Also, you can determine a bounded context by communication structures within an organization. You might already know Conway's Law:
|Any organization that designs a system (defined more broadly here than just information systems) will inevitably produce a design whose structure is a copy of the organization's communication structure.|
Almost always, Conway's law will help you to discover the desired bounded context, but it's not a decision you should take without careful planning.
How to Implement Bounded Context
Every bounded context can have different architecture. You may have CQRS for one and Hexagonal for another. But I do recommend use complex architecture for bounded contexts that are located in the core sub-domain. Someone asked about simple CRUD in a bounded context. I believe CRUD is an anti-pattern. As Greg Young said:
|CRUD is our industry's Grand Failure.|
You may ask about the presentation layer and how to integrate bounded context for presentation purposes. You can use a composite UI to pull together bounded contexts of the system. Also, a bounded context can have its own presentation layer.
Communication Between Bounded Contexts: Context Mapping
The technical details of contexts within systems are not the only things that that matter to the staff. Without any relationship between different parts, the system won't satisfy domain expert requirements and has no officiant impact. For example, some of the teams need to share their requirements with other teams. The way to handle relationships between different bounded context is by defining a Context Map. A context map ensures enabling technical teams to have the best possible chance of overcoming issues early and to avoid accidentally weakening the usefulness of the models by violating their integrity.
The context map is responsible for defining an explicit boundary between bounded contexts and makes sure they have a right contact point. A context map should be simple enough to be understood by the domain expert and the technical team. The more important issue you should care about is that the context map should not show the detail of a model; instead, they must demonstrate the integration points and the flow of data between bounded contexts.
Let's talk about how context maps can communicate with each other. There are patterns to describe common relationships between bounded contexts. They do not define technical ways; they're just models to describe how to define relations. The Anti-corruption Layer is the first pattern to create a relationship between bounded contexts. To avoid corruption and protect your model from third parties or legacy code (that you cannot modify or that modification is hard), you can create an isolation layer that contains an interface written in terms of your model.
Shared Kernel refers to a relation based on sharing a common model in terms of domain concepts between bounded contexts. If you do mapping instead of sharing kernels, it brings you too much overhead and sometimes would be impossible. Open Host Service is useful when different bounded contexts try to define a relationship with one bounded context on their own. At first sight, it seems that each bounded context defines the anti-corruption layer, but it provides a set of services that expose the functionality of a context via a clearly defined, explicit contract.
Separate Ways: Sometimes, you may need to integrate with other bounded contexts, but the cost of integration is high, such as with technical complexities. It's better to not integrate contexts at all and simply have teams implement separately from one another. You may integrate them into the presentation. An Upstream/Downstream Relationship is a kind of relationship defined in terms of a direction. Downstream is dependent on Upstream. We have two type of upstream/downstream relationship; the first one is Customer Supplier. The Customer is the downstream and Supplier is upstream. It emphasizes that the customer team's bounded context relies on the supplier team's bounded context, but not vice versa.
Conformist is similar to Customer Supplier, but the supplier is outside. Alternatively, an anti-corruption layer can be used to retain the integrity so that changes to a contact point don't effect the underlying model.
Using a bounded context will help you to break a big problem into smaller parts so you can focus on specific parts and form a consistent language around it.