Oh My Learning

Ubiquitous language in collaborative discovery

learning-ddd/05 mid drafted · 9 min read

Learning Domain-Driven Design (Vlad Khononov), Part I, Chapter 4 - "Communicating with Domain Experts" / Ubiquitous Language

Prerequisites learning-ddd/03

Ubiquitous language in collaborative discovery#

TL;DR#

Ubiquitous language is the discipline of using the exact same vocabulary - the same nouns, verbs, and business rules - in conversation with domain experts, in documentation, and in the code itself, within a given bounded context. When engineers silently translate business terms into technical ones ("a Customer becomes a UserRecord, a Discount becomes a boolean flag"), every translation step is a place meaning quietly leaks out, and the code stops being a reliable source of truth about how the business actually works.

The idea#

Most software teams have two vocabularies running in parallel: the language domain experts use when describing the business ("when a customer redeems a loyalty voucher, the order gets a line-item discount, but only if the voucher hasn't expired and the customer's tier allows stacking with other promotions"), and the language the code actually uses (applyDiscount(order, flag1, flag2)). Every point where these two vocabularies diverge is a point where a domain expert reading the code (or a bug report) cannot recognize their own business rules, and a point where an engineer implementing a change has to mentally re-translate business intent into technical terms - a lossy, error-prone step performed silently, often differently by different engineers, drifting further out of sync over time.

Ubiquitous language is the antidote: model the code's classes, methods, and even variable names directly on the vocabulary domain experts actually use, within the boundary of a single bounded context (learning-ddd/03 - the same word can and should mean different things in different contexts, so the language is ubiquitous within a context, not necessarily across the whole system). The word "ubiquitous" signals the goal: the same term appears everywhere - in conversation, in requirements documents, in test names, in class names, in the domain model - with zero silent translation at any point.

This is not a documentation exercise; it's a modeling discipline that shapes the code itself. If a domain expert says "an order can be split into multiple shipments when items come from different warehouses," the code should have a splitIntoShipments() method and a Shipment concept - not a generic processOrderLogistics() function with an internal array nobody outside the code calls "shipments."

How it works#

Discovery is collaborative, not a one-way interview#

