50%

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.

11 min read

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

The same operation before and after the language of the domain reaches the code.typescript
// 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.

RelationshipWhat it meansCommits you to
Shared kernelTwo contexts share a small model deliberatelyJoint ownership: neither may change it alone
Customer–supplierThe downstream context's needs influence the upstream's roadmapUpstream accepting requirements and a negotiated contract
ConformistThe downstream adopts the upstream's model as-isLiving with a model that does not fit, in exchange for no translation work
Anti-corruption layerThe downstream translates the upstream's model into its ownBuilding and maintaining a translation layer, and keeping your model clean
Published languageBoth sides integrate through a documented, versioned schemaGoverning that schema and its deprecation path
Separate waysThe contexts do not integrate; duplication is acceptedAccepting inconsistency, deliberately, where integration costs more than it saves
The relationships you will actually use, and what each commits you to.

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?Reveal

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.