Oh My Learning

Bounded Contexts And Autonomy Boundaries

learning-ddd/03 drafted · 9 min read

Bounded contexts and autonomy boundaries#

TL;DR#

A bounded context is an explicit boundary - both linguistic and organizational - inside which a single model, with a single consistent meaning for its terms, applies. Outside that boundary, the same word can (and often should) mean something different. Bounded contexts are the solution-space answer to the problem-space subdomains identified in learning-ddd/02: they are where a team draws the actual lines a codebase and its data will respect.

The idea#

Language is where most modeling failures start. The word "Customer" means something different to Sales (a lead with a name, a company, and a pipeline stage), to Billing (an account with a payment method, an invoice history, and a credit limit), and to Support (a person with a ticket history and an entitlement level). If a team tries to build one unified Customer class that satisfies all three departments, it accretes fields nobody in any single department fully understands, gets bloated with conditional logic ("if this customer is being billed, use these fields; if it's a support context, use those"), and becomes fragile - a change made for Billing's needs breaks an assumption Support was relying on.

A bounded context solves this by drawing an explicit boundary: inside the boundary, "Customer" has exactly one meaning, defined by one model, spoken in one ubiquitous language (learning-ddd/05). Outside the boundary, in a different bounded context, "Customer" can mean something else entirely, modeled independently. The word is allowed to be polysemic (multiple meanings) across the whole system precisely because each individual context keeps it monosemic (one meaning) within its own walls. This is the core autonomy guarantee: a team owning a bounded context can evolve its model, its data, and its code without needing every other team to agree on a shared, universal definition first.

Bounded contexts typically - but not always - map closely to subdomains (learning-ddd/02): a core subdomain often becomes its own bounded context so it can be modeled with maximum precision and evolved independently; several small, related supporting or generic subdomains might be combined into one bounded context for practical reasons (team size, deployment cost) even though they are conceptually distinct in the problem space. The mapping is a design decision, not an automatic derivation.

How it works#

Boundary by language, not by table or by team org chart (though those often follow)#

The defining test for whether two things belong in the same bounded context is: does the same term mean the same thing, and does the same rule apply, on both sides? If "Order" in the warehouse-fulfillment context means "a physical pick-and-pack task with a location and a packer" and "Order" in the sales-reporting context means "a completed revenue-recognized transaction with a fiscal period," those are two different concepts wearing the same English word - a signal they belong in two different bounded contexts, each with its own model of what an "Order" is, even though both ultimately trace back to "a customer bought something."

