Steven's Knowledge

DDD Strategic Design

Bounded contexts, context maps, ubiquitous language — the half of Domain-Driven Design that decides where your service boundaries actually go

DDD Strategic Design

Domain-Driven Design has two halves. The tactical half — aggregates, value objects, repositories, domain events — gets most of the attention because it shows up in code. The strategic half — bounded contexts, context maps, ubiquitous language — is harder to teach but is where the architecturally consequential decisions actually live.

If you only learn one half, learn this one. Tactical patterns help you write a clean module; strategic patterns tell you where the modules go, why your microservice split keeps producing distributed monoliths, and why two teams using the same word for two different concepts cannot ship a coherent product. This page is about the strategic side; DDD Tactical Patterns covers the rest.

What Strategic Design Is About

Strategic design answers two questions:

  1. Where does the boundary between subsystems go?
  2. What does each subsystem mean by the words it uses?

These sound like the same question but are not. You can have well-drawn boundaries with inconsistent vocabulary (which produces translation bugs at every interface) and you can have consistent vocabulary across badly-drawn boundaries (which produces tight coupling that no naming convention can hide).

The pattern set: bounded context (where the boundary goes), ubiquitous language (what the words mean inside it), context map (how multiple bounded contexts relate).

Bounded Context

A bounded context is the scope within which a particular domain model is consistent and meaningful. Inside the context, words have precise definitions, models are coherent, and behavior is unambiguous. Outside the context, the same words may mean something else.

The canonical example: the word "customer" in an e-commerce business.

  • In the sales context, "customer" means "person evaluating a purchase, with lead status, sales rep assignment, and conversion funnel position."
  • In the support context, "customer" means "person who has bought something, with case history, satisfaction score, and SLA tier."
  • In the billing context, "customer" means "entity that pays invoices, with payment method, tax jurisdiction, and credit terms."
  • In the shipping context, "customer" means "recipient of physical goods, with address book, delivery preferences, and signature requirements."

These are four different concepts that share the word "customer." Trying to model a single Customer entity that satisfies all four leads to:

  • A bloated class with 50+ fields that no one team owns.
  • Constant cross-team negotiation about every change.
  • Bugs where one context updates a field meaningfully empty for another context.
  • Pull requests that touch all four subsystems for a single feature.

The strategic move is to recognize that "customer" is four bounded contexts' worth of customer, each with its own model, each owned by its own team or module. The same physical person may appear in all four — connected by a shared ID — but the model is distinct in each.

What Defines a Bounded Context

A boundary is real when, inside it:

  • Vocabulary is consistent. "Order" means one thing, with one definition.
  • One team owns the model. Changes happen without cross-team coordination.
  • The data store reflects the boundary. Each context owns its own tables (or schema, or database).
  • The behavior is coherent. Business rules can be stated entirely in the context's vocabulary.

A boundary is not real if:

  • The same model is used unchanged across multiple contexts.
  • Multiple teams modify the same database tables.
  • Words have different meanings in different parts of the same supposed context.
  • A single feature requires understanding code in multiple "contexts."

Ubiquitous Language

Within a bounded context, the language used by engineers, product, designers, and users for that domain should be identical. Code that says User while the business says Member while support says Customer is bug-prone, hard to discuss, and hard to onboard onto.

Ubiquitous language is not a glossary handed down. It is a vocabulary that:

  • Emerges from conversation with domain experts. Not invented in a design doc.
  • Is reflected directly in code. Method names, class names, variable names match the spoken vocabulary.
  • Evolves as understanding deepens. Renaming is welcome, not avoided.
  • Is per-context. Different contexts have different languages, and that is the point.

If a billing engineer and a support engineer use the same word for different things, the language is no longer ubiquitous within either context — it has been corrupted by the other. The fix is not to negotiate one meaning, but to recognize you have two contexts and let each keep its own meaning.

Context Maps

Bounded contexts do not exist in isolation; they relate to each other in specific ways. A context map describes these relationships. Evans defined a vocabulary for them:

Shared Kernel

Two contexts share a small, jointly-owned model. Useful when collaboration is intense and the shared part is genuinely shared. Costly because changes require both teams to agree.

Customer / Supplier

The downstream (customer) context depends on the upstream (supplier) context. They negotiate the upstream's API: the customer gets influence on the supplier's roadmap.

Conformist

The downstream context conforms to whatever the upstream provides. No negotiation. The upstream's model becomes part of the downstream's reality. Common when the upstream is a vendor system or an external service.

Anticorruption Layer

The downstream context wraps the upstream's model in a translation layer that exposes a model in the downstream's own ubiquitous language. Protects the downstream from upstream changes and from awkward upstream models. See Anticorruption Layer for the dedicated treatment.

Open Host Service

A context publishes a well-defined protocol or API that many downstreams can integrate with. Cleaner than ad-hoc relationships when there are many consumers.

Published Language

The shared interchange format between contexts (often event schemas or domain events). Stable, versioned, treated as a separate artifact from any one context's internal model.

