← Back to writing

Ubiquitous Language and Naming Strategies

dddubiquitous-languagedomain-modelingnaming

This is the second deep-dive in a series on Domain-Driven Design. It builds on the concepts introduced in Domain-Driven Design: A Practical Overview.

The Most Important Idea in DDD

If you take one thing from Domain-Driven Design and ignore everything else, make it this: the words you use in conversation should be the words you use in code.

That’s ubiquitous language. It’s not a glossary pinned to a wiki that nobody reads. It’s the active, enforced practice of using the same terms, with the same meanings, across conversations with domain experts, in user stories, in class names, in method signatures, in database columns, in API contracts, and in test descriptions.

It sounds simple. It is hard.

Why Naming Is a Design Activity

Naming is usually treated as a cosmetic concern. Something you clean up during code review. In DDD, naming is a design decision. The name you give a class or method is a claim about what that thing is in the business domain. If the name is wrong, the model is wrong.

Consider a system that processes insurance claims. If the code calls it Request but the business calls it Claim, every conversation between developers and domain experts requires mental translation. Multiply that across dozens of concepts and the translation overhead adds up. Worse, the translations drift. Someone new joins the team and assumes Request means something different. A bug report references a “claim” and three developers search the codebase for a class that doesn’t exist.

When the code uses the same language as the business, these problems disappear. A domain expert can read a method signature like claim.Submit() or policy.Renew() and confirm whether the behavior is correct, without needing a developer to translate.

Establishing the Language

Ubiquitous language doesn’t come from developers choosing good names. It comes from conversations with domain experts where both sides negotiate the terms.

Start During Discovery

The best time to establish language is during domain discovery, particularly during Event Storming sessions. When a domain expert writes “Order Placed” on an orange sticky, that’s a term being proposed. When someone else writes “Order Created” for the same event, that’s a disagreement worth resolving now, before it becomes two different class names maintained by two different developers.

Pay attention to the words domain experts actually use, not the words you think they should use. If the warehouse team says “pick list” and not “fulfillment order,” the code should say PickList. You’re modeling their domain, not inventing one.

Challenge Vague Terms

Some terms feel precise but aren’t. “Process,” “handle,” “manage,” “service.” These are red flags. When someone says “the system processes the order,” ask what that means specifically. Does it validate the order? Route it? Calculate pricing? Each of those is a distinct domain concept that deserves its own name.

Similarly, watch for overloaded terms: words that mean different things in different contexts. “Account” in billing is not the same as “account” in identity management. This is often a signal that you’re crossing a bounded context boundary, and each context should have its own model with its own language.

Write It Down, But Keep It Alive

Maintain a living glossary. Not a static document, but something the team references and updates. The glossary should capture:

  • The term as used by domain experts
  • A brief definition
  • The bounded context it belongs to
  • Any terms it is explicitly not (to prevent confusion)

The glossary is a reference, not a rulebook. The real language lives in conversations and code. If the glossary says one thing and the domain experts say another, update the glossary.

Naming Strategies in Code

Once the language is established, the challenge is encoding it consistently in the codebase. Here are the patterns that hold up in practice.

Domain Types Use Domain Names

Classes in the domain layer should use the exact terms from the ubiquitous language, without technical suffixes or prefixes.

// Good — these are domain concepts
public class Order { }
public class Claim { }
public class Policy { }
public class Shipment { }

// Bad — technical noise obscuring domain meaning
public class OrderEntity { }
public class ClaimModel { }
public class PolicyData { }
public class ShipmentObj { }

The class name is the domain term. Adding Entity, Model, or Data to a domain class signals confusion about what layer the class belongs to, not what it represents.

Commands and Events Carry Intent

Commands should read as imperative instructions. Events should read as things that happened. Both should use domain language.

// Commands — what someone wants to do
public record PlaceOrder(OrderId OrderId, CustomerId CustomerId);
public record SubmitClaim(ClaimId ClaimId, PolicyId PolicyId);
public record CancelShipment(ShipmentId ShipmentId, string Reason);

// Events — what happened
public record OrderPlaced(OrderId OrderId, DateTime OccurredOnUtc);
public record ClaimSubmitted(ClaimId ClaimId, PolicyId PolicyId);
public record ShipmentCancelled(ShipmentId ShipmentId, string Reason);

The naming pattern itself communicates the concept. PlaceOrder is a command. Someone is asking the system to do something. OrderPlaced is an event. The system is reporting that something happened. If you find yourself naming an event OrderPlacedEvent, the suffix is redundant. The past tense already tells you it’s an event.

Methods Express Domain Operations

Methods on domain objects should read as operations the business recognizes, not as data mutations.

// Good — domain operations
order.Submit();
order.Cancel("Customer requested");
policy.Renew(renewalDate);
claim.Approve(adjusterId);

