Lesson 1 of 2
Bounded Contexts and the Language That Reveals Them
The same word means different things to different parts of a business. Those differences are not sloppiness to be tidied away — they are where the boundaries are.
Domain-driven design, from Eric Evans' 2003 book, is often reduced to a catalogue of object patterns. That is the smaller half. The half that changes systems is strategic: the claim that a large domain cannot be modelled by one consistent model, and that trying is the root cause of the unmaintainable enterprise application.
The clearest way to find contexts is to listen for a word that different groups use differently. In a logistics business, "shipment" means a customer promise to sales, a set of physical parcels to the warehouse, a customs declaration to compliance, and a line item to finance. A single `Shipment` class serving all four ends up with sixty fields, of which each caller uses eight, and every change to it requires four teams to agree.
One word, four models
The differences are the design information. Flattening them into one class is how a system becomes unchangeable.
The word "shipment" at the centre, with four bounded contexts around it. Sales means a delivery promise with a date and a customer. Warehouse means parcels, weights and a pick list. Compliance means a customs declaration with commodity codes. Finance means a revenue line with a cost allocation. Each context has its own model and its own identifier, related by translation at the boundaries.
Sales context
Delivery promise
date, customer, SLA
Warehouse context
Parcels and pick list
weight, location
Compliance context
Customs declaration
commodity codes, value
Finance context
Revenue line
cost allocation, margin
// Before: the business rule is invisible, and no domain expert can read it.
policy.status = 4;
policy.statusChangedAt = new Date();
if (policy.premiumsPaid < policy.premiumsDue) policy.flagged = true;
// After: the code says what the business says, and the rule lives in one place.
const lapsed = policy.lapse(clock.now(), LapseReason.NonPayment);
// Inside the entity:
lapse(at: Date, reason: LapseReason): Policy {
if (this.status === PolicyStatus.Cancelled) {
throw new DomainError('A cancelled policy cannot lapse');
}
return new Policy({ ...this.state, status: PolicyStatus.Lapsed, lapsedAt: at, lapseReason: reason });
}Context mapping: naming the relationship
Once you have several contexts, the relationships between them need naming, because each relationship is a different commitment about who accommodates whom. This is context mapping, and it is as much an organisational statement as a technical one.
| Relationship | What it means | Commits you to |
|---|---|---|
| Shared kernel | Two contexts share a small model deliberately | Joint ownership: neither may change it alone |
| Customer–supplier | The downstream context's needs influence the upstream's roadmap | Upstream accepting requirements and a negotiated contract |
| Conformist | The downstream adopts the upstream's model as-is | Living with a model that does not fit, in exchange for no translation work |
| Anti-corruption layer | The downstream translates the upstream's model into its own | Building and maintaining a translation layer, and keeping your model clean |
| Published language | Both sides integrate through a documented, versioned schema | Governing that schema and its deprecation path |
| Separate ways | The contexts do not integrate; duplication is accepted | Accepting inconsistency, deliberately, where integration costs more than it saves |
In practice
Finding contexts by listening
An insurer's claims platform had one `Claim` class with 84 fields. Four teams contributed to it, every release required coordination, and a change to the fraud-scoring fields had recently broken the customer-facing claim tracker.
Constraints
- Four teams: intake, assessment, fraud, payments
- The 84-field class is used by all four
- No appetite to stop feature work for a restructure
- Regulatory reporting reads the class directly
Decision
Run a language workshop with the four teams and the domain experts. The word "claim" turned out to mean four different things, and the fields partitioned almost cleanly. Split into four contexts, each with its own model and its own tables, integrating through published events and one anti-corruption layer against the regulatory reporting format.
Why
The class had become the union of four models, which is why it had 84 fields and why any change could break an unrelated team. The workshop was the cheapest possible diagnostic: two days of conversation revealed the boundaries that two years of refactoring had failed to find, because the boundaries were in the language rather than in the code.
What it cost
Claim identity now exists four times, correlated by a shared claim reference, and answering "everything about claim 4471" requires four queries instead of one — a real loss for support staff, which they mitigated with a dedicated aggregating view. Coordination between the four teams fell from every release to roughly once a quarter, when a published event schema changes.
Two teams are arguing about whether a `Customer` needs a `creditLimit` field. Marketing says no, finance says yes. How does bounded-context thinking resolve this?RevealHide
It dissolves the argument rather than settling it. They are talking about two different concepts that share a word: marketing's customer is a person who receives communications and has preferences and a consent state, while finance's customer is a legal entity with a credit limit, a payment history and a tax status. Neither needs the other's fields. Each context gets its own model, and they are correlated by a shared identifier rather than merged into one class. The tell that you are in this situation is exactly this argument — a debate about whether a field belongs on a shared model is almost always evidence that two models are being forced into one, and the resolution is to stop asking which team is right.
Key takeaways
- A bounded context is a boundary inside which one word means exactly one thing.
- Language differences between groups are design information, not sloppiness to standardise away.
- A single enterprise-wide model of a core concept reliably fails; several sharp models with translation succeed.
- Name each context relationship — the commitment differs and so does who accommodates whom.
- An argument about whether a field belongs on a shared model usually means two contexts are being merged.