Oh My Learning

Ubiquitous language and collaborative modeling

ddd-distilled/02 mid drafted · 8 min read

Domain-Driven Design Distilled (Vaughn Vernon), Chapter 2 "Strategic Design with Bounded Contexts and Ubiquitous Language"

Prerequisites ddd-distilled/01

Ubiquitous language and collaborative modeling#

TL;DR#

Ubiquitous language is a shared vocabulary — built jointly by developers and domain experts, used consistently in conversation, documentation, and code — so that a term means exactly one thing within a given boundary. It is built through collaborative modeling (structured conversation, sketching, examples) rather than by a business analyst writing a glossary in isolation and handing it to engineers.

The idea#

Every domain has its own vocabulary, and that vocabulary carries precision that generic words don't. To a warehouse manager, "ship an order" might mean something very specific — "the order has been handed to a carrier and a tracking number issued" — as opposed to "pack an order" (items boxed, not yet with a carrier) or "fulfill an order" (broader, covers digital + physical). If the codebase uses shipOrder(), processOrder(), and completeOrder() interchangeably for overlapping-but-different things, then every conversation between a developer and a domain expert requires a silent, error-prone mental translation in both directions. Bugs creep in exactly at these translation seams.

Ubiquitous language is the discipline of eliminating the translation step: the words domain experts use in conversation are the words that appear in class names, method names, module names, and documentation. When a domain expert says "an order can't be shipped until it's been through address verification," a developer working in a ubiquitous-language codebase should be able to point at an Order class with a ship() method that expresses precisely that precondition, using terms that match.

Two things make this hard, and both are worth naming explicitly: - A term's meaning can differ by bounded context (ddd-distilled/03). "Customer" in the sales context (a lead with contact info) is not the same concept as "Customer" in the billing context (an entity with a payment method and outstanding balance) — same word, different model, different context. Ubiquitous language is not one global glossary for an entire company; it's a locally consistent vocabulary, consistent within one bounded context, that may legitimately differ across contexts. - The language evolves. It is not captured once in a kickoff meeting and frozen. As the team's understanding deepens (often triggered by a hard edge case domain experts disagree about), the language — and the code that expresses it — should be refactored to match. Evans called this "knowledge crunching."

How it works#

Collaborative modeling sessions#

The mechanism for building ubiquitous language is structured conversation between developers and domain experts — not developers interviewing experts once and going off to code alone. Vernon recommends lightweight, visual techniques over heavyweight documents: - Domain storytelling / example-driven conversation — walk through a concrete scenario ("a customer in California orders three items, one of which is out of stock") and let the vocabulary that domain experts naturally use surface. Write it down verbatim; don't paraphrase into "generic developer words." - Event storming (introduced more fully in ddd-distilled/09) — a workshop technique using sticky notes for domain events, commands, and actors, run collaboratively on a wall or whiteboard, that surfaces vocabulary and process flow at the same time. - A living glossary — a shared, short document mapping term to definition, per bounded context, that gets edited whenever the conversation reveals the current definition is wrong or incomplete. It's a record of consensus reached in conversation, not a substitute for the conversation.

Worked example — insurance claims vocabulary session#

A team building a claims system holds a session with claims adjusters. The developers initially used "claim status" as a single field with values like open, closed, pending. In conversation, an adjuster says: "a claim can be closed two totally different ways — closed-paid and closed-denied — and we track different things for each: a paid claim needs a payment reference, a denied claim needs a denial reason code that feeds into our regulator report." That single sentence reveals the generic status: closed was hiding two distinct domain concepts. The team renames the model: instead of one enum value closed, they model ClaimClosedPaid and ClaimClosedDenied as distinct domain events (ddd-distilled/08) with different data, and the class names, event names, and the adjusters' own vocabulary now match exactly. This is collaborative modeling changing the model shape, not just relabeling variables — that's the real payoff.

Worked example — catching a hidden dual meaning in retail#

