Bounded Context Mapping — DDD Context Maps for Microservices
What is a bounded context?#
A bounded context is a boundary within which a particular domain model is defined and applicable. Inside the boundary, every term has a precise, unambiguous meaning. Outside, the same word might mean something completely different.
Example: "Account" in the Banking context means a financial account with a balance. "Account" in the Identity context means a user login with credentials. Same word, different models, different bounded contexts.
Why context mapping matters#
When you build microservices, each service typically owns one bounded context. The hard problem is not what happens inside each context — it is what happens at the boundaries where contexts interact.
A context map is a visual and strategic tool that documents the relationships between bounded contexts. It answers: who depends on whom, who controls the shared model, and how do models translate at boundaries?
The seven relationship patterns#
1. Shared Kernel#
Two bounded contexts share a subset of the domain model. Both teams own the shared code and must coordinate changes.
┌─────────────┐ ┌─────────────┐
│ Shipping │ │ Billing │
│ Context │ │ Context │
│ ┌───┼─────┼───┐ │
│ │ Address │ │ │
│ │ Model │ │ │
│ └───┼─────┼───┘ │
└─────────────┘ └─────────────┘
When to use: when two contexts are closely related and maintained by the same team or tightly collaborating teams. The shared kernel is small and well-defined.
Risk: changes to the shared kernel require coordination. If the kernel grows too large, you have a distributed monolith.
2. Customer-Supplier#
One context (the supplier/upstream) provides data or services to another (the customer/downstream). The upstream team plans work with the downstream team's needs in mind.
The downstream team can influence the upstream API, but the upstream team ultimately decides what to build.
Example: the Orders context (upstream) supplies order data to the Analytics context (downstream). The Analytics team requests specific events or fields, and the Orders team accommodates within reason.
When to use: when there is a clear producer-consumer relationship and the upstream team is willing to collaborate.
3. Conformist#
Like customer-supplier, but the downstream team has no influence over the upstream. The downstream context conforms to whatever model the upstream provides.
Example: your service consumes a third-party payment API. You cannot change their data model. You conform to it.
When to use: when the upstream is an external system, a legacy system, or a team that will not accommodate your needs.
Risk: your domain model becomes polluted by upstream concepts that do not fit your domain language.
4. Anti-Corruption Layer (ACL)#
The downstream context builds a translation layer that converts the upstream model into its own domain language. The ACL isolates your domain from external influence.
┌──────────┐ ┌─────────────────┐ ┌──────────┐
│ External │ │ Anti-Corruption │ │ Your │
│ System │────▶│ Layer │────▶│ Context │
│ │ │ (translates) │ │ │
└──────────┘ └─────────────────┘ └──────────┘
The ACL contains adapters, translators, and facades that convert external data structures into your domain objects.
// Anti-corruption layer: translate external payment model to our domain
class PaymentACL {
translateFromStripe(stripeCharge: StripeCharge): Payment {
return {
id: generateId(),
amount: Money.fromCents(stripeCharge.amount),
currency: Currency.from(stripeCharge.currency),
status: this.mapStatus(stripeCharge.status),
paidAt: new Date(stripeCharge.created * 1000),
};
}
private mapStatus(stripeStatus: string): PaymentStatus {
const mapping: Record<string, PaymentStatus> = {
succeeded: PaymentStatus.COMPLETED,
pending: PaymentStatus.PROCESSING,
failed: PaymentStatus.FAILED,
};
return mapping[stripeStatus] || PaymentStatus.UNKNOWN;
}
}
When to use: always, when integrating with external systems or legacy systems whose model does not match yours. The ACL is one of the most important DDD patterns.
5. Open Host Service (OHS)#
The upstream context provides a well-defined, stable protocol (API) that any downstream context can consume. Instead of custom integrations for each consumer, the upstream publishes a general-purpose service.
Example: the User context exposes a REST API with versioned endpoints. Any context that needs user data consumes this API. No custom integrations per consumer.
When to use: when you have multiple downstream consumers and want to avoid per-consumer coupling.
6. Published Language#
A shared, well-documented data format used for communication between contexts. Often paired with Open Host Service.
Examples of published languages:
- Protocol Buffers (protobuf) schemas
- JSON Schema definitions
- Avro schemas in a schema registry
- OpenAPI specifications
- Domain events published to a message broker
// Published language: shared event schema
message OrderPlaced {
string order_id = 1;
string customer_id = 2;
repeated LineItem items = 3;
google.protobuf.Timestamp placed_at = 4;
}
When to use: when multiple contexts need to exchange data and you want a formal, versioned contract.
7. Separate Ways#
Two contexts have no integration at all. They operate independently. If there is overlap, each context implements its own version.
When to use: when the cost of integration outweighs the benefit. Sometimes duplication is cheaper than coupling.
Drawing a context map#
A context map is a diagram showing all bounded contexts and their relationships. Notation conventions:
- U = upstream, D = downstream
- ACL = anti-corruption layer
- OHS = open host service
- PL = published language
- SK = shared kernel
- CF = conformist
┌──────────────────┐ ┌──────────────────┐
│ Orders (U) │ │ Shipping (D) │
│ │──OHS/PL──▶│ [ACL] │
│ │ │ │
└──────────────────┘ └──────────────────┘
│
│ Customer-Supplier
▼
┌──────────────────┐ ┌──────────────────┐
│ Analytics (D) │ │ Payments (D) │
│ │ │ [ACL] │
└──────────────────┘ └──────────────────┘
▲
│ Conformist
┌───────┴──────────┐
│ Stripe (U) │
│ (external) │
└──────────────────┘
Context mapping in practice#
Step 1: Identify bounded contexts#
Talk to domain experts. Listen for language boundaries — where the same word means different things to different people, you have found a context boundary.
Step 2: Classify relationships#
For each pair of interacting contexts, determine: who is upstream? Who is downstream? Is there collaboration, or is one side conforming?
Step 3: Choose integration patterns#
Based on the relationship type and team dynamics:
- Same team, closely related? Shared kernel
- Collaborative teams, clear producer-consumer? Customer-supplier
- No control over upstream? Conformist + ACL
- Multiple consumers? Open host service + Published language
- Not worth integrating? Separate ways
Step 4: Implement translation#
At every boundary, implement the appropriate translation mechanism. ACLs for external systems. Shared schemas for published languages. API contracts for open host services.
Anti-patterns to avoid#
The Big Ball of Mud — no clear boundaries between contexts. Every service reaches into every other service's database. This is the absence of bounded contexts.
Shared database — two services reading and writing the same tables. This couples their schemas and deployment cycles. Each bounded context should own its data.
Leaking domain language — using upstream terminology in your domain model without translation. Your Shipping context should not use Stripe's charge object directly.
Over-integration — connecting every context to every other context. Sometimes separate ways is the right answer.
Context mapping and microservices#
The ideal microservice boundary aligns with a bounded context boundary. Each service:
- Owns its data store
- Exposes an API (open host service)
- Publishes events in a shared schema (published language)
- Translates external models at the boundary (ACL)
When you decompose a monolith, context maps tell you where to cut.
Visualize your context map#
Map your bounded contexts and their relationships with Codelit. Generate an interactive diagram showing upstream/downstream dependencies, ACLs, and shared kernels.
Key takeaways#
- Bounded contexts define where a domain model is valid — same word, different meaning, different context
- Context maps document relationships between contexts: who depends on whom and how
- Anti-corruption layers protect your domain from external model pollution
- Open host service + published language scales to multiple consumers
- Shared kernels require tight coordination — keep them small
- Conformist is the reality when integrating with external APIs
- Microservice boundaries should align with bounded context boundaries
- This is article #383 of our ongoing system design series
Explore the Discord architecture interactively
Try it →Try these templates
Scalable SaaS Application
Modern SaaS with microservices, event-driven processing, and multi-tenant architecture.
10 componentsNetflix Video Streaming Architecture
Global video streaming platform with adaptive bitrate, CDN distribution, and recommendation engine.
10 componentsSearch Engine Architecture
Web-scale search with crawling, indexing, ranking, and sub-second query serving.
8 componentsContinue learning
Domain-Driven Design (DDD): From Strategic Patterns to Tactical Implementation
7 min readmicroservices decompositionMicroservices Decomposition Strategies: How to Break a Monolith Apart
8 min readservice discoveryService Discovery Patterns: How Microservices Find Each Other
6 min readGo deeper on microservices and integration
As an Amazon Associate I earn from qualifying purchases. Codelit may receive a commission at no extra cost to you.
Building Microservices
Sam Newman · 2021
600 pages on splitting a monolith: service boundaries, data decomposition, migration order.
Enterprise Integration Patterns
Gregor Hohpe, Bobby Woolf · 2003
A catalog of 65 messaging patterns with diagrams - the vocabulary queue designs still use today.
4.6 (581)Designing Data-Intensive Applications
Martin Kleppmann, Chris Riccomini · 2026
Traces replication, consistency and consensus end to end, with the failure modes behind each choice.
3.6 (196)#1 in Data MiningSoftware Architecture: The Hard Parts
Neal Ford, Mark Richards, Pramod Sadalage, Zhamak Dehghani · 2021
Works through service granularity, distributed data and saga patterns as full trade-off case studies.
4.6 (731)#2 in Software Design Tools