Menu
Module 5 / 8 Intermediate 15 min read

Context Mapping

Bounded Contexts don't live in isolation. A Context Map names exactly how they relate — technically and politically — using nine recognized patterns.

In this module

Learn the nine Context Map relationship patterns, see five of them at work across the bookstore's Sales, Billing, and Shipping contexts, and learn to read which side is upstream.

Why a map, not just a list

Module 4 drew three boxes — Sales, Billing, Shipping — around the bookstore’s domain. A Context Map is what you draw between the boxes: who depends on whom, who has to ask permission to change their model, and who’s been left to drift unattended. This matters because a Bounded Context’s independence is never absolute. The moment Billing needs to know an order exists, two models have to agree on something, and how they agree — automatically, by negotiation, by one side simply imposing its will — is itself a design decision with real consequences for how easily either side can change.

A Context Map records direction as carefully as it records the relationship type: which context is upstream (it can change its model and the downstream side has to adapt) and which is downstream (it adapts, or insulates itself, or both).

The nine patterns

  • Partnership — two contexts succeed or fail together, with coordinated, mutual dependency. Neither is upstream; they evolve in lockstep, usually because the same team or a tightly synced pair of teams owns both.
  • Shared Kernel — two contexts deliberately share a common subset of the model — a shared library or module — that neither side may change unilaterally. The bookstore’s Sales and Billing both need the Money value object (an amount plus a currency, with the same rounding and arithmetic rules). Rather than each context maintaining its own slightly-different Money, both depend on one shared money module, changed only by agreement between the two teams.
  • Customer–Supplier — an upstream context supplies a downstream one, and the downstream’s needs get negotiated and prioritized by the upstream team. Sales is the supplier to Billing here: Billing needs Sales to publish reliable, complete order facts, and if Billing needs a new field on that fact, it has to ask Sales’s team to add it.
  • Conformist — a downstream context adopts the upstream model wholesale, with no translation, because it has no real influence over the upstream. If the bookstore integrates with a payment processor whose API shape it cannot negotiate, the integration code that conforms to that processor’s vocabulary directly — rather than translating it into the bookstore’s own terms — is a Conformist relationship. It’s pragmatic, not lazy: translation has a cost, and sometimes the upstream model is stable and good enough to just adopt.
  • Anticorruption Layer (ACL) — the downstream context defends itself with a translation layer that converts the upstream model into its own terms, so the upstream model can’t leak in and corrupt the downstream one. Shipping does exactly this at its edge: a CarrierGateway class translates the third-party courier API’s tracking-status vocabulary ("in_transit", "out_for_delivery", raw provider jargon) into Shipping’s own ShipmentStatus concepts. If the bookstore ever switches couriers, only the ACL changes — the rest of Shipping never knew the old vocabulary existed.
// Anticorruption Layer: the only place that knows the carrier's vocabulary
class CarrierGateway {
    ShipmentStatus translate(CarrierTrackingResponse raw) {
        return switch (raw.statusCode()) {
            case "PU", "in_transit" -> ShipmentStatus.IN_TRANSIT;
            case "OFD" -> ShipmentStatus.OUT_FOR_DELIVERY;
            case "DEL" -> ShipmentStatus.DELIVERED;
            default -> ShipmentStatus.UNKNOWN;
        };
    }
}
  • Open Host Service — an upstream context publishes a well-defined protocol or API for many downstream consumers at once, rather than negotiating bespoke integrations one at a time. If the bookstore later let third-party affiliates query order status, Sales exposing one stable, versioned API for all of them (instead of a custom integration per affiliate) is an Open Host Service.
  • Published Language — a shared, well-documented interchange format, often the very data shape an Open Host Service exposes. The bookstore’s OrderPlaced domain event — its field names, its types, its meaning — is Sales’s Published Language: Billing and Shipping both consume the same event schema rather than each negotiating a private format with Sales.
  • Separate Ways — two contexts with no integration at all, by deliberate choice. If the bookstore’s internal HR system and its Sales context never need to exchange data, that’s not a gap to fix — it’s a correct boundary, left alone.
  • Big Ball of Mud — a region with no clear boundaries and no consistent model. Naming this honestly matters: if a legacy Warehouse system predates the bookstore’s DDD effort and has no discoverable boundary or stable vocabulary, the right move is to flag it as a Big Ball of Mud and treat any integration with it as something to wrap in an Anticorruption Layer — not to pretend it already has the structure it doesn’t.

Direction is not optional information on a Context Map. "Billing depends on Sales" and "Sales depends on Billing" describe two completely different systems to maintain.

Reading integration code for the pattern

You can usually tell which pattern you’re looking at from the integration code itself, without anyone explaining the relationship to you:

  • A shared module imported by two contexts, with no obvious owner → Shared Kernel (or Published Language, if it’s a pure schema with no behavior).
  • A mapper or translator class sitting right at a context’s boundary, converting types → Anticorruption Layer.
  • A context importing another context’s domain types directly and using them unmodified → Conformist.
  • A REST client or messaging consumer calling another service → Customer–Supplier if the API was shaped around this one consumer’s needs, Open Host Service if it’s clearly built for many.

Tip. When you can’t tell Customer–Supplier from Conformist from the code alone, ask the political question: can the downstream team actually get a change made upstream, or are they stuck adapting? That answer is the real distinguishing fact — the code often looks identical either way.

Strategic design is now complete: you can divide a domain into subdomains, draw Bounded Contexts around where a language holds, and map exactly how those contexts relate. Part 3 moves inside a single context — Sales — to build the tactical objects that actually express the model in code, starting with the most basic distinction of all: things with identity versus things defined purely by value.

Next: Module 6 — Entities & Value Objects.