No Bounded Context lives in isolation. In a real system, contexts need to collaborate: Underwriting consumes data from Sales, Billing reacts to underwritten policies, and so on. Context Mapping is the DDD discipline that describes how the different Bounded Contexts relate to and integrate with each other and, above all, what kind of power, dependency, and translation relationship exists between them. This lesson closes the module by connecting strategic design with organizational reality: mapping patterns not only describe technical integrations, but also relationships between teams. Understanding them will allow you to design healthy integrations and protect yourself from external models that could contaminate your own.
Contents
- What is a Context Map and why does it matter?
- Patterns of relationship between contexts
- The Anti-Corruption Layer (ACL) in detail
- The Open Host Service and the Published Language
- Drawing an example Context Map
- What is a Context Map and why does it matter?
A Context Map is a representation—usually a diagram—of all the Bounded Contexts of a system and the relationships between them. It does not describe the internal detail of each context (that is tactical design), but rather the boundaries and points of contact.
Its value is twofold:
- Technical: it shows which context depends on which, where the data flows, and where translations are needed.
- Organizational: the relationships between contexts usually reflect relationships between teams. An upstream context imposes its model; a downstream one must adapt. Knowing who is upstream and who is downstream avoids conflicts.
Two cross-cutting concepts that appear in all the patterns:
- Upstream: the context that provides, the one others depend on. It has more "power": its changes affect the others.
- Downstream: the context that consumes, which depends on the upstream one and must adapt to its changes.
- Patterns of relationship between contexts
DDD catalogs several relationship patterns. These are the most important ones:
| Pattern | Power relationship | Description | When to use it |
|---|---|---|---|
| Partnership | Egalitarian | Two contexts/teams depend on each other and coordinate their changes jointly. Shared success or failure. | Teams that must evolve at the same time and communicate well |
| Shared Kernel | Egalitarian | They share a portion of the model and code (a common module). Changing it requires agreement from both. | A very stable common subdomain between two close teams |
| Customer-Supplier | Upstream/Downstream | The supplier (upstream) serves the needs of the customer (downstream); the customer can influence the supplier's plan. | The upstream is willing to prioritize the downstream's needs |
| Conformist | Upstream/Downstream | The downstream adopts the upstream's model as-is, without translating it, because it has no ability to influence. | The upstream does not negotiate (e.g. a large external system) and its model is acceptable |
| Anti-Corruption Layer (ACL) | Upstream/Downstream | The downstream builds a translation layer that isolates and converts the external model into its own. | The upstream's model is bad or foreign and you don't want it to contaminate yours |
| Open Host Service (OHS) | Upstream | The upstream publishes a well-defined API/protocol so that many consumers can integrate in a standard way. | Many clients consume the same context |
The key difference between Conformist and ACL is crucial:
- Conformist: "I swallow your model as-is, with its flaws, because it's cheaper for me than translating."
- ACL: "I don't want your model inside my house; I put in a layer that translates it into mine and protects me from your changes."
- The Anti-Corruption Layer (ACL) in detail
The Anti-Corruption Layer is probably the most useful pattern in systems that integrate with legacy or external software. Its mission is to prevent another context's model from leaking in and corrupting ours.
Imagine that your Underwriting context must query an old external credit scoring system that returns raw data with a model very different from yours:
// RAW model returned by the external system (we don't want this in our domain)
public class ExternalScoringResponse {
public int cod_riesgo; // 1, 2, 3... what do they mean?
public String flag_moroso; // "S" / "N"
public double pct; // of what?
}This external model is ugly: numeric codes without meaning, flags with strings, cryptic names. If we let it in as-is, it contaminates our entire domain.
// ACL: translates the external model into OUR clean model
public class CreditScoringAcl {
private final ExternalSystemClient external; // the legacy system
public CreditScoringAcl(ExternalSystemClient external) {
this.external = external;
}
public CreditProfile obtainProfile(InsuredId id) {
ExternalScoringResponse raw = external.query(id.value());
// TRANSLATION: from the foreign model to ours, clean and meaningful
RiskLevel level = translateLevel(raw.cod_riesgo);
boolean delinquent = "S".equals(raw.flag_moroso);
return new CreditProfile(level, delinquent); // object of OUR domain
}
private RiskLevel translateLevel(int code) {
switch (code) {
case 1: return RiskLevel.LOW;
case 2: return RiskLevel.MEDIUM;
default: return RiskLevel.HIGH;
}
}
}What we achieve with the ACL:
- The external system and its ugly model are encapsulated behind
CreditScoringAcl. The rest of our domain never seesExternalScoringResponse. - The translation converts
cod_riesgo(a meaninglessint) into aRiskLevel(a clear concept of our domain) and the"S"/"N"flag into aboolean. - Our domain receives a clean
CreditProfile, expressed in our Ubiquitous Language. - If the external system changes its format, we only change the ACL; the rest of the domain remains intact. This is the key protection.
graph LR
subgraph Nuestro[Bounded Context: Underwriting]
D[Clean domain]
ACL[Anti-Corruption Layer]
end
subgraph Externo[Legacy external system]
E[Ugly / raw model]
end
D --> ACL
ACL --> EThe ACL acts as a membrane: inward it speaks our language; outward, that of the external system.
- The Open Host Service and the Published Language
When an upstream context has many consumers, it is not reasonable to adapt to each one. Instead, it publishes an Open Host Service (OHS): a stable, documented API that anyone can consume in a standard way. Alongside the OHS there usually goes a Published Language: a well-defined and versioned exchange format (for example, a JSON schema or an OpenAPI contract).
# Published Language: contract of the event published by the Policies OHS
PolicyUnderwrittenV1:
type: object
required: [policyId, insuredId, effectiveDate, annualPremium]
properties:
policyId: { type: string, format: uuid }
insuredId: { type: string, format: uuid }
effectiveDate: { type: string, format: date }
annualPremium:
type: object
properties:
amount: { type: number }
currency: { type: string, example: "EUR" }Why this contract is a good Published Language:
- It is explicit and versioned (
PolicyUnderwrittenV1): consumers know exactly what to expect, and a futureV2does not break those who useV1. - It defines the required fields (
required) and their types: it is a formal agreement, not a structure that changes without warning. - It is expressed in a neutral format (JSON/OpenAPI-style schema), independent of the internal technology of the context that publishes it.
With an OHS and a Published Language, the Policies context can have dozens of consumers (Billing, Notifications, Reinsurance...) without coupling to any of them: they all speak the published language.
- Drawing an example Context Map
Let's put the patterns together in our insurance company's Context Map:
graph TD
Ventas -->|Customer-Supplier| Suscripcion
Suscripcion -->|ACL| ScoringExterno[EXTERNAL Credit Scoring]
Suscripcion -->|publishes OHS events| Polizas
Polizas -->|Open Host Service| Facturacion
Polizas -->|Open Host Service| Notificaciones
Polizas <-->|Partnership| SiniestrosReading the map and justifying each relationship:
- Sales → Underwriting (Customer-Supplier): Sales is upstream and provides the leads; Underwriting is downstream and can ask Sales to include certain data. There is dialogue between teams.
- Underwriting → External Scoring (ACL): the scoring system is external and we don't like its model, so we isolate it behind an anti-corruption layer.
- Policies → Billing / Notifications (Open Host Service): Policies has several consumers, so it publishes a standard API/events for all of them.
- Policies ↔ Claims (Partnership): both evolve together and depend on each other (a claim needs the active policy; the policy reflects the claims experience), so their teams coordinate changes jointly.
This map allows the organization to see at a glance where the critical dependencies are, where protection is needed (ACL), and where it is advisable to stabilize contracts (OHS).
Common Mistakes and Tips
- Not having a Context Map. Without it, integrations grow chaotically and no one knows who depends on whom. Draw it and keep it updated.
- Being a Conformist out of laziness when the external model is bad. Adopting a defective model as-is contaminates yours. If the external model is ugly or unstable, invest in an ACL.
- Sharing a database between contexts. It is the worst "pattern": it couples the contexts through the DB and breaks their boundaries. Integrate them via API or events, never through shared tables.
- A Shared Kernel that is too large. The more you share, the more coordination you need. Keep the shared kernel minimal and very stable, or don't use it.
- Not versioning the Published Language. If you change the contract without a version, you break all the consumers at once. Always version it (V1, V2...).
- Tip: the relationships in the Context Map are both technical and organizational. Before choosing a pattern, ask yourself which team has the power and the willingness to collaborate; that usually decides between Customer-Supplier, Conformist, or ACL.
Exercises
Exercise 1. Your context must integrate with the API of a large external payment provider that will not adapt anything to you, but whose model is reasonably clean. Which pattern do you choose and why? And what if the model were chaotic?
Exercise 2. Two teams share a "tax calculation" module that both modify constantly and that causes frequent conflicts. Identify which pattern they are using and propose a healthier alternative.
Exercise 3. For each relationship, indicate which context is upstream and which is downstream: (a) a Reports context that reads data from the Sales context; (b) a Catalog Open Host Service consumed by Orders.
Solutions
Solution 1. If the provider's model is clean and it does not negotiate, the natural pattern is Conformist: you adopt its model as-is because it is acceptable and translating it would not pay off. If the model were chaotic or unstable, the right choice is an Anti-Corruption Layer that isolates your domain from that model and protects you from its changes.
Solution 2. They are using a Shared Kernel that is too large and volatile, hence the constant conflicts. Healthier alternatives: turn the tax calculation into its own context with an Open Host Service that both consume, or assign ownership to one team (upstream) and have the other consume it as Customer-Supplier. This eliminates the need to modify shared code at the same time.
Solution 3. (a) Sales is upstream (provides the data); Reports is downstream (depends on it). (b) Catalog is upstream (publishes the OHS); Orders is downstream (consumes the service).
Conclusion
With this lesson we close the Domain-Driven Design module. We have seen that the Context Map reveals how the Bounded Contexts relate to each other and that those relationships are both technical and organizational. We have studied the key patterns—Partnership, Shared Kernel, Customer-Supplier, Conformist, ACL, and Open Host Service—and the decisive difference between conforming to a foreign model (Conformist) and protecting yourself from it with a translation layer (ACL). Finally, we have composed a complete Context Map that integrates all the patterns.
With the four pillars of the module—fundamental concepts, strategic design, tactical design, and context mapping—you now have a complete framework for tackling complex domains. The next natural step in the Application Architecture course is to see how these contexts and models materialize into concrete architectural styles, such as hexagonal architecture and event-driven architectures, which put into practice everything learned here.
Application Architecture Course
Module 1: Fundamentals of Application Architecture
- What Is Application Architecture?
- The Role of the Software Architect
- Quality Attributes and Non-Functional Requirements
- Architectural Decisions and Trade-offs
- Architecture Documentation: Views and the C4 Model
Module 2: Design Principles and Tactics
- Coupling, Cohesion and Separation of Concerns
- SOLID Principles Applied to Architecture
- DRY, KISS, YAGNI and Other Design Principles
- Architectural Tactics for Quality Attributes
- Managing Technical Debt
Module 3: Architectural Styles and Patterns
- Monolithic Architecture
- Layered Architecture (N-Tier)
- Client-Server Architecture
- Hexagonal Architecture (Ports and Adapters)
- Clean and Onion Architecture
Module 4: Distributed Architectures and Microservices
- Introduction to Distributed Systems
- Microservices Architecture
- Service Decomposition and Bounded Contexts
- API Gateway, Service Discovery and Inter-Service Communication
- Resilience Patterns: Circuit Breaker, Retry and Bulkhead
- The CAP Theorem and Data Consistency
Module 5: Event-Driven Architectures and Messaging
- Fundamentals of Event-Driven Architecture
- Asynchronous Messaging: Queues and Brokers
- Event Patterns: Event Sourcing and CQRS
- Managing Distributed Transactions: The Saga Pattern
- Real-Time Data Streaming
Module 6: Domain-Driven Design (DDD)
- Core DDD Concepts
- Strategic Design: Bounded Contexts and Ubiquitous Language
- Tactical Design: Entities, Aggregates and Repositories
- Context Mapping
Module 7: Data and Persistence
- Persistence Strategies: SQL vs NoSQL
- Data Access Patterns: Repository, Unit of Work and DAO
- Database per Service and Distributed Data Management
- Caching and Invalidation Strategies
Module 8: Cloud Architecture and Deployment
- Cloud Computing Fundamentals (IaaS, PaaS, SaaS)
- Containers and Orchestration with Docker and Kubernetes
- Serverless Architecture
- Cloud-Native Design Patterns
- Infrastructure as Code (IaC)
Module 9: Quality, Security and Observability
- Scalability: Horizontal vs Vertical and Load Balancing
- High Availability and Fault Tolerance
- Security by Design and Authentication/Authorization
- Observability: Logging, Metrics and Tracing
- Performance and Load Testing
