Chapter 4. Context Mapping

Not only does the bounded context pattern protect the consistency of a ubiquitous language, but it also enables modeling. You cannot build a model without specifying its purpose—its boundary. The boundary divides the responsibility of languages. A language in one bounded context can model the business domain for the solving of a particular problem. Another bounded context can represent the same business entities, but model them for solving a different problem.

Moreover, models in different bounded contexts can be evolved and implemented independently. That said, bounded contexts are not independent. A system cannot be built out of independent components; the components have to interact with one another to achieve the overarching system goals. The same goes for bounded contexts. Although their implementations can evolve independently, they have to integrate with each other. As a result, there will always be touchpoints between bounded contexts. These are called contracts.

The need for contracts results from differences in bounded contexts’ models and languages. Since each contract affects more than one party, they need to be defined and coordinated. Also, by definition, two bounded contexts are using different ubiquitous languages. Which language will be used for integration purposes? These integration concerns should be addressed by the solution’s design.

In this chapter, you will learn about domain-driven design’s patterns for defining relationships and integrations between bounded contexts. These patterns are driven by the nature of collaboration between teams working on bounded contexts. We will divide the patterns into three groups, each representing a type of team collaboration: cooperation, customer–supplier, and separate ways.

Cooperation

Cooperation patterns relate to bounded contexts implemented by teams with well-established communication.

Naturally, this requirement is fulfilled for bounded contexts that are implemented by the same team. It also applies to teams that have dependent goals, where the success of one team depends on the other’s and vice versa. Again, the main criterion here is the quality of the teams’ communication and collaboration.

Let’s take a look at two DDD patterns suitable for cooperating teams: the partnership and shared kernel patterns.

Partnership

In the partnership model, the integration between bounded contexts is coordinated in an ad hoc manner. A team can notify a second one about a change in the API, and the second team will cooperate and adapt, as shown in Figure 4-1. No drama or conflicts.

Partnership
Figure 4-1. Partnership

The coordination of integration here is two-way. No one team dictates the language that is used for defining the contracts. The teams can work out the differences, and choose the most appropriate solution. Also, both sides cooperate in solving any integration issues that might come up. Neither team is interested in blocking the other one.

Well-established collaboration practices, high levels of commitment, and frequent synchronizations between teams are all required for successful integration in this manner.

Note that this pattern might not be a good fit for geographically distributed teams, since it may present synchronization and communication challenges.

Shared Kernel

The shared kernel is a more formal way of defining a contract between multiple bounded contexts. Here, instead of ad hoc integrations, the contract is defined explicitly in a compiled library—the shared kernel. The library defines the integration methods and language used by both bounded contexts, as shown in Figure 4-2.

Shared kernel
Figure 4-2. Shared kernel

The shared kernel is both referenced and owned by multiple bounded contexts. Each team is free to modify the compiled library that defines the integration contract. A change to the contract can break the other team’s build, though; hence, as in the partnership case, this pattern requires high levels of commitment and synchronization between teams.

A peculiar detail about the shared kernel pattern is that in a way, it contradicts a core principle of bounded contexts: that only one team can own a bounded context. Here we extract a shared part of multiple bounded contexts into its own bounded context. As a result, the shared bounded context is jointly owned by multiple teams.

The key to implementing the shared kernel pattern is to keep the scope of the shared kernel small, and limited to the integration contract only.

One team owning multiple bounded contexts

It’s worth mentioning that a shared kernel is a natural fit for integrating bounded contexts that are owned and implemented by the same team. In such a case, an ad hoc integration of the bounded contexts can “wash out” the contexts’ boundaries over time. A shared kernel can be used for explicitly defining the integration contract.

Moreover, in this scenario, the one team ownership principle is not broken—both bounded contexts are implemented by the same team.

Customer–Supplier

The second group of collaboration patterns we’ll examine are the customer–supplier patterns. As shown in Figure 4-3, here one of the bounded contexts—the supplier—provides a service for its customers. The service provider is “upstream,” and the customer or consumer is “downstream.”

Customer supplier
Figure 4-3. Customer–supplier

Unlike in the cooperation case, both teams (upstream and downstream) can succeed independently. Hence, in most cases, we have an imbalance of power: either the upstream or the downstream team can dictate the integration contract.

In this section, we will discuss three patterns addressing such power differences: the conformist, anticorruption layer, and open-host service patterns.

Conformist

In some cases the balance of power is in favor of the upstream team, which has no real motivation to support its clients’ needs. Instead, it just provides the integration contract, defined according to its own model—take it or leave it. Such power imbalances can be caused by integration with service providers that are external to the organization, or simply by organizational politics.

If the downstream team can accept the upstream team’s model, the relationship between the bounded contexts is called conformist. The downstream team conforms to the upstream team’s model, as shown in Figure 4-4.

Conformist
Figure 4-4. Conformist

The downstream team’s decision to give up some of its autonomy can be justified in multiple ways. For example, the contract exposed by the upstream team may be an industry-standard, well-established model, or it may just be good enough for the downstream team’s needs.

