Skip to content
Leo Jung
Go back

[DDD Intro #3] Ubiquitous Language: Good design starts with good language

Edit page

Ubiquitous Language is one of the most important concepts in DDD, and also one of the easiest to treat lightly. Many teams understand Ubiquitous Language as little more than “creating a glossary.” Of course, organizing terms is a good start. But Ubiquitous Language is much deeper than a glossary.

Ubiquitous Language is the language shared by domain experts and developers. It is the language that aligns the words used in meetings, planning documents, test names, class names, method names, and event names.

Good design starts with good language. Conversely, when language is vague, design becomes vague too.

The moment we believe the same word means the same thing

This happens often in development teams. Because everyone uses the same word, we assume everyone understands the same thing.

Take the word “cancellation” in a subscription service. A product planner may call it cancellation when “a user requests that they no longer subscribe.” Customer support may call it cancellation when “the subscription is terminated by user request and a refund is processed.” The payment team may think of cancellation as “stopping recurring billing so that the next payment does not occur.” A developer may understand it simply as changing SubscriptionStatus to CANCELED.

Everyone says “cancellation,” but they may be talking about different things.

If this difference is not revealed, the code becomes ambiguous.

subscription.cancel();

What does this method mean?

Does it remove access immediately? Does the user keep access for the remaining period? Does it request a refund? Does it only stop the next payment? Are free trial cancellation and paid subscription cancellation handled the same way?

If we cannot answer these questions, the name cancel() is not enough. The problem is not that the name is short. The problem is that the domain language is not yet clear enough.

Ubiquitous Language reduces translation

When the language does not match, developers must keep translating.

A domain expert says:

If a user cancels during a free trial, access disappears immediately.
Paid subscribers can keep watching until the current period ends even after cancellation.
If payment repeatedly fails, the subscription is suspended.

But the code looks like this:

user.setType("N");
subscription.setStatus(3);
payment.updateFlag("F");

This code does not preserve the language of the domain. Anyone reading it must translate numbers and flags back into domain terms. Over time, the translation rules remain only in people’s heads, and new developers struggle to understand the code.

When Ubiquitous Language is reflected in code, translation decreases.

trialSubscription.cancelImmediately(reason);
paidSubscription.cancelAtPeriodEnd(reason);
subscription.suspendDueToPaymentFailures();

Real code may need to be more refined than this. The important point is that code follows the domain’s words. Reading the code should be enough to understand what is happening in the domain.

A glossary is only the beginning

Creating a glossary is a good way to start building Ubiquitous Language. But creating a glossary does not mean Ubiquitous Language exists.

Even if the glossary says “Subscription: a contract through which a user uses a specific plan,” the language has not reached the code if the code only contains names like UserProductMapping, PayInfo, and ServiceUseYn.

Ubiquitous Language must flow into these places:

Words used in meetings
Terms in planning and policy documents
Class names in the domain model
Method names
Test names
Domain event names
Major concepts in APIs
Commit messages and review comments

For example, if a domain expert says “a subscription is renewed,” the concept of renewal should appear in the code.

subscription.renew(paymentResult);

Tests should use the same language too.

When payment succeeds, the subscription period is renewed to the next cycle
When payment fails, the subscription moves to a payment-failed state
When a free trial subscription is canceled, access is lost immediately

Then tests are not just verification code. They become documents that explain domain rules.

Good names reveal responsibility

Ubiquitous Language is not just about choosing pretty names. Good names reveal responsibility.

Look at these names:

SubscriptionService
SubscriptionManager
SubscriptionProcessor
SubscriptionHandler

These are common in many projects. But they do not reveal what they are responsible for. What does Manager manage? What does Processor process? Which domain behavior does Handler handle?

When domain behavior is expressed more specifically, the intent becomes clearer.

RenewSubscription
CancelSubscription
SuspendSubscription
ResumeSubscription
RefundSubscriptionPayment

This does not mean every class must be named this way. What matters is that names should not hide domain behavior and policy.

The same applies to method names.

subscription.updateStatus(CANCELED);

This code describes a technical change.

subscription.cancelAtPeriodEnd(reason);

This code describes a domain behavior.

In a domain model, good names reveal domain meaning before implementation details.

Language becomes clear through debate

Ubiquitous Language is not complete from the beginning. Good language is often created through debate.

Are “cancellation” and “termination” the same?

Are “refund requested” and “refund completed” the same state, or different events?

Is “subscription expiration” an action performed by a user, or an event that happens over time?

Are “suspension” and “access restriction” the same concept?

These questions are not simple word choices. When words change, models change. When models change, code responsibilities and boundaries change.

For example, at first all endings may be expressed as cancel().

subscription.cancel();

But through conversation, different concepts may emerge.

subscription.cancelAtPeriodEnd(reason);
subscription.expire(now);
subscription.suspendDueToPaymentFailure();
subscription.terminateByAdmin(reason);

All of these methods may result in the subscription no longer being normally maintained. But their domain meanings are different. If the meaning is different, the name should be different too.

Code can also clarify language

Ubiquitous Language does not mean copying domain experts’ words into code without thought. Sometimes code makes domain conversations clearer.

For example, a planning document may say, “The user cancels the subscription.” When a developer tries to implement it, they discover that there are several kinds of cancellation.

One is withdrawing a request before payment. Another is stopping an already-started paid subscription from the next renewal date. Another includes refund processing after payment.

The developer must ask questions:

Does cancellation here mean removing a subscription that has not started yet?
Does it mean stopping a subscription that has already started?
Is access maintained for the remaining period?
Does a refund always happen together?

Through these questions, the language of the planning document is refined too. In DDD, developers are not passive recipients of requirements. Developers are model makers, and they refine language in order to make models.

Ubiquitous Language is valid inside a Bounded Context

There is one more important point. Ubiquitous Language is not a language that must always have one meaning across the entire company. Language must be consistent within a boundary.

Take the word “product.”

In a display context, a product is about the name, image, description, and display status shown to customers. In an ordering context, a product is about whether it can be ordered and what its price was at the time of order. In an inventory context, a product is about the item and quantity managed in a warehouse. In a settlement context, a product is about supply price, commission rate, and settlement criteria.

Trying to unify the word “product” into one meaning across the entire company can damage the model. What matters is that language is consistent inside each context.

Therefore, Ubiquitous Language must be understood together with Bounded Context. If the same word is used with different meanings, that may not mean the language is wrong. It may be a signal that we need to find model boundaries.

How to start in practice

You do not need a grand process to build Ubiquitous Language. You can start with small practices like these.

First, record the words domain experts use repeatedly. Pay special attention to words that describe states, behaviors, and policies.

Second, question ambiguous words. Words like “cancel,” “complete,” “process,” “approve,” and “settle” almost always have more detailed meanings.

Third, align code names with domain language. If names such as process, handle, update, and changeStatus hide domain behavior, look for more specific names.

Fourth, write test names as domain sentences. Test names show how the team understands domain rules.

Fifth, change the code when the language changes. If a better name is discovered through conversation with domain experts, do not keep the old name merely because the team is used to it.

Closing

Ubiquitous Language is not a decoration in DDD. Ubiquitous Language is the material of the domain model.

Without good language, it is hard to create a good model. Ambiguous words create ambiguous responsibilities, and ambiguous responsibilities create ambiguous code. Conversely, good language reveals domain differences and makes it possible to move those differences into code structure.

In DDD, design is deeply connected to naming. A name is not just a label. A name is a compressed model that shows how we understand the domain.

In the next post, we will look at why this language does not always need to have one meaning across the entire system. A model is consistent only within a boundary. That boundary is the Bounded Context.


Edit page
Share this post:

Previous Post
[DDD Intro #4] Bounded Context: A model is consistent only within a boundary
Next Post
[DDD Intro #2] A model is not a copy of reality