12 · Bounded contexts
When you finish this lesson you should be able to:
- Define a bounded context vs a module
- Draw a context map with an honest relationship on every arrow
- Choose shared kernel, customer/supplier, conformist, ACL, separate ways, open host, or published language on purpose
- Stop “one company model” as a slogan
Integrity is the problem
Section titled “Integrity is the problem”Once more than one team or system exists, there is no single model in anyone’s head. If you pretend there is, you get:
- the same word, different rules
- a database everyone is afraid to migrate
- translation scattered through controllers
Model integrity: inside a boundary, names and rules are consistent and match the code. Across boundaries, you translate, visibly.
Bounded context
Section titled “Bounded context”A bounded context delimits where a model applies. Team members know what must be consistent, and how this model relates to others. Inside: keep it logically unified. Outside: other models, other dialects of the language. Integration requires translation you can analyze.
Evans’s booking example in the book is the same shape as Identity.Account vs Billing.Account. Draw the line. Name both.
Contexts are not modules
Section titled “Contexts are not modules”Modules organize one model (namespaces, cohesion). Contexts separate languages. Putting two models in two folders without saying they are different contexts just hides the fork. Putting one context in many modules is normal.
If two squads never integrate but share a package named domain, you already have two contexts and a lie.
Continuous integration (inside a context)
Section titled “Continuous integration (inside a context)”Merge often so the model does not fragment inside the boundary. This is cheaper than a translation layer to yourself. If the team is too big to CI the model, split the context (and the team) rather than fake a kernel of everything.
Context map
Section titled “Context map”The most useful diagram in a large org: contexts as boxes, relationships as labeled arrows. Without it, every meeting assumes a global language.
Update it when a breakthrough happens. A six-month-old map is a wish.
Relationships (the catalog)
Section titled “Relationships (the catalog)”| Pattern | When | Cost |
|---|---|---|
| Shared kernel | Tiny shared subset both can CI | Political + coordination; keep ruthless small |
| Customer/supplier | Downstream needs things from upstream | Negotiation; downstream is a customer |
| Conformist | Downstream accepts upstream’s model | Honest; your language is theirs |
| Anticorruption layer | You must integrate with a mess / a foreign model | Translation code; protects your core |
| Separate ways | Integration costs more than duplication | Two products; no fake sharing |
| Open host service | Many consumers | You maintain a protocol |
| Published language | Shared documents/payloads with semantics | Versioning; worth it if many conform |
Unifying the elephant: adding columns until every team is sort of happy. Nobody can speak the result. Prefer a map.
Transformations: contexts split and merge. Plan for it.
Anticorruption layer (use this more than shared kernel)
Section titled “Anticorruption layer (use this more than shared kernel)”A facade in your language. Three kinds of operation: toOurs, toTheirs, dropIfMeaningless. Your core never imports their schema. Legacy systems almost always deserve an ACL. “We’ll just use their User DTO” is conformist by accident.
Shared kernel (use this less)
Section titled “Shared kernel (use this less)”A carefully selected subset. Not the whole common package. If the kernel is “all entities,” you did not split.
Example
Section titled “Example”flowchart LR identity[Identity] exp[Experiments] msg[Messaging] billing[Billing] identity -->|"published userId"| exp identity -->|"published userId"| msg msg -->|"ACL on billing events"| billing
- Experiments conform to
userId; they do not store passwords. - Messaging’s ACL turns
InvoicePastDueinto something in messaging language—or ignores it. - Growth and Finance editing one
userstable is not a kernel. DeclareUserId+ nothing else, or split.
Drill (25 minutes)
Section titled “Drill (25 minutes)”- Three contexts. One homonym. Both definitions.
- Four arrows, each labeled from the table. No unlabeled “calls.”
- Worst integration: design an ACL with three functions.
- List what you stop sharing (tables, enums,
common). - If you proposed a kernel, list the exact types. If more than a handful, you are faking one context.
Further reading
Section titled “Further reading”- Domain-Driven Design — Chapter 14, Maintaining Model Integrity (bounded context; CI; context map; the relationship catalog)