Ubiquitous language is built with domain experts, in ongoing conversation, not extracted from them once via a requirements document and then frozen. Techniques that support this collaborative discovery include structured workshops (learning-ddd/06's event storming is the primary one Khononov covers), but also simply the habit of an engineer asking "what do you call this?" and "what happens if...?" repeatedly during design and implementation, and correcting the code the moment a mismatch is found, rather than letting the mismatch persist because "the code already works."

Worked example - e-commerce discount rules#

A product manager says: "A customer can apply one promo code per order. If the promo code is a percentage-off type, it applies to the order subtotal before tax and shipping. If it's a free-shipping type, it zeroes out the shipping cost instead. Promo codes have an expiration date and can be restricted to a minimum order value."

A team that skips ubiquitous language might implement this as:

applyPromo(order, code) {
  if (code.type == 1) { order.total *= (1 - code.value); }
  else if (code.type == 2) { order.shippingCost = 0; }
  if (code.exp < now) return false;
  if (order.total < code.min) return false;
}

Here, type == 1 and type == 2 are meaningless without cross-referencing a lookup table somewhere else, exp and min are truncated in a way no domain expert would recognize, and the method silently mutates order.total (conflating subtotal with total, a bug a domain expert reading the code would immediately catch precisely because they don't recognize the term "total" being used where they'd say "subtotal").

A team applying ubiquitous language would instead write:

class PromoCode {
  type: PromoCodeType;         // PercentageOff | FreeShipping
  expirationDate: Date;
  minimumOrderValue: Money;
}

order.applyPromoCode(promoCode) {
  if (promoCode.isExpired(now)) throw new PromoCodeExpiredError();
  if (this.subtotal.isLessThan(promoCode.minimumOrderValue)) {
    throw new OrderBelowMinimumForPromoError();
  }
  promoCode.applyTo(this);   // dispatches to PercentageOff or FreeShipping behavior
}

Every name here - PromoCodeType, isExpired, minimumOrderValue, applyPromoCode - is a term the product manager used verbatim. A domain expert reading this code (or its test names) can verify it matches their understanding of the rule without an engineer translating for them, and a new engineer implementing the next rule change can ask the domain expert using the code's own vocabulary.

Worked example - healthcare scheduling#

A scheduling coordinator says: "if a provider cancels, we look for a matching provider - same specialty, available in the same time window - and if we find one, we rebook the patient automatically; otherwise the patient goes on a waitlist." Ubiquitous language means the resulting code has a findMatchingProvider(cancelledAppointment) method, a Waitlist concept, and a rebook() operation - not a generic handleCancellation() function with an internal status integer that happens to represent "waitlisted" as the number 3.

Worked example - logistics, catching a hidden mismatch#

During a modeling session, an engineer describes shipment "status" as a single field with values like pending, in_transit, delivered. A domain expert, hearing this described back to them, corrects it: "no, a shipment doesn't have one status - it has a current location and a separate delivery confirmation. Those are different things: a truck can be at the destination warehouse but the package hasn't been confirmed delivered yet because the recipient hasn't signed." This single correction, surfaced only because the team was actively checking the code's vocabulary against the expert's, reveals a missing domain concept (DeliveryConfirmation as distinct from Location) that a purely technical, pre-existing status enum had been silently collapsing into one field - a latent source of bugs (marking something "delivered" the moment the truck arrives, before signature) that ubiquitous-language discipline surfaces before it ships, not after a customer complaint.

Maintaining it over time#

Ubiquitous language decays the same way naming decays generally (compare clean-code/02): a rename that domain experts agree to in a meeting but that never makes it into the code leaves a permanent, growing gap. The discipline requires treating a vocabulary mismatch discovered in conversation as a small, immediate refactoring task (rename the class, the field, the method) rather than a "someday" cleanup item - because the longer a mismatch persists, the more code and the more engineers' mental models get built on top of the wrong words.

Pros#

  • Makes the code itself a reliable, checkable artifact of business understanding - a domain expert (or a new engineer, using the expert's language) can read class and method names and verify correctness without a translator.
  • Surfaces missing or conflated domain concepts early (the "delivery confirmation vs. location" example above) - these are exactly the misunderstandings that otherwise ship as bugs.
  • Reduces the "two teams talking past each other" failure mode in requirements gathering, since the requirements conversation and the implementation conversation use identical terms.
  • Directly feeds learning-ddd/06's event storming and learning-ddd/07's domain modeling - both are far more productive when the vocabulary is already shared and precise.

Cons#

  • Requires sustained access to domain experts, which is not always available (contractors, understaffed teams, experts too busy for ongoing collaboration) - without it, "ubiquitous language" degrades into engineers guessing at business vocabulary.
  • Renaming code to track vocabulary changes has real cost in a large, already-built system - the discipline is far cheaper to establish early than to retrofit.
  • Domain experts themselves sometimes disagree on terminology (different departments, different seniority, different history with the business) - the team must reconcile or scope the disagreement to separate bounded contexts (learning-ddd/03) rather than pretend a single universal term exists.
  • Overzealous literal translation of spoken language into code can produce awkward names if not tempered with programming-language idiom and consistency norms (compare clean-code/02's guidance on searchable, pronounceable names).

Alternatives#

  • A separate glossary/requirements document, disconnected from code - lower discipline cost up front, but the two artifacts (glossary and code) drift apart the moment either changes without the other being updated in lockstep; ubiquitous language's core claim is that the code itself must be the living glossary.
  • Purely technical naming conventions (CRUD-generic terms like Entity, Manager, Processor) - easier for engineers unfamiliar with the domain to write generically, but produces code that says nothing about the actual business rules it implements, defeating the purpose of learning-ddd/07's domain-model pattern.
  • ddd-evans's original Ubiquitous Language chapter - the concept originates there; Khononov's contribution is tying it explicitly to a collaborative discovery process (event storming, learning-ddd/06) as the practical mechanism for building it, rather than treating it as a static glossary exercise.

When to use it#

Apply it inside every bounded context, especially core subdomains (learning-ddd/02) where getting the model precisely right has the highest payoff. Start using it from the very first conversation with a domain expert about a new feature or context - retrofitting it later is far more expensive.

When NOT to use it#

For a purely generic subdomain being bought rather than built (learning-ddd/02), there is little payoff in building an elaborate ubiquitous-language-driven domain model around a third-party product's own vocabulary - use the vendor's terms directly, since there's no meaningful business-specific language to discover. Also don't force literal, awkward code names purely to match spoken phrasing when a more idiomatic (but still meaning-preserving) name reads better - the goal is shared meaning, not verbatim transcription.

Key takeaways / mental model#

Whenever you're about to name a class, method, or variable, ask: "would the domain expert I'm building this for recognize this name as the term they use?" If the honest answer is "no, I translated it into something more 'technical'," that translation is exactly where meaning is leaking out of the system - undo it, and use their word instead.

Self-check questions#

  1. Find a place in code you've written where a business term was silently translated into a more generic or technical name (a flag, an enum number, a generic method name). What would the domain-expert-recognizable version look like?
  2. Why does ubiquitous language apply "within a bounded context" rather than uniformly across an entire system? What would go wrong if you tried to force one universal vocabulary everywhere?
  3. Describe a time (real or hypothetical) where discussing the domain with an expert revealed that a single field in the code was actually conflating two distinct business concepts. How did the conflation happen, and what did separating them fix?
  4. Why is ubiquitous language described as something that must be actively maintained rather than a one-time naming pass?

References#

  • Learning Domain-Driven Design (Vlad Khononov), Part I, Chapter 4: "Communicating with Domain Experts".
  • Domain-Driven Design (Eric Evans, 2003), Chapter 2, "Communication and the Use of Language" - see domain-modeling/ddd-evans.