A retail team uses "reserve" for both "hold inventory for an in-progress checkout" and "hold inventory for a corporate bulk order awaiting approval." Both were implemented as inventory.reserve(quantity). In a modeling session, a merchandising expert points out that checkout reservations expire automatically after 15 minutes, while bulk-order reservations require manual approval and never auto-expire — these are governed by completely different business rules and, it turns out, belong to different bounded contexts (checkout vs. wholesale ordering). The shared method name had been masking two different domain concepts that should never have shared an implementation. Ubiquitous language work exposed this before it caused a production bug (e.g., a bulk-order reservation silently auto-expiring because it reused checkout's expiry logic).

Keeping language and code in sync#

The test of whether ubiquitous language is real, not aspirational, is: can you open the code and find the domain expert's words in it? If a domain expert says "a policy lapses if premium isn't paid within the grace period" and the code has if (daysSincePaid > GRACE_PERIOD) { status = 3; } — the language has not made it into the code. The fix is usually small and mechanical (rename status = 3 to a lapse() method, replace the magic number with a named gracePeriod concept) but requires deliberately treating naming and refactoring as part of the modeling process, not a cosmetic afterthought. software-engineering/clean-code covers general naming discipline (clean-code/02); ubiquitous language is that discipline applied specifically to domain vocabulary, sourced from real conversations with experts rather than a developer's best guess.

Pros#

  • Removes an entire class of miscommunication bugs caused by translating between "business words" and "code words."
  • Makes code reviewable by domain experts, not just by other engineers — a huge win for catching logic errors early, since experts can read policy.lapse() and reason about it directly.
  • Surfaces hidden distinctions (like the two "closed" claim types above) early, when they're a naming/modeling fix, rather than late, when they're a production incident.
  • Gives the team a durable shared reference (the glossary) that reduces onboarding time for new engineers and reduces "what did they mean by X" churn.

Cons#

  • Requires genuine, recurring access to domain experts' time — a scarce resource in most organizations, and the single most common reason ubiquitous language work stalls.
  • Cross-team consistency is not the goal (each bounded context has its own language), which can surprise engineers used to "one glossary for the whole company" — it takes explicit explanation to avoid confusion.
  • The language can and should change as understanding deepens, which means code needs ongoing refactoring to stay in sync — teams that treat the initial glossary as final lose the benefit over time.
  • Facilitating a good modeling session (keeping it example-driven, catching hidden distinctions like the "reserve" example) is a skill that takes practice; a poorly run session produces a glossary of vague nouns with no real modeling insight.

Alternatives#

  • Business analyst-authored glossary, handed to developers — faster to produce but loses the collaborative discovery process; tends to produce vague, developer-unaware definitions and misses the back-and-forth that reveals hidden distinctions like the "reserve" example above.
  • No shared vocabulary discipline (implicit team conventions) — works for very small, low-complexity domains where miscommunication risk is low; breaks down fast as domain complexity or team size grows.
  • Data dictionaries / schema-first documentation — documents field names and types but typically describes storage shape, not business meaning or behavior — useful as a reference, not a substitute for language work.

When to use it#

Every bounded context worth modeling deliberately (i.e., anything beyond a trivial generic subdomain, see ddd-distilled/04) benefits from an explicit ubiquitous language effort. It's especially valuable early — before the code has calcified around mistranslated vocabulary — and worth revisiting whenever a domain expert and a developer notice they've been talking past each other.

When NOT to use it#

Don't run heavyweight glossary/modeling sessions for subdomains with no real complexity or ambiguity (a straightforward internal admin tool, a subdomain that's purely CRUD). Also resist the urge to build one giant cross-context glossary — that pressure to unify vocabulary across contexts is a symptom of missing bounded-context boundaries, addressed in ddd-distilled/03.

Key takeaways / mental model#

Ubiquitous language is not a document, it's a discipline: the words in the room and the words in the code are the same words, kept in sync through ongoing conversation. When a term feels ambiguous or overloaded, that's a signal to schedule a conversation with a domain expert, not to guess. And remember — consistency is local to a bounded context, not global to the organization.

Self-check questions#

  1. In your own words, why can the word "Customer" legitimately mean two different things in two different bounded contexts within the same company, without that being a modeling failure?
  2. Describe a time (from work or from a hypothetical) where a single overloaded term in code caused a real misunderstanding or bug. How would a collaborative modeling session have surfaced it earlier?
  3. What's the difference between a glossary written by a business analyst alone and one produced through collaborative modeling? Why does the difference matter?
  4. Take a method or class name from a project you know and ask: would a domain expert recognize this name and agree it means what the code does with it? If not, what would you rename it to?

References#

  • Domain-Driven Design Distilled (Vaughn Vernon), Chapter 2: "Strategic Design with Bounded Contexts and Ubiquitous Language".
  • For general naming discipline that pairs well with domain-specific vocabulary work, see software-engineering/clean-code (clean-code/02).
  • For the deepest treatment of ubiquitous language and "knowledge crunching," see domain-modeling/ddd-evans; for practitioner techniques on running modeling sessions, see domain-modeling/learning-ddd.