We now arrived at the last post of this series about Domain-Driven Design (DDD) patterns…
DDD Concepts and Patterns – Context Map
Different areas or parts of a software system are called bounded contexts in domain driven design (DDD). In the previous post of this series I tried to explain what the properties of these bounded contexts are. Now software systems very seldom consist of only one isolated context. Either the software itself is big enough to be split into multiple ones, or it is interacting with outside systems through API’s. In either case, the context map helps to understand the system by showing the involved contexts and their connections.
We can connect multiple contexts in many ways and model different relationships between them. In DDD these are explicitly named and defined patterns called integration patterns.
Context Map
Let’s first look at a context map example. The following map shows the bounded contexts from the example application also used in my previous post: Billing and Marketing. However, this time I added important detail, the name of the relation between the two contexts.
For this example, I choose the customer/supplier integration pattern. Therefore the billing context (supplier) needs to provide means for the marketing context (customer) to access required data or functions. When we clearly define and communicate these relations with a context map, we provide guidelines on how the contexts need to interact with each other.
How to start
If we work on a greenfield project we start developing the ideal context map according to our current knowledge. But if there is an existing system that we would like to improve, do we start with the ideal situation too? Eric Evans states in his original book about DDD [Eva04] to start with a context map of the current situation. Afterward, we can identify areas we like to improve and derive context maps accordingly.
That’s very practical advice because it is much simpler to improve a certain area of the software than to draw a bright picture of the future that is very difficult to achieve in one step.
Integration Patterns
Here is the list of DDD integration patterns in no particular order: Customer/Supplier, Shared Kernel, Open Host Service, Published Language, Anticorruption Layer, Conformist and Separate Ways.
The integration patterns can be aligned on two axes. The first is how good the involved teams communicate and adhere to the commitments they make. The second is how much control you have over the involved systems. When deciding on which pattern to use it is helpful to keep these in mind. The following introductions to the different patterns include a hint on where the pattern is located on the axes.
Customer/Supplier
In the customer/supplier pattern, as the name implies, one context is the supplier of data or functions to the other. In planning sessions, the teams act according to their role, negotiate deliverables and schedule.
Because the supplier implements functionality the customer needs, the same rules apply as for an external customer to the business. The customer needs to be available for questions the supplier team may have.
On the other hand, the customer is king. Their needs do have to have priority for the supplier team. Otherwise, the customer team may be blocked and cannot continue to deliver value to the company.
Level of communication and commitment: Medium
Level of control over the involved systems: Medium
Shared Kernel
When two contexts seem to have a common set of entities they may want to use a shared kernel. Both teams need to be willing to cooperate with and regard each other’s needs.
A shared kernel should include both model and data persistence. It should be automatically tested on each change with suites from both teams to ensure compatibility.
With the shared kernel duplication can be reduced and we can easily integrate contexts. However, big commitment is needed from the teams because they cannot change the kernel freely.
Level of communication and commitment: High
Level of control over the involved systems: High
Open Host Service
When we need to integrate a bounded context with many others, it can be useful to build a common model for all integrations. This model is published as a set of services that the other contexts use.
It is only feasible to implement an open host service when we can find a common model, and the other contexts are willing to accept it. Of course, each consuming context can build an anti-corruption layer on his end, but this defeats the use of the open host service somewhat.
Level of communication and commitment: High
Level of control over the involved systems: Medium
Published Language
Further development of the open host service may lead to (the use of) a published language that has it’s own bounded context. The language could be a model defined by some industry association or state for example.
As source context, we may translate into the published language and out of it if our model doesn’t match. We provide our services in the published language.
Level of communication and commitment: High
Level of control over the involved systems: Low
Conformist
With the conformist integration pattern, we adapt our model fully to the model of the other context we would like to connect. There are two reasons why it may is a good idea to be a conformist.
The first is if the translation from and to the other model would be very complex. It could be complex because we have no control over the other context for example. Alternatively, it is a very different context that is naturally difficult to translate into our own.
The second reason is if the other context is based on a common standard or component. Most probably the model of this context is very mature in its area. Your model may didn’t get this far yet.
Level of communication and commitment: Low
Level of control over the involved systems: Low
Anticorruption Layer
If we have no control over the context we would like to connect to, and its model doesn’t fit ourselves an anticorruption layer should be considered. This layer communicates with the other context in its language and translates from and to it.
The anticorruption layer can be especially useful when migrating a legacy system. It encapsulates the legacy system, and our new shiny solution communicates only with this layer in a clean language.
Level of communication and commitment: Low
Level of control over the involved systems: Low
Separate Ways
Because integration always has an associated cost you may want to go separate ways instead. Maybe you rather duplicate some logic and data in your own context than build a complex integration layer.
The two contexts may still be connected through a middleware tire or on the GUI level. However, take care not to connect them on a model level accidentally.
Level of communication and commitment: Low
Level of control over the involved systems: Low
Wrap Up / Final Thoughts
I think the benefit of context mapping is very obvious. It is beneficial for teams if they know their relationship to other parts of the software system they build.
Maybe the integrations between different contexts don’t always follow exactly one of the patterns. I think they can be altered or even combined at times.
[Eva04] Eric Evans: Domain-Driven Design – Tackling Complexity in the Heart of Software (homepage)