Mapper Contexts & Supercontexts: Decoupling Domain-Specific and Domain-Generic Bounded Contexts
By: Nick Tune
Tags: Software Architecture, Domain Driven Design, Sociotechnical

You’re building a new system and two members of your team propose alternative architectures for sending notifications. Which one is correct?

The first developer proposes a push model: bounded contexts should instruct the notifications context to send a notification. The Notifications context (Notifications) should simply obey commands from other contexts and send notifications when instructed to.

The second developer dislikes the push model and proposes a choreographed solution: when bounded contexts raise events Notifications should listen and determine when to send a notification.

How would you architect the solution? More importantly, how would you resolve this decision in the team? How will you design the most effective architecture which supports short-term goals and long-term evolution?

Having DDD skills in the team is a major advantage. Being able to analyse your domain to understand core, supporting, and generic capabilities enables you to make sound engineering trade-offs.

Let’s explore this scenario further with a DDD perspective.

Domain Capability Analysis

The argument for option 1 (pushed commands) is that generic (Notifications) should not depend on specific. If something is generic across many domains, logically it is wrong if it is coupled to something in a single domain.

Looking beyond abstract reasoning, what is this heuristic warning us about? What are the engineering consequences we need to be aware of?

There are two scenarios we want to avoid:

Avoiding domain-specific logic leaking into generic contexts resulting in co-change Inability to reuse or replace generic contexts with an off the shelf solution due to being too tightly coupled to domain-specific bounded contexts

Coupling Domain and Generic Responsibilities

With option 1, there is no co-change between domain-specific and domain-generic APIs. If a new notification is needed only the domain-specific contexts change. But this is not the case with option 2.

With option 2 (choreographed), if a new event is introduced which requires notifications, a domain-specific API will need to publish the new event and the notifications service will need to subscribe to the event and send a notification. This doesn’t feel right — knowledge of the domain is hiding inside the generic context.

Isolating Generic Capabilities

If the notifications service is truly generic and reused across many teams or even an organisation, it will need to know about hundreds of domain events. And with so many teams depending on the notifications context, it will surely become a bottleneck reducing it’s potential for reuse across an organisation.

Another piece of design feedback is that we can’t replace the generic context with an off-the-shelf solution. If it truly is generic across many domains yet we can’t replace it with an off-the-shelf solution which has more functionality, and costs less to run, the design is giving feedback that something is wrong.

So Commands from Specific to Generic is a Best Practice?

All of the evidence suggests option 1 is correct: domain-specific contexts should send commands to domain-generic contexts to decouple them from domain logic. However, our analysis has been shallow. We need to analyse the domain further to give us the confidence we are making good engineering choices.

Deeper Domain Analysis

Looking more closely at option 1 (specific instructs generic), every domain-specific context which needs to send notifications has taken on an additional responsibility. It needs to know when to send notifications and what type of notification to send.

Is it sensible to have notifications logic scattered throughout all of the bounded contexts? Along with tangled code, this could mean a large scale coordination across many teams if the notifications approach changes.

There is also an increased risk or inconsistencies and duplication/waste if each team adopts their own approach to notifications. Shared libraries are a possibility but they don’t solve all of the problems and they also bring compromises.

Some domain concepts partition clearly (red, grey, yellow triangles; blue circles) but some have overlap across different dimensions and can be categorised in multiple ways (blue triangles)

Whenever there is difficulty deciding if a responsibility should belong in one context or another, zoom in and decompose the responsibility further. Look for sub-responsibilities which can be broken apart — maybe there is a hidden domain concept.

When one concept doesn’t fit cleanly into a single bounded context, analyse it further to identify sub-responsibilities. Maybe there is a hidden domain concept which can yield a cleanly-partitioned model.

Zooming into the contentious responsibility of sending notifications, could there be a missing concept? Perhaps there is a third concept which links domain-specific to domain-generic to provide a more elegant model.

Domain Mapper Contexts

One pattern which can be used to decouple domain-specific and domain-generic is the Domain Mapper Context. When a context assumes this role, it listens for specific domain events and maps them onto commands sent to a generic context.

