The central strategic pattern
A Bounded Context is an explicit boundary inside which a particular model — and the Ubiquitous Language that goes with it — applies consistently and without ambiguity. It is, without much competition, the single most important pattern in strategic DDD: get your context boundaries right and a confusing system becomes legible; get them wrong (or skip drawing them at all) and no amount of careful class design inside will save you.
The defining trick of a Bounded Context is this: the same word can mean genuinely different things in different contexts, and that’s not a bug — it’s the model working correctly. In the bookstore, Customer in the Sales context is “whoever placed this order” — identified, with a default shipping address, nothing more. If the bookstore later added a marketing context, Customer there might be a prospect with an email-engagement score and a list of abandoned carts — a completely different shape, different fields, different rules, same English word. Trying to force both into one shared Customer class is precisely the mistake Bounded Contexts exist to prevent: it produces a class bloated with fields half the codebase doesn’t need, owned by no one in particular, that breaks in one context every time it’s changed for another.
A Bounded Context is a solution-space boundary — how you've chosen to organize the software. A subdomain is a problem-space boundary — how the business itself is shaped. In a tidy design they line up one-to-one. In real systems, they often don't, and noticing where they diverge is itself useful information.
Where the bookstore’s contexts land
This course treats the bookstore as three Bounded Contexts, matching the three areas introduced in Module 1:
Sales— placing orders, theOrderaggregate, the core domain.Billing— generating and tracking invoices once an order exists.Shipping— turning an order into one or more shipments and getting them to the customer.
Each owns its own version of any shared-sounding word. Order is referenced in Billing and Shipping, but neither owns it or holds the full Order object — they hold an OrderId and react to facts Sales publishes (you’ll see exactly how in Module 5 and again with Domain Events in Module 8). That’s the discipline: a context can know about another context’s concepts, but it shouldn’t absorb them.
Heuristics for finding the boundary
Real codebases rarely have a label that says “context boundary here.” You infer it, in roughly this priority order:
- Deployment and module boundaries. Separate services, separate Maven/Gradle modules, or distinct top-level Java packages (
com.bookstore.sales,com.bookstore.billing) are the strongest signal you’ll get. If someone already drew a line, take it seriously before second-guessing it. - The same name, different shapes. If
Customerexists in two packages with different fields and different methods, you’ve found two contexts meeting — that’s theCustomerexample above, made concrete. - Distinct persistence. Separate schemas, separate datasources, or sets of
@Entityclasses that never reference each other’s tables, suggest the data itself has already been partitioned along context lines. - Translation code at the edges. A mapper, adapter, or façade that converts one package’s types into another’s is a strong tell — nobody writes a translator between two parts of the same model. (This is also your first hint of an Anticorruption Layer, the subject of Module 5.)
- Cohesion of language. A cluster of types that constantly reference each other and share vocabulary, with only sparse references pointing outward, behaves like one context even without a clean module boundary to prove it.
For each context you find, it’s worth writing down its name, the module(s) it lives in, its core concepts, and the dominant vocabulary it speaks — this is the glossary from Module 2, scoped to one boundary.
Note. Signal 1 (module boundaries) and signal 2 (repeated names) sometimes disagree — you’ll find
Customerduplicated inside what looks like a single package. Trust the language cohesion over the folder structure when that happens; packages get reorganized for all kinds of non-domain reasons, but a word genuinely meaning two different things doesn’t lie.
A common mistake: one context per database table
It’s tempting to let your ORM decide your contexts for you — one @Entity, one table, one “context.” Resist this. A Bounded Context is about where a model and its language hold consistently, not about how data happens to be normalized. OrderLine lives inside the Order aggregate in the Sales context even though it might have its own table; it doesn’t become a context of its own just because it’s persisted separately (you’ll see exactly why when Aggregates are introduced in Module 7). Boundaries are about meaning, not about schema.
With the contexts identified, the next question is unavoidable: how do Sales, Billing, and Shipping actually talk to each other, and who’s in charge of the shared facts between them? That’s Context Mapping.
Check yourself. Answer each one before you open it — the point is to catch the places where the module read as obvious but didn’t stick.
Customer in Sales has an id and a shipping address. Customer in a marketing context has an engagement score and abandoned carts. Which one is wrong?
Neither — this is the pattern working. The mistake would be merging them into one shared Customer class, which produces a type bloated with fields half the codebase ignores, owned by nobody, that breaks in one context every time it changes for another.
The package structure suggests one context, but you find Customer genuinely meaning two different things inside it. Which signal wins?
Language cohesion. Packages get reorganised for build reasons, team reasons, and historical accident; a word that actually means two different things doesn’t lie.
OrderLine has its own database table. Does that make it a Bounded Context?
No. A Bounded Context is the region where one model and one language hold consistently — it’s about meaning, not about how the data happens to be normalised. Letting the ORM draw your context boundaries is one of the reliable ways to get them wrong.
Next: Module 5 — Context Mapping.