Start Learning
Javaneer
Back to stage
Stage 2Β·Domain-Driven Design Lite

Bounded Contexts

The strategic heart of DDD: the same word ('account', 'customer') means different things in different parts of the business - draw boundaries and map the relationships between them.

15 min readAdvanced
On this page

Everything so far - entities, value objects, aggregates, events - is tactical DDD, about designing objects. This lesson is the strategic heart, and it's the idea most worth taking away even if you use nothing else: the bounded context. A single, unified model of the whole business is a fantasy that produces bloated, confused code. The reality is that the same word means different things in different parts of the business, and each part deserves its own model with its own boundary.

One word, many meanings

Consider "Customer" across ledger-legacy's business:

  • To Sales, a Customer is a lead: contact details, pipeline stage, likelihood to convert.
  • To Billing, a Customer is a payer: billing address, payment methods, credit limit, outstanding balance.
  • To Support, a Customer is a ticket-holder: contact history, entitlements, satisfaction score.

Trying to build one Customer class serving all three produces a monster with forty fields, half null for any given use, and change requests from three teams colliding in one file. The concepts share a name but are genuinely different models. Forcing them together is the original sin of enterprise modeling.

The bounded context

A bounded context is an explicit boundary within which a particular model - and its ubiquitous language - is consistent and complete. Inside the Billing context, "Customer" means precisely the payer model, and everyone (code and people) uses it that way. Cross into Sales, and "Customer" legitimately means something else.

β”Œβ”€β”€ Sales Context ──────┐   β”Œβ”€β”€ Billing Context ─────┐   β”Œβ”€β”€ Support Context ────┐
β”‚  Customer = lead      β”‚   β”‚  Customer = payer      β”‚   β”‚  Customer = ticket    β”‚
β”‚  (pipeline, contact)  β”‚   β”‚  (credit, invoices)    β”‚   β”‚   holder (history)    β”‚
β”‚  ubiquitous language  β”‚   β”‚  its OWN language       β”‚   β”‚  its own language     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Each context has its own model, its own database schema (often), and its own team. This is precisely why the "ubiquitous language" is ubiquitous only within a context, as flagged earlier - a context is the scope where a language is unambiguous. Bounded contexts are also the natural seams for microservices: a well-drawn context boundary is a candidate service boundary (recall the Spring microservices module).

Context mapping

Contexts aren't islands - they relate, and DDD names the patterns for how (context mapping):

  • Shared Kernel - two contexts share a small common model (risky; needs tight coordination).
  • Customer/Supplier - a downstream context depends on an upstream one, which agrees to its needs.
  • Conformist - downstream just accepts the upstream model as-is (no leverage to negotiate).
  • Anticorruption Layer (ACL) - the crucial one: a downstream context builds a translation layer that converts the upstream model into its own terms, so a foreign or legacy model doesn't leak in and corrupt the clean one.

The anticorruption layer is a senior favorite: when integrating with ledger-legacy's messy model (or any third-party system), you don't let its concepts contaminate your new clean context - you translate at the border, exactly like the Adapter pattern applied at the architectural scale.

Draw contexts around language, teams, and change

How do you find context boundaries? Listen for where the same word shifts meaning, or where a term needs a qualifier ('billing customer' vs 'sales lead'). Watch where different teams own different rules. Notice where parts of the system change for different reasons and at different rates. Those seams are your bounded contexts - and getting them right matters far more than any tactical pattern, because a wrong boundary couples teams and models that should be independent.

The word 'account' at a bank, a gym, and Twitter

Say 'account' to a banker (a balance and transactions), a gym receptionist (a membership and access rights), and a social-media user (a handle and followers) - three completely different things wearing one word. No sane person builds a single universal 'Account' concept spanning all three; each domain has its own, and that's correct. Bounded contexts formalize this obvious truth for a single business: your company's 'customer' or 'order' also fractures into distinct meanings across departments, and the boundary is where one meaning ends and another begins. An anticorruption layer is the interpreter at the border who translates the banker's 'account' into the gym's terms so neither has to adopt the other's language.

Split the god model

ledger-legacy has one Product class with 35 fields serving three teams: the Catalog team (name, description, images, category), the Pricing team (base price, discounts, tax class), and the Warehouse team (SKU, shelf location, stock count, dimensions). Changes from any team risk breaking the others. Apply strategic DDD, and describe how the three resulting models refer to 'the same product.'

What is a bounded context in DDD?

Key takeaways

  • Strategic DDD's core idea: a single unified model of the whole business fails; the same word means different things in different parts.
  • A bounded context is an explicit boundary within which one model and its ubiquitous language are consistent and complete.
  • Different contexts model the same term differently (Customer = lead / payer / ticket-holder) and often have separate schemas and teams.
  • Context mapping names how contexts relate; the anticorruption layer translates a foreign/legacy model at the border so it can't corrupt a clean context.
  • Find boundaries where words shift meaning, teams differ, or parts change for different reasons - and these seams are natural microservice boundaries.
Was this lesson helpful?
Edit this page on GitHub