Notice how this pattern’s trade-offs compare to the pros and cons of the initial two options. It provides the pros of both — freedom to change how notifications are sent without cluttering up every domain-specific context with notifications-related complexity.

Consider the scenario: an in-house notifications system is to be replaced by an off-the-shelf solution. The domain mapper would route all commands to the new notifications service, without any domain-specific contexts being impacted.

Consider another scenario: a new notification is to be added. A registration would be configured within the mapper: when {domain event} trigger {notification}.

Notifications are a domain-generic capability — many domains leverage email and push notifications

Notifications Preferences on Twitter — mapping from domain-specific event to domain-generic actions (emails)

Why Called Mapper Contexts? The naming of this pattern is significant. Actions that happen inside one domain are mapped onto actions that happen in another domain (a generic context is generic across many domains so is partially in another domain). You may similarities with other patterns like the messaging translator , however translation implies some equivalence; the translated value is a different representation of the original value. With a mapper, this is not the case. A mapper is more of a listener, observing what happens within the domain. It makes a decision about how to respond to events in the domain with an action in another domain.

Gateway Domain Mapper Contexts

If you decide to replace your custom built generic context with a SaaS solution, your Domain Mapper Context becomes a Gateway Domain Mapper Context.

A Gateway sits at the edge of a system managing inflow and outflow of information

A Gateway Domain Mapper Context performs essentially the same function, however, it now sits at the edge of your system and communicates across system boundaries. It’s a route for information flowing in and out of the system.

The implementation may look the same, but having more precise terminology is useful in allowing you to communicate your architecture more clearly.

Domain Mapper Engineering Trade-offs

The associated with Domain Mapper Contexts patterns are not insignificant. The additional mapping layer means that there are now three collaborators involved. More things that can fail.

There is also co-change. When new events are introduced or existing events change, both the domain-specific contexts owning the events and the mapper context will need to change.

There a widely-used solutions to minimise these costs.

Self-service Domain Mapper Contexts

One pattern frequently occurring in the wild is the self-service bounded context . A bounded context playing this role allows consumers to leverage the context’s capabilities without being blocked by the team which owns the context.

The first variation is a compile time mechanism via source control and the second is a runtime mechanism via API calls.

In the notifications scenario, the self-service context could provide a DSL. Teams which own domain-specific contexts would create a pull-request containing changes in a configuration file (even better - code which compiles and is tested), which using the DSL, configures a mapping between a domain event to subscribe to and a notification to be sent.

With the dynamic version, the corresponding configuration would be performed via an API call or UI. When dynamic configuration is possible and there are no compile-time dependencies on the domain the context shifts towards becoming fully generic. This a common evolutionary pattern (from custom built to commodity).

Published Language

Another DDD pattern to consider in this type of scenario is the published language. Any event that triggers a notification should form part of a published language.

A published language is a contract or an agreement on the format of messages produced by a bounded context. Ensuring that events which require a notification are part of a published language means greater care should be taken to ensure backwards compatibility and notifying consumers of future changes.

Should I Always Use Domain Mapper Contexts?

Definitely not. Each of the patterns presented in the post are valid and used successfully in a variety of systems. Domain Mapper Contexts are another pattern with clear trade-offs which you can use to explore alternative modelling possibilities.

The Search for Supercontexts

It’s easy to model domains at a naive or superficial level. When hearing a word like ‘notifications’, it’s easy to fall for the cohesive name fallacy, assuming that because a word sounds like a single concept that it must be represented by a single bounded context in our system.

When we use DDD to analyse at a deeper level, and we look to separate domain-specific and domain-generic concepts, often we will find that there are multiple ways to model the domain, including having multiple bounded contexts — delineating domain specific from generic — within a single supercontext.

Decoupling domain-specific from domain-generic is not about creating beautiful abstract models that follow ancient DDD rules, it’s about decoupling concepts which change together for different reasons so that we can engineer trade-offs which allow systems to evolve more easily.

Try to avoid the Single Domain Classification Fallacy: assuming that one high-level capability, Notifications, must have a single domain classification (e.g. generic).

In order to gain deeper domain insights, I recommend bringing DDD skills and DDD activities into your team’s way of working. Experiment with EventStorming and The Bounded Context Design Canvas as a starting point for learning how to model better bounded contexts.