Worked example - e-commerce, the word "Product"#

  • In the Catalog context: a Product has a title, description, images, categories, and SEO metadata. Its lifecycle is about content quality and discoverability.
  • In the Inventory context: a Product (often renamed SKU or StockItem within this context's own language) has a quantity on hand, a reorder threshold, and warehouse locations. Its lifecycle is about physical stock movement.
  • In the Pricing context: a Product has a base price, active promotions, and a currency. Its lifecycle is about price changes and promotional windows.

Each context models "the same real-world thing" completely differently, using only the attributes and behaviors relevant to its own concerns, and each can evolve its model - adding a reorderThreshold field to Inventory's model, say - without touching Catalog's or Pricing's code, database, or deployment. This independence is the entire point: bounded contexts are what let multiple teams (or one team wearing multiple hats) move at their own pace without constant cross-team negotiation over a single shared "Product" table.

Worked example - SaaS billing, the word "Plan"#

In a Subscription Management context, a Plan is a commercial offering: tier name, price, billing interval, and which features it unlocks. In a Usage Metering context, the same idea is represented as a RateSchedule: a set of per-unit prices and thresholds used purely for the arithmetic of computing an invoice, with no notion of "tier name" or marketing copy at all. Trying to force these into one shared Plan class means Usage Metering's fast-changing rate-calculation logic keeps needing schema changes agreed upon with the Subscription team, and vice versa - the exact coordination tax bounded contexts exist to eliminate.

Worked example - logistics, the word "Shipment"#

A Route Planning context models a Shipment as a sequence of stops with time windows and vehicle-capacity constraints - relevant to optimizing driver routes. A Customer Notifications context models the same real-world shipment as a simple status timeline ("picked up," "in transit," "delivered") with a single tracking-number lookup - relevant to sending the right SMS at the right time. Neither model needs (or should carry) the other's complexity: Notifications does not need vehicle-capacity constraints, and Route Planning does not need SMS copy templates.

Drawing the boundary: signals to look for#

  • Divergent vocabulary for the same real-world entity across departments or teams (the clearest, most reliable signal - see the examples above).
  • Different rates of change - a core, fast-iterating subdomain (learning-ddd/02) benefits from its own bounded context so its model can evolve without dragging a slower, more stable supporting subdomain's release cadence along with it.
  • Different consistency needs (elaborated in learning-ddd/10) - if one part of the system needs strict transactional consistency and another can tolerate eventual consistency, that's often a sign they belong in separate contexts.
  • Team/organizational boundaries (Conway's Law, elaborated fully in learning-ddd/14) - a bounded context is easiest to keep coherent when a single team owns it end to end; if two teams must constantly negotiate changes to "the same" model, that friction is itself diagnostic.

What crossing a boundary looks like#

Once contexts are drawn, any communication between them happens deliberately - never through silently sharing a database table or a domain class. learning-ddd/04 catalogs the specific relationship patterns (Partnership, Customer-Supplier, Conformist, Anticorruption Layer, and others) that govern how contexts talk to each other, and learning-ddd/11 covers the concrete integration mechanisms (synchronous APIs, async messaging, events).

Pros#

  • Eliminates the "one model to rule them all" trap, where a shared domain model accretes complexity and cross-team coupling until every change requires a company-wide meeting.
  • Enables genuine team autonomy: a team owning a bounded context can change its internal model, refactor its database schema, and deploy independently, as long as it honors its published contract to other contexts.
  • Makes polysemic business language explicit and safe instead of a silent source of miscommunication and bugs.
  • Provides a principled unit for service/deployment boundaries later (learning-ddd/12) - though the boundary is conceptual first, physical second.

Cons#

  • Drawing boundaries well requires real domain knowledge and iteration; a boundary drawn too early, before the team understands the domain, tends to be wrong and expensive to redraw once code, data, and teams have grown around it.
  • Too many small bounded contexts (over-fragmentation) recreates a distributed-systems version of the tight-coupling problem it was meant to solve - excessive cross-context calls, chatty integration, and duplicated logic.
  • Too few, too-large bounded contexts (under-fragmentation) reintroduces the "one shared model" problem inside a single context's boundary.
  • Boundaries are not free - each one needs an explicit contract, translation logic at the edges (learning-ddd/04's Anticorruption Layer), and ongoing maintenance of that contract as both sides evolve.

Alternatives#

  • A single shared domain model for the whole system - simpler to reason about initially, and viable for genuinely small systems or a small supporting/generic subdomain cluster, but breaks down as team size and business complexity grow, reproducing the exact problem this lesson opens with.
  • Database-table-boundary as the de facto context boundary - common in practice by accident rather than design; risks drawing boundaries around accidental technical structure (how the data happens to be stored) rather than around actual linguistic/conceptual divergence, which is a much weaker signal.
  • Microservice-per-team without explicit modeling - organizationally similar in spirit but skips the linguistic analysis that makes a bounded context's boundary actually correct; can produce services that are technically separate but conceptually still tangled (shared ambiguous vocabulary smuggled across the API).
  • ddd-evans's original Bounded Context definition - Evans introduced the term and the core insight; Khononov's contribution here is connecting it explicitly back to the subdomain classification of learning-ddd/02 and forward to the relationship-pattern catalog of learning-ddd/04, making the concept more actionable for teams starting fresh.

When to use it#

Use bounded contexts whenever a system serves multiple distinct business capabilities with genuinely different vocabularies, rates of change, or ownership - which in practice means almost any system beyond a small, single-team application. Draw the first boundaries around your core subdomain(s) from learning-ddd/02, since that's where getting the model right matters most and where a shared, ambiguous model would do the most damage.

When NOT to use it#

Don't introduce multiple bounded contexts for a small, single-team, low-complexity system where the vocabulary genuinely is shared and consistent everywhere - the ceremony of maintaining explicit contracts between contexts has a real cost, and a single well-organized model can be entirely appropriate for a system that hasn't yet grown enough business complexity to need splitting. Revisit the decision as the system and team grow, rather than pre-splitting a small system "for future scale."

Key takeaways / mental model#

Ask, for any term used in two different parts of a system: "if I asked a domain expert from each area to define this word in one sentence, would I get the same sentence?" If not, that's a bounded context boundary waiting to be drawn - and the two "different" concepts sharing one English word should become two separate models, each internally consistent, connected only through an explicit, designed relationship (learning-ddd/04).

Self-check questions#

  1. Find a word in a system you know that means something subtly different in two different parts of the codebase. Was that difference handled explicitly (separate models) or implicitly (one shared model with conditional logic)? What problems did the implicit handling cause?
  2. Why does Khononov emphasize that a bounded context boundary is primarily linguistic, not primarily technical (a service boundary or a database boundary)?
  3. Explain the relationship between a subdomain (learning-ddd/02) and a bounded context: why don't they always map one-to-one?
  4. What signals would tell you a set of bounded contexts has been drawn too finely (over-fragmented)? What would you observe in day-to-day development?

References#

  • Learning Domain-Driven Design (Vlad Khononov), Part I, Chapter 3: "Strategic Design: Bounded Contexts".
  • Domain-Driven Design (Eric Evans, 2003), Part III, "Bounded Context" - see domain-modeling/ddd-evans.