// Bad — data mutation language
order.SetStatus(OrderStatus.Submitted);
order.UpdateCancellation(true, "Customer requested");
policy.SetRenewalDate(renewalDate);
claim.SetApproved(true);

order.Submit() is something a domain expert understands. order.SetStatus(OrderStatus.Submitted) is an implementation detail that exposes how the system works rather than what it does. The first version can be validated by a business stakeholder. The second cannot.

Strongly-Typed Identifiers Prevent Confusion

When every ID is a Guid or an int, it’s easy to pass an OrderId where a CustomerId was expected. The compiler won’t help you, and the bug will be subtle.

// Ambiguous — which Guid is which?
public Order GetOrder(Guid orderId, Guid customerId) { }

// Clear — the types enforce domain meaning
public record OrderId(Guid Value) : Id<OrderId>(Value);
public record CustomerId(Guid Value) : Id<CustomerId>(Value);

public Order GetOrder(OrderId orderId, CustomerId customerId) { }

The type name is the domain concept. You can’t accidentally pass a CustomerId where an OrderId is expected. This is ubiquitous language enforced by the type system.

Value Objects Name Domain Concepts

Value objects should represent concepts that the business actually talks about, not just groups of fields.

// Good — the business talks about "money" and "addresses"
public class Money : ValueObject { }
public class Address : ValueObject { }
public class DateRange : ValueObject { }
public class EmailAddress : ValueObject { }

// Bad — generic containers that don't mean anything
public class AmountInfo { }
public class LocationData { }
public class DatePair { }

If you can’t name a value object using a term the business recognizes, it might not be a real domain concept. It might be a data structure that belongs in the infrastructure layer.

Suffixes Signal Layer, Not Domain

Reserve suffixes for indicating which layer or role a class plays, not for decorating domain concepts.

SuffixMeaningExample
(none)Domain modelOrder, Claim, Policy
InfoRead model / projectionOrderInfo, ClaimSummaryInfo
RequestAPI input DTOPlaceOrderRequest
ResponseAPI output DTOOrderResponse
HandlerCommand/event handlerPlaceOrderHandler
RepositoryPersistence abstractionOrderRepository
ConfigurationEF Core configOrderConfiguration

The domain class is Order. The read model is OrderInfo. The API DTO is PlaceOrderRequest. Each name tells you both what it represents and where it lives, without polluting the domain language.

Language Boundaries

Ubiquitous language is ubiquitous within a bounded context, not across the entire system. Different contexts will and should use different terms for related concepts.

In an e-commerce system:

  • The ordering context has Order, LineItem, Customer
  • The fulfillment context has Shipment, PickList, Recipient
  • The billing context has Invoice, Charge, PaymentMethod

A Customer in ordering might carry preferences, addresses, and order history. A Recipient in fulfillment only needs a name and shipping address. These are not the same concept. Forcing them into a single Customer class creates a model that serves no context well.

This is why overloaded terms are such an important signal during discovery. When “order” means different things to different teams, you’re not looking at a naming problem. You’re looking at a context boundary.

When the Language Changes

Domains evolve. Business terminology shifts. New concepts emerge. When this happens, the code should change too.

Renaming a class or method feels expensive, but the cost of maintaining outdated language is higher. Every developer who encounters a name that no longer matches the business term has to learn the translation, remember it, and teach it to the next person. That tax compounds.

Modern IDEs make renames cheap. What’s expensive is the meeting where someone says “when you see Fulfillment in the code, that’s actually what we now call Dispatch” and everyone nods and then half the team forgets.

Rename the class. Update the API. Fix the tests. The ubiquitous language is only ubiquitous if you maintain it.

Practical Advice

Start with events. If you can name your domain events clearly (OrderPlaced, ClaimApproved, PolicyRenewed), the rest of the language tends to follow. Events force you to think about what matters to the business.

Read your code out loud. If order.Submit() reads naturally but order.ExecuteSubmissionWorkflow() doesn’t, trust that instinct. Domain code should read like a description of what the business does.

Let the domain expert name things. When you’re unsure what to call something, ask. “What do you call this in your day-to-day work?” is a better question than “what should I name this class?”

Don’t abbreviate domain terms. Cust is not a domain concept. Customer is. Abbreviations save keystrokes and cost clarity. The person reading the code six months from now, including you, will appreciate the full word.

Use the language in tests. Test names are documentation. PlaceOrder_WithInvalidCustomer_ThrowsValidationException tells a story in domain terms. Test_Method1_Scenario3_Fails tells nothing.

Watch for language drift. If developers start using different terms than domain experts, or if two teams use the same term differently, surface it immediately. Language drift is a leading indicator of model drift.


Ubiquitous language is not a one-time exercise. It’s an ongoing discipline, a commitment to keeping the words in code aligned with the words in conversation. The next post in this series, Designing and Integrating Bounded Contexts, explores how these language boundaries become architectural boundaries.

Found this useful?

If this post helped you, consider buying me a coffee.

Buy me a coffee

Comments