Este post también está disponible en español.

Posts in this series:

Context Mapping

In a large system there are multiple Bounded Contexts that collaborate with each other. Some are developed by company teams and others are legacy systems or third-party systems. If the teams do not know the existing contexts and how they relate with each other, there is a risk of corrupting the models and generate a Big Ball of Mud.

The relationships between the teams are also important, not all teams are motivated by the same forces or are aligned in the same direction. Politics between teams must be managed properly.

To combat this, a Context Map is usually created to visualize the relationships between the Bounded Contexts at a technical and organizational level.

This map allows everyone to have a holistic view, allowing conflicts to be resolved early and avoiding accidentally corrupting the integrity of the models. The map evolves over time, it’s a living document.

First Context Map

In this first example, we can see that the teams that develop the Payroll and Human Resources contexts work together on the same project establishing a partnership. On the other hand, the Payroll team is influenced by the Finance context, therefore it must be modified when the other changes. Finally, the Human Resources context is integrated with a context developed by a third-party, it adapts to its model to avoid the cost of translations.

Next we are going to see the different elements that are usually visible in a Context Map.

Anti corruption Layer

When two models need to interact, they are likely to use a different ubiquitous language and are modeled in a completely different way. If we are not careful when integrating the models and we don’t adapt the interfaces we can corrupt our model by introducing foreign concepts and generating ambiguities.

To avoid corruption and protect our model from external influences, we can create a layer that contains interfaces written with the language of our model. That layer implements adapters that translate those concepts to the concepts of the other model. This layer is known as the Anticorruption Layer (ACL).

Anti corruption layer

In the image we can see that a small layer is created that does not contain any business logic, it only contains translation logic. The Orders context model is adapted to the Commerce model to avoid corrupting it and to limit the dependencies.

For example, a user management context might have the UserAccount class with a roles property that might have the values admin, project-manager, or developer. This context is generic, it is responsible for managing users with roles in a generic way and can be used for different businesses and systems. It could be a SaaS service developed by a third-party.

In a project management context we could transform that UserAccount model into our context’s own models (eg: Developer or ProjectManager classes). In the context of user management, roles are simply strings, it is a generic model and can manage user accounts that can be used in any other system. Instead our project management context handles business concepts where a Developer or a ProjectManager are core classes of our model and have business logic. The ACL is in charge of making these translations, preventing our model from being contaminated with the concept of UserAccount and roles.

Let’s see a code example:

interface DeveloperService {
    fun getDeveloperById(id: Int): Developer
}

// Adapter implementation in the ACL
class UserManagementDeveloperService(
    private val userManagementContext: UserManagementContextAPI,
): DeveloperService {
    override fun getDeveloperById(id: Int): Developer {
        val userAccount = userManagementContext.getUserAccountById(id)
        if (userAccount == null || !userAccount.hasRole("developer")) throw NotFoundError()
        return Developer(id, userAccount.fullName)
    }
}

Shared Kernel

If two teams work on the same application in two bounded contexts that share many domain concepts and logic, the overhead of keeping the teams isolated and doing translations from one to the other is very high.

In these cases it is better to collaborate and share a portion of the model. This shared model is called Shared Kernel.

Because there is a code dependency you have to be very careful because one team can break the code of the other. Changes in the shared code requires a continuous integration system to verify both models.

Shared Kernel

Open Host Service / Published Language

Sometimes a Bounded Context offers services to others by publishing elements of its model and exposing some internal complexities. In these cases, we sometimes see that each Bounded Context that consumes these services implements its own ACL layer, making translations that are very similar to each other.

In these cases, to avoid the duplication of the ACLs, the translation can be passed to the Bounded Context that provides the service. These services constitute an API that uses a public model that is common to all Bounded Contexts. This public model is known as a Published Language and this API is known as a Open Host Service.

To better understand it we could make an analogy with languages. If a Bounded Context speaks German (its own ubiquitous language), another Spanish, and another French, the Published Language (which everyone understands) would be English (an international language used to communicate without translators).

For example, a complex sales order system may require translations in the CRM and commerce systems. To avoid this duplication the order system can publish a simplified version directly as an API.

Published Languages must be very well documented and their contract must be kept as stable as possible.

Of course this also has its drawbacks, but like everything in our industry, it is about finding the best trade-off in each context.

Open Host Service

Politics: Separate Ways

If the cost of integrating is too high for technical or political reasons, you can decide not to do any integration at all. The integration can be done by manual processes. Although it sounds strange at first, this is a very common approach. When a new employee starts in a company, it is common that it’s user must be created in different systems. This is because they are not integrated.

It is important to reflect this decision in the Context Map so that everyone knows about it.

Politics: Partnership

If 2 teams are responsible for different contexts but are working towards a common goal, a partnership can be formed to ensure cooperation in the integration. You can align releases, create a shared kernel, etc.

Politics: Upstream / Downstream

When a client depends on a supplier, these roles are often indicated with the analogy of a river. The client (who consumes the data or services) has the downstream role and the supplier (who provides the data or services) has the upstream role.

You can define the relationship between contexts based on the direction of the relationship. The downstream depends on data or behavior of the upstream.

We can see different kinds of relationships between customers and suppliers.

Customer / Supplier

In this upstream/downstream relationship, a contract is agreed between the parties. Members of the team that develops the client context go to the provider context team meetings to ensure that their needs and dates are understood and considered.

Customer / Supplier

Conformist

In this relationship the supplier does not collaborate with the client therefore the client must comply. It is a typical relationship when the provider is an external service.

For example, an external payment gateway provider is not going to change its API for a particular customer need.

Example of a complete Context Map

Context Map

Next post

In the next post we’ll see all the technical options we have when integrating Bounded Contexts.