Domain-Driven Design
Ubiquitous Language
The words we share with the people who live the domain, used in speech and in code.
A ubiquitous language is the vocabulary used to discuss the model and change the code. If a conversation says Envelope while the class says MessageWrapper, every change begins with a translation. If the code says Site after the model has moved to Store, the old model is still deciding the work.
It is not a glossary written once. The language changes when a name stops describing the rules, and the boundary records any old names that still have to cross it.
A name makes a promise
MiniVerine calls the stable string for a CLR message type a wire name. That phrase appears in the project description, the model, and the tests. The implementation then has to keep the promise: an incoming wire name may be known or unknown.
MessageTypeCatalog.Lookup returns that distinction in the model:
public MessageTypeLookup Lookup(MessageType name)
{
ArgumentNullException.ThrowIfNull(name);
return _byName.TryGetValue(name.Value, out var clrType)
? new KnownMessageType(clrType)
: new UnknownMessageType(name);
}
UnknownMessageType is useful language. It tells the caller what happened in the bus. KeyNotFoundException would only tell the caller how a dictionary failed.
public record UnknownMessageType(MessageType Name) : MessageTypeLookup;
The test is not whether a type sounds business-like. It is whether its name preserves a distinction the model needs.
The language can correct the payload
Here is an example from an editor payload that still contains siteId fields. One queue field already referred to a Store and was corrected to populate StoreIds. Other legacy values still arrive through SiteIds while that contract is being retired.
The current validation path keeps that compatibility name at the edge, then combines both sets before validating Stores:
var storeIdsToValidate =
new HashSet<string>(references.StoreIds, StringComparer.Ordinal);
foreach (var siteId in references.SiteIds)
storeIdsToValidate.Add(siteId);
if (storeIdsToValidate.Count > 0)
await ValidateStoresAsync(storeIdsToValidate, userGroupId, errors, ct);
The old string did not earn a Site in the current model. It earned a translation. Later payloads can say storeId; older ones can still be read without making both words true inside the context.
Shared means used
A vocabulary becomes ubiquitous through use: questions, code, tests, and corrections all employ the same distinctions. MiniVerine’s tests ask for an UnknownMessageType. The editor fix validates legacy site identifiers as stores. In both cases the language changes behaviour; it is not a list pinned above a desk.
The words are shared inside a bounded context. Across the line, another model or an old contract may use different words. Translate at that line, rather than carrying the disagreement through every type.
Once the words are shared, the next job is deciding which parts of the problem the model should keep. That is Modelling.