Separate Ways

Two contexts have no integration. Sometimes the right answer: if integration adds complexity without value, do not integrate.

Big Ball of Mud

Not a relationship to design for — a description of what you have if you ignored the others. Recognize it and contain it (often with an anticorruption layer around the boundary).

How Strategic Design Maps to Service Boundaries

The standard observation: one bounded context maps cleanly to one microservice (or one module in a modular monolith). The mapping is so natural that experienced practitioners describe DDD as "the methodology that tells you where to draw your microservice boundaries."

The corollary is that most microservice failures are bounded-context failures. The most common patterns:

  • Microservice-per-table. Each database table becomes a microservice. None of them is a real bounded context. Every feature crosses many of them. Distributed monolith.
  • Microservices that mirror the org chart's current state. Conway's Law produces this whether or not you intend it (see Conway's Law). When the org chart does not reflect bounded contexts, the services do not either.
  • Microservices that share databases. No bounded context exists when two services read and write each other's data. The boundary is gone.

The strategic-design view of all these is the same: find the bounded contexts before drawing services. If you do not have time for strategic design, you do not have time for microservices.

Doing Strategic Design

A practical process:

  1. Event Storming. A workshop technique (Alberto Brandolini) where domain experts and engineers gather around a wall covered in sticky notes, write down every domain event ("OrderPlaced", "PaymentReceived", "RefundIssued"), then group and arrange them. Boundaries emerge as the events cluster.
  2. Map the actors and decisions. For each event, who triggered it? What decision led to it? Decisions cluster into commands; commands cluster into bounded contexts.
  3. Listen for vocabulary breakdowns. When two participants are saying the same word and meaning different things, you have probably found a context boundary. ("When you say 'order', do you mean the cart submission or the fulfilled shipment?")
  4. Draft a context map. Sketch the contexts you have identified and the relationships between them. Use Evans's vocabulary if it helps.
  5. Validate with code. Strategic design is not finished when the diagram is. Start writing code in the proposed boundaries; the friction reveals where the diagram is wrong.
  6. Iterate. Boundaries are not perfect from day one. Refine as understanding grows.

Common Mistakes

  • Treating DDD as tactical patterns only. Picking up the words "aggregate," "value object," "repository" and skipping bounded contexts. The tactical patterns help only after the strategic ones are in place.
  • One bounded context for the whole system. "Our system is the context." This means you have no contexts. There is no internal boundary, no ubiquitous language discipline.
  • Bounded contexts that mirror data tables. A users-context, orders-context, payments-context. These are CRUD boundaries, not bounded contexts. A bounded context is a coherent domain, not a data type.
  • Sharing a model across contexts because it is "the same thing." The whole point of bounded contexts is that the same person can be Customer in four contexts with four different models. Do not collapse them.
  • Skipping ubiquitous language. Inventing technical terminology that domain experts do not use. The bug-rich translation cost of doing this scales with team size.
  • Designing boundaries up front and not iterating. Strategic design is a learning process, not a one-time decision. Boundaries evolve as understanding does.

Relation to Other Pages

  • DDD Tactical Patterns — the implementation side; aggregates, entities, value objects, repositories, domain events.
  • Anticorruption Layer — one of the context-map relationships, important enough to get its own treatment.
  • Conway's Law — bounded contexts only work as service boundaries if team boundaries align with them.
  • Microservices — the bounded context is the natural unit of a microservice.
  • Modular Monolith — the bounded context is also the natural unit of a module.

Further Reading

  • Eric Evans, Domain-Driven Design: Tackling Complexity in the Heart of Software (2003) — the book that named the field. Read Part IV (Strategic Design) first; many engineers stop at the tactical patterns earlier in the book and miss the central message.
  • Vaughn Vernon, Implementing Domain-Driven Design (2013) — chapters 2 and 3 give the most accessible modern treatment of bounded contexts and context maps.
  • Eric Evans, Domain-Driven Design Reference (free PDF) — short reference card of patterns.
  • Alberto Brandolini, EventStorming (book and talks) — the practical methodology for discovering bounded contexts.
  • Nick Tune, blog posts on strategic DDD — current-decade practitioner writing.
  • Michael Plöd, Hands-on Domain-Driven Design – by example — applied examples.

Pre-commit Checklist

  • For each major subsystem in my architecture, can I name its bounded context and the ubiquitous language inside it?
  • Does any word in my system have two meanings? If so, have I split that into two bounded contexts, or am I papering over it?
  • For each pair of contexts in my system, can I name the context-map relationship (shared kernel, customer/supplier, conformist, anticorruption layer, etc.)?
  • Are my microservice boundaries drawn along bounded-context lines, or am I cutting along data tables / org chart?
  • Have I done a structured discovery (event storming, conversations with domain experts) — or did I draw boundaries on a whiteboard alone?
  • Am I treating bounded contexts as fixed, or am I prepared to refine them as the domain understanding deepens?

On this page