What you'll learn: By the end of this lesson you will be able to explain why one model cannot serve a whole large system, define ubiquitous language and bounded context the way Evans does, sort a domain into core, supporting and generic subdomains, choose a relationship between two contexts (partnership, shared kernel, customer/supplier, conformist, anti-corruption layer, open host service, published language, separate ways), and keep a context separate from a module or a service.
In a restaurant, "ready" means three different things. For the kitchen it means the dish is plated. For the host it means the table is cleaned and laid. For the cashier it means the bill is printed. Everyone uses the same word, nobody is wrong, and the restaurant works because each person knows which "ready" they are in. Trouble starts when a system built for the kitchen is asked to answer the host's question.
Software has the same problem at a larger scale. Architecture styles says to split along business boundaries, and ShopStream Stage 8 shows seven contexts and a context map. This lesson is the dedicated treatment of the idea underneath: what a bounded context is, how to find one, and how contexts relate. Turning contexts into services with their own data is in microservice decomposition and data ownership. Aggregates, entities and value objects (tactical design) have a planned lesson, not yet published.
Verified against Evans's Domain-Driven Design Reference (2015), Fowler's bliki pages and the Azure Architecture Center, on 2026-11-08. The Java example needs Java 16 or later and was run on JDK 26.0.1.
The core idea
Strategic design starts from one fact: a large system holds many models, and a model is only meaningful inside a boundary. DDD makes that boundary explicit and gives the language inside it one meaning. Evans defines a bounded context as a boundary, typically a subsystem or the work of one team, within which a particular model is defined and applicable. The rest of strategic design is deciding where to draw boundaries, where to invest, and how the contexts talk to each other.
Three illustrations run through the lesson, each labelled:
- Minimal example: two models of the same grocery item and a translation layer, in Java.
- Realistic example: ShopStream adding a store point-of-sale feed to Inventory, in "In practice".
- Failure example: one shared status type that every context must upgrade together.
One word, many models
Evans notes that multiple models are in play on any large project and that they emerge for many reasons: different user communities with different jobs, teams solving the same problem in different ways, different tool sets. They are inevitable, and combining code based on distinct models makes software buggy and hard to understand. Fowler adds, attributing the point to Evans, that total unification of the domain model for a large system will not be feasible or cost-effective, and that confusion recurs with words such as "Customer" and "Product".
In ShopStream the word to watch is available:
| Context | "Available" means | Decided by |
|---|---|---|
| Catalogue | The product is listed and can be shown | A merchandiser |
| Inventory | On hand minus reserved, at one store | Counts and reservations |
| Delivery | A courier has capacity in a time slot | Courier schedules |
| Payments | The funds or credit can be used | The payment provider |
Figure 1. The same word, four different meanings, each correct in its own context. How to read this: each box keeps its own definition. Contexts refer to each other by identifier and messages, never by sharing one class that tries to mean all four.
Language and the boundary
The ubiquitous language is, in Evans's definition, a language structured around the domain model and used by all team members within a bounded context to connect all the activities of the team with the software. The pattern asks the team to use the same words in speech, diagrams, writing and code, and to treat a change in the language as a change to the model: rename classes and methods when the words change. The language is local. A context has one, and the word means one thing inside it.
Evans says to set the boundary in terms of team organisation, usage within specific parts of the application, and physical manifestations such as code bases and database schemas, and to apply continuous integration inside it so model concepts stay consistent. He also notes that as few as three or four people in one context can see the model fragment. Fowler's summary of the ubiquitous language adds that it must be rigorous because software does not cope well with ambiguity, and that talking with domain experts is part of testing the model.
Signals that you have found a seam: the same word gets different answers from different experts; a class keeps growing fields that only one use case reads; two groups need different release rhythms or different rules for the same data. The Azure guidance suggests mapping business functions and their connections with domain experts, with event storming as one structured approach, then looking for closely related functions, core versus supporting functions, and the dependency graph between them.
Core, supporting and generic subdomains
Not every part of a domain deserves the same effort. Evans's core domain pattern says to define the critical core of the model, make it small, apply top talent to it, and justify investment in any other part by how it supports the core. His generic subdomains pattern says to factor out cohesive subdomains that are not the motivation for the project, give them lower priority, and consider off-the-shelf solutions. The Azure guidance names three categories:
| Kind | What it is | Typical treatment |
|---|---|---|
| Core | A competitive advantage that defines the business | Deep modelling, strongest team |
| Supporting | Needed to operate, but not a differentiator | Custom but simpler |
| Generic | A solved problem | Buy or reuse |
For ShopStream, one illustrative answer (each business would decide for itself): the delivery promise and stock accuracy are core, Orders and Notifications are supporting, and sign-in and card processing are generic. The consequence is practical: the most careful modelling goes where it pays, and the team does not hand-build a payment provider.
DDD is not free. Evans frames it as an approach for complex software and observes that many projects do modelling work without getting much benefit. A mostly create, read, update and delete (CRUD) area, such as a store's opening-hours page, rarely needs it.
Context maps: how contexts relate
A context map, in Evans's words, identifies each model in play, names its context, and describes the points of contact between models, with explicit translation for any communication. It maps the existing terrain first and leaves transformations for later. The relationships use two terms: upstream (its actions affect the downstream's success, but not the reverse) and downstream.
| Relationship | Evans's idea | ShopStream example (illustrative) |
|---|---|---|
| Partnership | Two teams that succeed or fail together plan and integrate jointly | Cart and Checkout with Orders: both ship for the same release |
| Shared kernel | A small subset of model and code the teams agree to share, changed only after consultation | A tiny shared Money type |
| Customer/supplier | Downstream needs enter upstream planning, with joint acceptance tests | Cart as customer of Catalogue's prices |
| Conformist | Downstream adopts the upstream model as given, which simplifies integration | Notifications taking Orders' events as published |
| Anticorruption layer | A translation layer that gives you the upstream's function in your own model | Inventory around the store POS feed |
| Open host service | A protocol open to everyone who needs to integrate | Inventory's stock API and events |
| Published language | A well-documented shared interchange language, often combined with an open host service | Versioned StockChanged schema |
| Separate ways | No integration at all, because the benefit is small | A seasonal promotions site with no link to stock |
| Big ball of mud | Draw a boundary around the mess and do not model it | The old back-office system, drawn as one box |
Figure 2. A way to pick a relationship. How to read this: the order of questions is a teaching device, not a rule from Evans. Start with whether to integrate at all. Translation is the strongest and most expensive protection, so it comes before the cooperative options.
Context, module and service are different things
A bounded context is a boundary for a model. A module and a service are ways to package code. The Azure guidance says a microservice's functionality generally should not span more than one bounded context, and its worked example has one context (Shipping) containing several services. Evans lists code bases and database schemas as physical manifestations of a boundary. In a modular monolith, a context is typically a module with its own schema. When it later becomes a service, the context is the same and only the packaging changes. Hexagonal architecture is a way to structure code inside one context, and has its own planned lesson.
Minimal example: two models and a translation layer
The smallest honest example is two records for the same grocery item and a layer that translates a foreign feed into one of them. Catalogue's product is about reading and buying. Inventory's stock item is about counts at one store, with its own available. The upstream store POS speaks its own vocabulary (sku, shop, qty, flag), so Inventory translates, and refuses what it cannot map.
record CatalogueProduct(String productId, String name, long priceCents) {}
record StockItem(String productId, String storeId, int onHand, int reserved) {
int available() { return onHand - reserved; }
}
record PosStockMessage(String sku, String shop, int qty, String flag) {}
static StockItem fromPos(PosStockMessage m, Map<String, String> skuToProduct, int reserved) {
String productId = skuToProduct.get(m.sku());
if (productId == null) {
throw new IllegalArgumentException("unmapped sku " + m.sku());
}
int onHand = switch (m.flag()) {
case "OK" -> m.qty();
case "DAMAGED_HOLD" -> 0;
default -> throw new IllegalArgumentException("unmapped flag " + m.flag());
};
return new StockItem(productId, m.shop(), onHand, reserved);
}
Running a main that builds a product, translates an OK message for 8 units with 3 reserved, a DAMAGED_HOLD message, and a RECALLED message prints (the code is the full program minus its main, run on JDK 26.0.1 with java Contexts.java):
Catalogue: CatalogueProduct[productId=p-100, name=Whole milk 1 L, priceCents=129]
Inventory: StockItem[productId=p-100, storeId=s-12, onHand=8, reserved=3] available=5
Held: StockItem[productId=p-100, storeId=s-12, onHand=0, reserved=0] available=0
Refused: unmapped flag RECALLED
Two points follow. The product and the stock item share only a productId, so neither model grows the other's fields. And the layer's default branch chooses to refuse an unknown flag instead of guessing: the foreign vocabulary stops at the boundary.
Failure example: one status type for everyone
A shortcut that looks like reuse: one OrderStatus library with every status, shared by Orders, Delivery and Notifications. Each context has a different idea of what a status means, so each needs different values.
Figure 3. How a shared model becomes a coupling. How to read this: the extra status matters only to Delivery, yet the others carry it. A dependency on a shared model is a dependency on every context that edits it.
What breaks: a shopper is never told the order was left with a neighbour. Detection: a metric for messages that hit a default branch, and an acceptance test that every status an upstream can emit is handled downstream. Prevention: each context owns its own status vocabulary and publishes events in a published language, and each downstream maps what it receives, as the anticorruption layer above does. Evans names the result of unmanaged mixing: models blur, definitions become ambiguous and the software drifts toward a big ball of mud.
In practice: ShopStream adds a store point-of-sale feed
This is the realistic example. It is an educational scenario on ShopStream with illustrative details; nothing in it describes a real company.
This is a realistic scenario, not a case history.
The situation
A chain of five partner stores (an illustrative subset of ShopStream's 5,000 stores) already runs its own point-of-sale (POS) system. The stores want ShopStream's stock counts to update from sales at the till, so shoppers do not order sold-out items. The POS sends a stock feed with its own codes. Inventory is the context that must consume it.
Starting architecture
Figure 4. Inventory is downstream of the POS and upstream of its own clients. How to read this: the POS model stops at Inventory's edge. Inventory's own clients see only its published language and API.
Options considered
| Option | Behaviour | Cost | Risk |
|---|---|---|---|
| A. Conform to the POS model | Inventory stores POS codes as they come | Least translation code | POS vocabulary spreads to Catalogue and Orders |
| B. Shared kernel with the POS vendor | One shared model and code | Joint governance | The vendor will not co-own ShopStream's model; changes stall |
| C. Anticorruption layer in Inventory | Translate into Inventory's model, refuse the unknown | A mapping to write and maintain | New vendor codes need a decision, not a silent guess |
The decision
Option C. The POS is a foreign model that the stores control, and the five stores are few enough that a mapping table is cheap. Inventory exposes its own stock API and a versioned event schema, so consumers never see POS codes.
Figure 5. Translate or park, never guess. How to read this: the top branch is the normal path. The bottom branch is the reason the layer exists: the foreign vocabulary changes without warning, and the layer decides what that means for Inventory.
Rollout and verification
Start with one store. Compare Inventory's available counts with a count taken at the shelf for a sample of items, and track the share of messages mapped, parked and refused. Add stores one by one and keep the mapping table in version control with a test per code.
The 2 a.m. failure
The page reads: stock for one product at three stores is frozen and shoppers order items the stores do not have. The POS vendor released a new flag, RECALLED, and the layer parks every message that carries it, so those counts stop updating. Checks: parked-message depth, the list of unmapped codes, and a comparison with a shelf count. Mitigation: confirm the flag's meaning with the vendor, map it (here, to zero on hand), and replay the parked messages. The lasting fix is an alert on the first unmapped code, a contract test against the vendor's code list, and an agreed default for unknown codes.
Architect's notes
- Name the contexts and the words. A one-page glossary per context beats a shared class.
- Invest by subdomain. Strong modelling in the core, purchase in the generic.
- Map before you change. Evans says to map the existing terrain and take up transformations later.
- Prefer a translation layer to a shared model. Translation costs code once; sharing costs coordination every time.
- Treat boundaries as hypotheses. Revise them when the language shows a new seam.
Common mistakes
- One model for everything. The "Product" class that serves four contexts.
- Using DDD on CRUD. Heavy modelling where the domain is simple.
- Letting foreign vocabulary in. Storing a vendor's codes in your domain.
- A shared library of domain objects. It couples every context that imports it.
- Confusing context with microservice. One context may be several services, or a module.
Key takeaways
- Large systems contain many models; a bounded context is the boundary within which one model and its language apply.
- Ubiquitous language is per context, and a change in the language is a change to the model.
- Core, supporting and generic subdomains decide where modelling effort goes.
- A context map records relationships: partnership, shared kernel, customer/supplier, conformist, anticorruption layer, open host service, published language, separate ways.
- Translate at the boundary and refuse what you cannot map.
- Context, module and service are different: the context is the model boundary, the others are packaging.
Practice exercises
Exercise 1 (30 min). Pick a word from your own system that means different things to two teams. Write each team's definition in one sentence and name the contexts.
Exercise 2 (45 min). Classify ShopStream's seven contexts as core, supporting or generic for a business of your choosing, and say what you would buy.
Exercise 3 (1 hour). Extend the Java example with a second foreign feed, from a delivery partner, and decide for each unknown code whether to refuse, map or hold.
Exercise 4 (2 hours). Draw the context map for a system you know, mark upstream and downstream on every arrow and the relationship type, and list where a translation layer is missing.
Related reading
- Architecture Styles: modular monolith, microservices and the distributed monolith.
- ShopStream Stage 8: the seven-context map in a full design.
- Microservice Decomposition and Data Ownership: turning contexts into services and giving each its own data.
- CQRS and Event Sourcing: separate read models between contexts.
- Event Streaming: how a published language travels.
Official documentation
- Evans, Domain-Driven Design Reference: Definitions and Pattern Summaries (2015, CC BY 4.0): the definitions and every pattern named above.
- Fowler, Bounded Context and Ubiquitous Language.
- Microsoft Azure Architecture Center, Use domain analysis to model microservices and Identify microservice boundaries.
- Lewis and Fowler, Microservices: the link between bounded context and decentralised data.
- Vernon, Effective Aggregate Design, Part II: aggregates across contexts.