The next pattern addresses the case where a consumer is not willing to accept the supplier’s model.

Anticorruption Layer

As in the case of the conformist pattern, the balance of power in this relationship is still skewed toward the upstream service. However, in this case the downstream bounded context is not willing to conform. What it can do instead is translate the upstream bounded context’s model into a model tailored to its own needs via an anticorruption layer, as shown in Figure 4-5.

Anticorruption layer
Figure 4-5. Anticorruption layer

The anticorruption layer pattern addresses scenarios in which it is not desirable or worth the effort to conform to the supplier’s model, such as:

  • When the downstream bounded context contains a core subdomain. A core subdomain’s model requires extra attention, and adhering to the supplier’s model might impede the modeling of the problem domain.

  • When the upstream model is bad or inconvenient. If a bounded context conforms to a mess, it risks becoming a mess itself. This is often the case with integration with legacy systems.

  • When the supplier’s contract changes often, and the consumer wants to protect its model from such frequent changes. With an anticorruption layer, the changes in the supplier’s model only affect the translation mechanism.

From the modeling perspective, the translation of the supplier’s model isolates the downstream consumer from foreign concepts that are not relevant to its bounded context. Hence, it simplifies the consumer’s ubiquitous language and model.

Open-Host Service

This pattern addresses the case where the power is skewed toward the consumers. The supplier is interested in protecting its consumers and providing the best service possible.

To protect the consumers from changes in its implementation, the upstream supplier decouples its implementation model from the public interface. This decoupling allows the supplier to evolve its implementation and public models at different rates, as shown in Figure 4-6.

Open host service
Figure 4-6. Open-host service

The supplier’s public interface is not intended to conform to its ubiquitous language. Instead, it is intended to expose a protocol convenient for the consumers, expressed in an integration-oriented language. Hence, the public protocol is called the “published language.”

In a sense, the open-host service pattern is a reversal of the anticorruption layer pattern: instead of the consumer, the supplier implements the translation of its internal model.

Separate Ways

The last collaboration option is, of course, not to collaborate at all. This pattern can arise for different reasons, in cases where the teams are not willing or able to collaborate. We’ll look at a few of them here.

Communication Issues

A common reason for avoiding collaboration is communication difficulties driven by the organization’s size or internal political issues. When teams have a hard time collaborating and agreeing, it may be more cost-effective for them to go their separate ways and duplicate functionality in multiple bounded contexts.

Generic Subdomains

The nature of the duplicated subdomain can also be a reason for teams to go their separate ways. More specifically, when the subdomain in question is generic, if the generic solution is easy to integrate it may be more cost-effective to integrate it in each of the bounded contexts locally. An example is a logging framework; it would make little sense for one of the bounded contexts to expose it as a service, as the added complexity of integrating such a solution would outweigh the benefit of not duplicating the functionality in multiple contexts. Duplicating the functionality would be less expensive than collaboration.

Model Differences

The difference in bounded contexts’ models can also be a reason to go separate ways. The models may be so different that a conformist relationship is not possible, and implementing an anticorruption layer would be more expensive than duplicating the functionality. In such a case, again, it’s more cost-effective for the teams to go their separate ways.

When to Avoid

The separate ways pattern should be avoided when integrating core subdomains. Duplicating the implementation of such subdomains would defy the company’s strategy to implement them in the most effective and optimized way.

Context Map

After analyzing the integration patterns between a system’s bounded contexts, we can plot them on a context map, as shown in Figure 4-7.

Context map
Figure 4-7. Context map

The context map is a visual representation of the system’s bounded contexts and integrations between them. This visual notation gives valuable strategic insight on multiple levels:

High-level design

A context map provides an overview of the system’s components and the models they implement.

Communication patterns

A context map depicts the communication patterns between teams—for example, which teams are collaborating and which prefer “less intimate” integration patterns, such as the anticorruption layer and separate ways patterns.

Organizational issues

Finally, a context map can give an insight into organizational issues. For example, what does it mean if a certain upstream team’s downstream consumers all resort to implementing an anticorruption layer, or if all implementations of the separate ways pattern are concentrated around the same team?

Conclusion

Bounded contexts are not independent. They have to interact with each other. The following patterns define different ways bounded contexts can be integrated:

Partnership

Bounded contexts are integrated in an ad hoc manner.

Shared kernel

The integration contract is defined as a compiled library that is referenced by both bounded contexts.

Conformist

The consumer conforms to the service provider’s model.

Anticorruption layer

The consumer translates the service provider’s model into a model that fits the consumer’s needs.

Open-host service

The service provider implements a published language—a model optimized for its consumers’ needs.

Separate ways

It’s less expensive to duplicate particular functionality than to collaborate and integrate it.

The integrations between bounded contexts can be plotted on a context map. This tool gives insight into the system’s high-level design, communication patterns, and organizational issues.

Now that you have learned about domain-driven design’s tools for analyzing and modeling business domains, let’s take a look at the different patterns for implementing these models in code.

Get What Is Domain-Driven Design? now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.