Files of Ubiquitous language
wondelai/
Show the full text203 lines
Ubiquitous Language
The single most important practice in Domain-Driven Design. A ubiquitous language is the shared vocabulary between developers and domain experts that is used everywhere -- in conversation, documentation, code, tests, and diagrams. It is not a glossary appended to a wiki. It is the living, evolving language that shapes how the system is built and how the team thinks about the domain.
Why Language Matters More Than Code
Software development is fundamentally a communication problem. The hardest bugs are not off-by-one errors or null pointer exceptions -- they are misunderstandings between people. When a developer hears "account" and thinks "user login record" while the domain expert means "financial ledger position," the resulting code will be structurally wrong in ways that no amount of testing can catch.
A ubiquitous language eliminates this class of failure by establishing a single, precise vocabulary that both sides use without translation. When the language is embedded in code, every class name, method name, and variable name becomes a checkpoint: if a domain expert cannot read the code and recognize the concepts, the model is wrong.
The Cost of Translation
Without a ubiquitous language, every conversation requires mental translation:
| Developer Says | Domain Expert Hears | Actual Meaning | Risk |
|---|---|---|---|
UserEntity |
"User? We don't have users, we have policyholders" | The system models a concept the business does not recognize | Code does not reflect reality; edge cases are missed |
processData() |
"Process what data? Which business operation?" | A generic method hiding domain-specific logic | Business rules buried in implementation; impossible to validate |
status = 3 |
"What does 3 mean?" | An opaque encoding of a domain concept | Magic numbers replace meaningful domain states |
ServiceManager |
"Manager of what?" | A catch-all class with no domain analog | God class accumulates unrelated responsibilities |
The Payoff of Alignment
When the language is shared:
- Domain experts can read tests. A test that says
policy.underwrite(application)is immediately meaningful. A test that saysservice.process(dto)is opaque. - Developers catch domain errors. When a developer writes
order.cancel()and the domain expert says "we don't cancel orders, we void them," the naming mismatch reveals a modeling error. - New team members onboard faster. The codebase teaches the domain because the names are the domain.
- Refactoring is safer. Renaming
process()toadjudicateClaim()is not just cosmetic -- it encodes domain knowledge that prevents future misuse.
Building the Language
Start with Domain Expert Conversations
The language does not come from developers reading documentation. It comes from intensive, iterative conversations between developers and domain experts. These conversations follow a pattern:
Listen for the nouns and verbs the expert uses naturally. "When a claim comes in, the adjuster reviews it, and then we either approve or deny the claim." The nouns are
Claim,Adjuster. The verbs arereview,approve,deny.Challenge ambiguity. "You said 'review' -- what exactly happens during a review? Is it the same as 'assess'?" Often the expert will distinguish between terms that outsiders conflate.
Propose the model. "So a
Claimgoes through anAdjudicationprocess where anAdjustereitherapprovesordeniesit?" The expert corrects: "Not exactly -- the adjuster makes a recommendation, but only a senior adjuster can approve claims over $10,000."Refine until the model is precise. The conversation reveals business rules that no requirements document captured: approval authority limits, recommendation vs. decision, escalation paths.
Model Exploration Whirlpool
Eric Evans describes a "whirlpool" process for modeling:
Scenario walkthrough. Walk through concrete business scenarios with domain experts. "A customer calls to report damage to their vehicle. What happens next?"
Concept extraction. Identify the key domain concepts that emerge:
Claim,Incident,CoverageVerification,DamageAssessment.Name negotiation. Debate and agree on names. "Should we call it a 'damage report' or a 'claim'? When does a report become a claim?"
Code spike. Quickly implement the emerging model in code to test whether it holds up under real logic.
Feedback loop. Show the code (or at least the class and method names) back to the domain expert. "Does this look right to you?"
This cycle repeats continuously throughout the project, not just at the beginning.
Language in Code
Naming Classes After Domain Concepts
Every class in the domain layer should be named after a concept the business recognizes:
Good names (domain language):
LoanApplication-- the business knows what this isCreditDecision-- an explicit outcome of an underwriting processPaymentSchedule-- a concrete domain conceptPolicyRenewalNotice-- named exactly as the business document is named
Bad names (technical language):
ApplicationDTO-- DTO is a technical pattern, not a domain conceptPaymentService-- "service" is a technical role, not a domain concept; what does this service do?DataProcessor-- meaningless in domain termsBaseEntityAbstractFactory-- pure technical jargon
Naming Methods After Domain Operations
Methods should read like sentences a domain expert would say:
Good:
policy.renew(effectiveDate)
claim.submitForAdjudication()
account.applyInterest(rate, period)
order.fulfillWith(shipment)
Bad:
policy.update(data)
claim.process()
account.calculate()
order.setStatus(STATUS_SHIPPED)
The difference is not cosmetic. claim.submitForAdjudication() tells you what the business operation is. claim.process() tells you nothing -- you must read the implementation to understand what it does, which means the domain knowledge is hidden.
Naming Events After Domain Facts
Domain events should be named as past-tense facts that a domain expert would recognize as significant business occurrences:
Good: PolicyIssued, ClaimDenied, PaymentOverdue, MembershipExpired
Bad: PolicyUpdated, ClaimProcessed, DataChanged, RecordModified
"Updated," "processed," and "changed" are technical descriptions of what happened to data. "Issued," "denied," "overdue," and "expired" are domain descriptions of what happened in the business.
Glossary Maintenance
The Living Glossary
Maintain a glossary document that evolves with the model. This is not a static artifact created at the start of the project and forgotten. It is a living document updated every time the language changes.
What goes in the glossary:
| Term | Definition | Context | Example |
|---|---|---|---|
| Claim | A formal request for payment under an insurance policy following a covered event | Claims processing | "The policyholder filed a claim for water damage" |
| Adjudication | The process of evaluating a claim to determine whether it is covered and how much to pay | Claims processing | "The claim is in adjudication pending the damage assessment" |
| Coverage | The set of perils and limits defined in a policy | Underwriting | "This policy provides coverage for fire but not flood" |
| Premium | The amount the policyholder pays for coverage | Billing | "The annual premium is $1,200, payable monthly" |
What does NOT go in the glossary:
- Technical terms (
Repository,Service,Controller) - Implementation details (
PostgreSQL table name,API endpoint) - Generic programming concepts (
interface,abstract class)
When Terms Conflict
The same word often means different things in different parts of the business. This is expected and healthy:
- "Account" in billing means a financial ledger position. In authentication, it means a user login. In sales, it means a company relationship.
- "Product" in the catalog means something you can browse. In inventory, it means a physical item with a location. In marketing, it means a brand offering.
The solution is not to force one definition. The solution is bounded contexts: each context has its own definition, and translations happen at the boundary.
Language Evolution
When to Change the Language
The ubiquitous language is not fixed. It evolves as the team's understanding of the domain deepens. Signals that the language needs to change:
- Awkward conversations. "Well, it's kind of like a customer but not exactly -- it's more of an applicant who might become a customer." This means
Customeris the wrong term;Applicantis a distinct concept. - Workarounds in code. A
typefield that switches behavior (e.g.,if customer.type == 'prospect') often means a single class is trying to represent two different domain concepts. - Expert correction. "We don't really call it a 'request' -- we call it a 'submission.'" Change the code immediately.
- New domain insight. "Actually, a cancellation and a voiding are different things. Cancellation is prospective; voiding is retroactive." Split the concept.
The Refactoring Trigger
When the language changes, the code must change. This is not optional. If the team agrees that "submission" is the correct term instead of "request," then:
- Rename the class:
RequestbecomesSubmission - Rename the repository:
RequestRepositorybecomesSubmissionRepository - Rename the events:
RequestCreatedbecomesSubmissionFiled - Update the database: table
requestsbecomessubmissions(with a migration) - Update the API:
/api/requestsbecomes/api/submissions(with versioning)
This may seem expensive, but the cost of maintaining a divergence between language and code is far higher. Every time a developer reads Request and has to mentally translate to "submission," they lose context and risk introducing errors.
How Naming Shapes Design
Names are not labels applied after the fact. They are design decisions that constrain and guide the system's evolution.
Names Reveal Missing Concepts
When you struggle to name something, the model is telling you something is wrong:
- "OrderProcessorHelper" -- If you need a helper, the class it is helping probably has the wrong boundaries. The behavior likely belongs inside
Orderor in a separate, well-named domain concept. - "MiscService" -- If you cannot name it, you do not understand it. Break it apart until each piece has a clear domain name.
- "DataValidator" -- Validation of what? By what rules?
CreditApplicationValidatororAddressVerifiertells you exactly what domain rules are being enforced.
Names Prevent Misuse
A method named account.debit(amount) tells future developers exactly what this does and constrains its usage. A method named account.update(amount) invites misuse because "update" could mean anything.
Names Create Boundaries
When two concepts share a name but have different behaviors, splitting the name splits the model:
Customerin sales vs.Customerin billing? These are different bounded contexts. The split in naming reveals the split in models.Orderbefore payment vs.Orderafter payment? Perhaps these arePendingOrderandConfirmedOrder-- two distinct states that deserve distinct types, not astatusflag.
Anti-Patterns to Avoid
The Jargon Trap
Developers invent internal jargon that has no domain analog: "We call it a 'widget' internally." If domain experts do not use the word "widget," it does not belong in the domain model.
The Abbreviation Trap
CustAcctMgr is not a ubiquitous language term. Write CustomerAccountManager -- or better yet, ask what the business actually calls this role. Maybe it is a RelationshipOfficer or an AccountExecutive.
The Thesaurus Trap
Using synonyms interchangeably (customer / client / user / patron) destroys precision. Pick one term per concept and enforce it everywhere. If the business uses "client" in legal contexts and "customer" in sales contexts, those are different bounded contexts with different terms -- and both are correct within their context.
The Persistence Trap
Naming domain concepts after their storage mechanism: CustomerRecord, OrderRow, PaymentTable. Domain objects are not records or rows. They are domain concepts that happen to be persisted. Name them for what they are in the domain: Customer, Order, Payment.
| 1 | # Ubiquitous Language |
| 2 | |
| 3 | The single most important practice in Domain-Driven Design. A ubiquitous language is the shared vocabulary between developers and domain experts that is used everywhere -- in conversation, documentation, code, tests, and diagrams. It is not a glossary appended to a wiki. It is the living, evolving language that shapes how the system is built and how the team thinks about the domain. |
| 4 | |
| 5 | ## Why Language Matters More Than Code |
| 6 | |
| 7 | Software development is fundamentally a communication problem. The hardest bugs are not off-by-one errors or null pointer exceptions -- they are misunderstandings between people. When a developer hears "account" and thinks "user login record" while the domain expert means "financial ledger position," the resulting code will be structurally wrong in ways that no amount of testing can catch. |
| 8 | |
| 9 | A ubiquitous language eliminates this class of failure by establishing a single, precise vocabulary that both sides use without translation. When the language is embedded in code, every class name, method name, and variable name becomes a checkpoint: if a domain expert cannot read the code and recognize the concepts, the model is wrong. |
| 10 | |
| 11 | ### The Cost of Translation |
| 12 | |
| 13 | Without a ubiquitous language, every conversation requires mental translation: |
| 14 | |
| 15 | | Developer Says | Domain Expert Hears | Actual Meaning | Risk | |
| 16 | |---------------|--------------------|-|------| |
| 17 | | `UserEntity` | "User? We don't have users, we have policyholders" | The system models a concept the business does not recognize | Code does not reflect reality; edge cases are missed | |
| 18 | | `processData()` | "Process what data? Which business operation?" | A generic method hiding domain-specific logic | Business rules buried in implementation; impossible to validate | |
| 19 | | `status = 3` | "What does 3 mean?" | An opaque encoding of a domain concept | Magic numbers replace meaningful domain states | |
| 20 | | `ServiceManager` | "Manager of what?" | A catch-all class with no domain analog | God class accumulates unrelated responsibilities | |
| 21 | |
| 22 | ### The Payoff of Alignment |
| 23 | |
| 24 | When the language is shared: |
| 25 | |
| 26 | **Domain experts can read tests.** A test that says `policy.underwrite(application)` is immediately meaningful. A test that says `service.process(dto)` is opaque. |
| 27 | **Developers catch domain errors.** When a developer writes `order.cancel()` and the domain expert says "we don't cancel orders, we void them," the naming mismatch reveals a modeling error. |
| 28 | **New team members onboard faster.** The codebase teaches the domain because the names are the domain. |
| 29 | **Refactoring is safer.** Renaming `process()` to `adjudicateClaim()` is not just cosmetic -- it encodes domain knowledge that prevents future misuse. |
| 30 | |
| 31 | ## Building the Language |
| 32 | |
| 33 | ### Start with Domain Expert Conversations |
| 34 | |
| 35 | The language does not come from developers reading documentation. It comes from intensive, iterative conversations between developers and domain experts. These conversations follow a pattern: |
| 36 | |
| 37 | **Listen for the nouns and verbs the expert uses naturally.** "When a claim comes in, the adjuster reviews it, and then we either approve or deny the claim." The nouns are `Claim`, `Adjuster`. The verbs are `review`, `approve`, `deny`. |
| 38 | |
| 39 | **Challenge ambiguity.** "You said 'review' -- what exactly happens during a review? Is it the same as 'assess'?" Often the expert will distinguish between terms that outsiders conflate. |
| 40 | |
| 41 | **Propose the model.** "So a `Claim` goes through an `Adjudication` process where an `Adjuster` either `approves` or `denies` it?" The expert corrects: "Not exactly -- the adjuster makes a recommendation, but only a senior adjuster can approve claims over $10,000." |
| 42 | |
| 43 | **Refine until the model is precise.** The conversation reveals business rules that no requirements document captured: approval authority limits, recommendation vs. decision, escalation paths. |
| 44 | |
| 45 | ### Model Exploration Whirlpool |
| 46 | |
| 47 | Eric Evans describes a "whirlpool" process for modeling: |
| 48 | |
| 49 | **Scenario walkthrough.** Walk through concrete business scenarios with domain experts. "A customer calls to report damage to their vehicle. What happens next?" |
| 50 | |
| 51 | **Concept extraction.** Identify the key domain concepts that emerge: `Claim`, `Incident`, `CoverageVerification`, `DamageAssessment`. |
| 52 | |
| 53 | **Name negotiation.** Debate and agree on names. "Should we call it a 'damage report' or a 'claim'? When does a report become a claim?" |
| 54 | |
| 55 | **Code spike.** Quickly implement the emerging model in code to test whether it holds up under real logic. |
| 56 | |
| 57 | **Feedback loop.** Show the code (or at least the class and method names) back to the domain expert. "Does this look right to you?" |
| 58 | |
| 59 | This cycle repeats continuously throughout the project, not just at the beginning. |
| 60 | |
| 61 | ## Language in Code |
| 62 | |
| 63 | ### Naming Classes After Domain Concepts |
| 64 | |
| 65 | Every class in the domain layer should be named after a concept the business recognizes: |
| 66 | |
| 67 | **Good names (domain language):** |
| 68 | `LoanApplication` -- the business knows what this is |
| 69 | `CreditDecision` -- an explicit outcome of an underwriting process |
| 70 | `PaymentSchedule` -- a concrete domain concept |
| 71 | `PolicyRenewalNotice` -- named exactly as the business document is named |
| 72 | |
| 73 | **Bad names (technical language):** |
| 74 | `ApplicationDTO` -- DTO is a technical pattern, not a domain concept |
| 75 | `PaymentService` -- "service" is a technical role, not a domain concept; what does this service do? |
| 76 | `DataProcessor` -- meaningless in domain terms |
| 77 | `BaseEntityAbstractFactory` -- pure technical jargon |
| 78 | |
| 79 | ### Naming Methods After Domain Operations |
| 80 | |
| 81 | Methods should read like sentences a domain expert would say: |
| 82 | |
| 83 | **Good:** |
| 84 | |
| 85 | policy.renew(effectiveDate) |
| 86 | claim.submitForAdjudication() |
| 87 | account.applyInterest(rate, period) |
| 88 | order.fulfillWith(shipment) |
| 89 | |
| 90 | |
| 91 | **Bad:** |
| 92 | |
| 93 | policy.update(data) |
| 94 | claim.process() |
| 95 | account.calculate() |
| 96 | order.setStatus(STATUS_SHIPPED) |
| 97 | |
| 98 | |
| 99 | The difference is not cosmetic. `claim.submitForAdjudication()` tells you what the business operation is. `claim.process()` tells you nothing -- you must read the implementation to understand what it does, which means the domain knowledge is hidden. |
| 100 | |
| 101 | ### Naming Events After Domain Facts |
| 102 | |
| 103 | Domain events should be named as past-tense facts that a domain expert would recognize as significant business occurrences: |
| 104 | |
| 105 | **Good:** `PolicyIssued`, `ClaimDenied`, `PaymentOverdue`, `MembershipExpired` |
| 106 | |
| 107 | **Bad:** `PolicyUpdated`, `ClaimProcessed`, `DataChanged`, `RecordModified` |
| 108 | |
| 109 | "Updated," "processed," and "changed" are technical descriptions of what happened to data. "Issued," "denied," "overdue," and "expired" are domain descriptions of what happened in the business. |
| 110 | |
| 111 | ## Glossary Maintenance |
| 112 | |
| 113 | ### The Living Glossary |
| 114 | |
| 115 | Maintain a glossary document that evolves with the model. This is not a static artifact created at the start of the project and forgotten. It is a living document updated every time the language changes. |
| 116 | |
| 117 | **What goes in the glossary:** |
| 118 | |
| 119 | | Term | Definition | Context | Example | |
| 120 | |------|-----------|---------|---------| |
| 121 | | Claim | A formal request for payment under an insurance policy following a covered event | Claims processing | "The policyholder filed a claim for water damage" | |
| 122 | | Adjudication | The process of evaluating a claim to determine whether it is covered and how much to pay | Claims processing | "The claim is in adjudication pending the damage assessment" | |
| 123 | | Coverage | The set of perils and limits defined in a policy | Underwriting | "This policy provides coverage for fire but not flood" | |
| 124 | | Premium | The amount the policyholder pays for coverage | Billing | "The annual premium is $1,200, payable monthly" | |
| 125 | |
| 126 | **What does NOT go in the glossary:** |
| 127 | Technical terms (`Repository`, `Service`, `Controller`) |
| 128 | Implementation details (`PostgreSQL table name`, `API endpoint`) |
| 129 | Generic programming concepts (`interface`, `abstract class`) |
| 130 | |
| 131 | ### When Terms Conflict |
| 132 | |
| 133 | The same word often means different things in different parts of the business. This is expected and healthy: |
| 134 | |
| 135 | **"Account"** in billing means a financial ledger position. In authentication, it means a user login. In sales, it means a company relationship. |
| 136 | **"Product"** in the catalog means something you can browse. In inventory, it means a physical item with a location. In marketing, it means a brand offering. |
| 137 | |
| 138 | The solution is not to force one definition. The solution is bounded contexts: each context has its own definition, and translations happen at the boundary. |
| 139 | |
| 140 | ## Language Evolution |
| 141 | |
| 142 | ### When to Change the Language |
| 143 | |
| 144 | The ubiquitous language is not fixed. It evolves as the team's understanding of the domain deepens. Signals that the language needs to change: |
| 145 | |
| 146 | **Awkward conversations.** "Well, it's kind of like a customer but not exactly -- it's more of an applicant who might become a customer." This means `Customer` is the wrong term; `Applicant` is a distinct concept. |
| 147 | **Workarounds in code.** A `type` field that switches behavior (e.g., `if customer.type == 'prospect'`) often means a single class is trying to represent two different domain concepts. |
| 148 | **Expert correction.** "We don't really call it a 'request' -- we call it a 'submission.'" Change the code immediately. |
| 149 | **New domain insight.** "Actually, a cancellation and a voiding are different things. Cancellation is prospective; voiding is retroactive." Split the concept. |
| 150 | |
| 151 | ### The Refactoring Trigger |
| 152 | |
| 153 | When the language changes, the code must change. This is not optional. If the team agrees that "submission" is the correct term instead of "request," then: |
| 154 | |
| 155 | Rename the class: `Request` becomes `Submission` |
| 156 | Rename the repository: `RequestRepository` becomes `SubmissionRepository` |
| 157 | Rename the events: `RequestCreated` becomes `SubmissionFiled` |
| 158 | Update the database: table `requests` becomes `submissions` (with a migration) |
| 159 | Update the API: `/api/requests` becomes `/api/submissions` (with versioning) |
| 160 | |
| 161 | This may seem expensive, but the cost of maintaining a divergence between language and code is far higher. Every time a developer reads `Request` and has to mentally translate to "submission," they lose context and risk introducing errors. |
| 162 | |
| 163 | ## How Naming Shapes Design |
| 164 | |
| 165 | Names are not labels applied after the fact. They are design decisions that constrain and guide the system's evolution. |
| 166 | |
| 167 | ### Names Reveal Missing Concepts |
| 168 | |
| 169 | When you struggle to name something, the model is telling you something is wrong: |
| 170 | |
| 171 | **"OrderProcessorHelper"** -- If you need a helper, the class it is helping probably has the wrong boundaries. The behavior likely belongs inside `Order` or in a separate, well-named domain concept. |
| 172 | **"MiscService"** -- If you cannot name it, you do not understand it. Break it apart until each piece has a clear domain name. |
| 173 | **"DataValidator"** -- Validation of what? By what rules? `CreditApplicationValidator` or `AddressVerifier` tells you exactly what domain rules are being enforced. |
| 174 | |
| 175 | ### Names Prevent Misuse |
| 176 | |
| 177 | A method named `account.debit(amount)` tells future developers exactly what this does and constrains its usage. A method named `account.update(amount)` invites misuse because "update" could mean anything. |
| 178 | |
| 179 | ### Names Create Boundaries |
| 180 | |
| 181 | When two concepts share a name but have different behaviors, splitting the name splits the model: |
| 182 | |
| 183 | `Customer` in sales vs. `Customer` in billing? These are different bounded contexts. The split in naming reveals the split in models. |
| 184 | `Order` before payment vs. `Order` after payment? Perhaps these are `PendingOrder` and `ConfirmedOrder` -- two distinct states that deserve distinct types, not a `status` flag. |
| 185 | |
| 186 | ## Anti-Patterns to Avoid |
| 187 | |
| 188 | ### The Jargon Trap |
| 189 | |
| 190 | Developers invent internal jargon that has no domain analog: "We call it a 'widget' internally." If domain experts do not use the word "widget," it does not belong in the domain model. |
| 191 | |
| 192 | ### The Abbreviation Trap |
| 193 | |
| 194 | `CustAcctMgr` is not a ubiquitous language term. Write `CustomerAccountManager` -- or better yet, ask what the business actually calls this role. Maybe it is a `RelationshipOfficer` or an `AccountExecutive`. |
| 195 | |
| 196 | ### The Thesaurus Trap |
| 197 | |
| 198 | Using synonyms interchangeably (`customer` / `client` / `user` / `patron`) destroys precision. Pick one term per concept and enforce it everywhere. If the business uses "client" in legal contexts and "customer" in sales contexts, those are different bounded contexts with different terms -- and both are correct within their context. |
| 199 | |
| 200 | ### The Persistence Trap |
| 201 | |
| 202 | Naming domain concepts after their storage mechanism: `CustomerRecord`, `OrderRow`, `PaymentTable`. Domain objects are not records or rows. They are domain concepts that happen to be persisted. Name them for what they are in the domain: `Customer`, `Order`, `Payment`. |
| 203 |
Discussion
Browse more free Claude skills.