Home/Learn/Microservices/Bounded Contexts

Bounded Contexts

Intermediate
Fundamentals

A bounded context defines the boundary within which a particular domain model is valid and consistent; each microservice ideally maps to one bounded context.

Overview

A Bounded Context is the central DDD (Domain-Driven Design) pattern for defining service boundaries. It is a logical boundary within which a specific domain model — its entities, value objects, aggregates, and business rules — is valid and internally consistent. The same word can mean different things across different bounded contexts: a "Customer" in the Order context might be just an ID and shipping address, while in the CRM context it is a rich object with interaction history and credit score. Bounded contexts communicate via well-defined APIs or domain events, and each context owns its data. In a microservices architecture, each service ideally maps to one bounded context. Getting boundaries wrong — either too tight (nanoservices) or too loose (distributed monolith) — is the primary cause of microservices complexity.

The Ubiquitous Language — Same Word, Different Meanings

Within a bounded context, the team uses a ubiquitous language — a shared vocabulary that is consistent between code and business conversation. The entity names, method names, event names, and database tables all use the same business terms.

The critical insight: the same term can have radically different meanings across contexts. An "Order" in the Fulfilment context cares about warehouse bins and shipping labels; an "Order" in the Finance context cares about invoices and revenue recognition. Forcing both meanings into one shared model creates an anemic, over-loaded entity that serves no context well. The solution is to maintain separate models with a translation layer (Anti-Corruption Layer) between them.

Java — Same Concept, Different Contexts
// ── Order context — cares about fulfilment ──────────────────────
package com.example.fulfilment.domain;

@Entity
public class Order {
    private String orderId;
    private String warehouseId;       // fulfilment-specific
    private List<PickItem> pickList;  // fulfilment-specific
    private ShippingLabel label;      // fulfilment-specific
    private FulfilmentStatus status;  // PICKING, PACKED, SHIPPED
}

// ── Finance context — cares about billing ────────────────────────
package com.example.finance.domain;

@Entity
public class Order {
    private String orderId;
    private BigDecimal   netAmount;        // finance-specific
    private BigDecimal   taxAmount;        // finance-specific
    private String       invoiceId;        // finance-specific
    private RevenueStatus revenueStatus;   // DEFERRED, RECOGNISED
}

// Same concept, different bounded contexts → different models
// Communication: Fulfilment publishes OrderShippedEvent
//                Finance subscribes and recognises revenue

Context Map — Relationships Between Bounded Contexts

A context map documents how bounded contexts integrate. Key relationship patterns:

**Shared Kernel** — two contexts share a subset of the domain model (dangerous, couples teams).

**Customer/Supplier** — downstream (customer) consumes what upstream (supplier) provides; upstream must not break the downstream.

**Anti-Corruption Layer (ACL)** — a translation layer that converts the upstream model into the local model, protecting your domain from external concepts leaking in.

**Published Language** — the upstream defines a public, stable schema (OpenAPI spec, Avro schema) that multiple downstreams consume without needing to understand the upstream internals.

Java — Anti-Corruption Layer
// Anti-Corruption Layer — translates external model to local model
// Fulfilment service consumes Order events from Order service
// but translates them to Fulfilment's own domain model

@Component
public class OrderEventTranslator {

    // Order service publishes: {"orderId":"123","lineItems":[...],"shippingAddress":{...}}
    // Fulfilment model needs: PickList with warehouseLocations

    public FulfilmentOrder translate(OrderPlacedEvent externalEvent) {
        // ACL: maps external schema → local domain model
        List<PickItem> pickItems = externalEvent.getLineItems().stream()
            .map(item -> PickItem.builder()
                .sku(item.getProductSku())
                .quantity(item.getQuantity())
                .warehouseLocation(warehouseService.locate(item.getProductSku()))
                .build())
            .collect(Collectors.toList());

        return FulfilmentOrder.builder()
            .orderId(externalEvent.getOrderId())
            .pickList(pickItems)
            .status(FulfilmentStatus.PENDING)
            .build();
    }
}

Bounded Context → Microservice Boundary

In practice, a bounded context maps to a microservice (or a module within a larger service). The boundary tells you: what data this service owns, what API it exposes, and what events it publishes/consumes. Wrong boundaries are the #1 source of microservices problems. Two failure modes:

**Too fine-grained (nanoservices)**: every method becomes a service; all calls are remote; latency and complexity skyrocket.

**Too coarse-grained (distributed monolith)**: services share databases, call each other synchronously for every operation, and cannot be deployed independently. All the cost of microservices with none of the benefits.

Java (comments) — Bounded Context Heuristics
// Signs of a well-bounded service:
// ✅ Owns its database — no other service reads/writes its tables directly
// ✅ Can be deployed independently without coordinating with other teams
// ✅ Has a small, stable API surface (OpenAPI spec fits on one page)
// ✅ Changes to its internals do not require changes in other services
// ✅ Business language is consistent internally (ubiquitous language)

// Signs of a poorly-bounded service (distributed monolith):
// ❌ Service A calls Service B synchronously for every request
// ❌ Two services share the same database/schema
// ❌ Deploying one service requires deploying 5 others simultaneously
// ❌ The "Order" entity is 50 fields serving 8 different use cases

// Starting heuristic: if a business capability can be described in one sentence
// and owned by one team, it is likely a good bounded context.
// Examples:
// - "Manages the lifecycle of customer orders from placement to payment" → Order + Payment context
// - "Tracks warehouse inventory and fulfils pick lists"  → Fulfilment context
// - "Manages customer profiles, preferences, and history" → CRM context

Key Points to Remember

  • 1A bounded context defines a boundary within which a domain model is valid; same business term can mean different things across contexts.
  • 2Each microservice should ideally map to exactly one bounded context — it owns its data and exposes a stable API.
  • 3Use an Anti-Corruption Layer (ACL) to translate external models into your local domain model — prevents coupling to other contexts.
  • 4Context map patterns: Shared Kernel (risky coupling), Customer/Supplier, ACL (isolation), Published Language (stable public schema).
  • 5Distributed monolith: services that share a database or require synchronous calls for every operation — worst of both worlds.
  • 6Start with fewer, larger contexts and split when a context grows too complex — wrong splits are costlier than delayed splits.

Interview Questions

Sign in to ask Aria
1

What is a bounded context and why is it important in microservices design?

EasyAmazon
2

What is an Anti-Corruption Layer and when would you use one?

MediumThoughtworks
3

What is a distributed monolith and how do you identify one?

MediumUber
4

How do you decide where to draw the boundary between two bounded contexts?

HardNetflix
5

The same word "Customer" appears in three different services with different meanings. Is this a problem? How do you handle it?

HardLinkedIn

Ask Aria about Bounded Contexts

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…