design-driven domain – Effective techniques for properly naming a ubiquitous language term (DDD) throughout the codebase

The problem

We all know how important language / universal names are in DDD.

I find myself often stuck thinking very hard about a good name for a new domain term. With the experience, I have acquired the instinct to do my best and find an ideal name from the first line of code, which is unfortunately not always possible.

But why? Why not just give it a pretty good name and rename it later? The reason is that domain terms tend to metastasize to every corner of the code base while the existing tools for renaming are surprisingly primitive.

For example, suppose I want to rename the term "InvoiceEntry"to more generic"DocumentEntry"(That may be just about anything else) because I discovered that the associated code is reusable for different types of documents and not just for invoices.

Clarification: I explicitly use the term "domain term" rather than "domain entity" or otherwise because there are other entities, classes, methods, and so on. (in a word – identifers) that are derived from the same term. I'm not even talking about possible typing errors, let alone the associated database schema, channel resources for the user interface, and so on.

If you are lucky, after renaming a type, a smart IDE will suggest renaming certain instances of the type, as well as derived classes / implementations (if any), and will look for the term in dynamic comments and invocations in a meaningful way. totally insensitive to the context. ambiguous suggestions not sure about things. And that's all.

But the work is not finished. At this point, I also have to try "Find the symbol" and then some iterations of the good old "Search in Files" (including searching with loose text fragments of the word that might be linked, for example "invoice entry" "," invoice "," entry "etc.) that can give hundreds of potentially related events. All I can count on is my attention, my diligence and my good judgment to make sure nothing is related to anything and that nothing irrelevant is affected. IDE simply can not solve this ambiguity.

Conclusion: even though with the best IDE available, it's surprisingly hard working to make a clean rename the domain term in a mature project. Doing it is a torture, reviewing someone's name is even worse.

It's easy to do neglected rename with a good IDE. However, an unscrupulous name change is probably worse than no name change, otherwise your code can quickly turn into a mess (see "Broken Window Theory").

Question and solution ideas

Can we do better than that?

Disclaimer: I'm going to talk about strongly typed languages ​​because I do not use dynamic languages ​​too much.

It is worth mentioning strongly typed functional languages with a good type inference, they are a little better in this respect, but they are far from ideal (and I simply can not go from an old project to a functional language):

  • Haskell's tradition is to give abstract names to identifiers such as a, b, c, f, and so on. This is unthinkable in classic OO languages ​​but not necessarily a bad thing. After all, if there is nothing else to say about the identifier, be it the instance of the type, it may be correct to name them "a" and "b" or " this "and" other "or similar. 100% Immunity-Abbreviated Name Identifiers ("InvoiceEntry" renamed "DocumentEntry" always makes sense, "DocumentEntry invoiceEntry1" is not)

  • with a strong type system and type inference, it is advisable to create generic functions instead of specific types. More generic functions equates to fewer mentions of a specific domain term, less code affected by the name change of a domain term.

Strongly typed domain term metadata (an idea)

What I'm looking for is an unambiguous metadata mechanism to describe the relationships between identifiers / code elements and domain terms in ubiquitous language – and be able to browse them to do something useful, especially renaming fast of a domain term. and everything that derives from it.

I have an idea of ​​how I would do this in C #. Here is a pseudocode (by no means valid or clean, but should make a concept clear):

// domain terms
Public static class DomainBloodlineTerms
{
public static const string InvoiceEntry = "InvoiceEntry";
public static const string Repository = "Repository";
constant static public string Id = "Id";
...

using _ = Domain.Static.DomainBloodlineTerms;
...

// a custom attribute to explicitly define relationships
// between the identifier (name of the class in this case) and the terms of the domain
[DomainBloodline(_.InvoiceEntry, _.Repository)
public class InvoiceEntryRepository
{
...
    //  a method with the bloodline
    [DomainBloodline("Get", _.InvoiceEntry, "By", _.Id)]
    GetInvoiceEntryById public (
// a parameter with the lineage
        [DomainBloodline(_.Id)]  int id)
{
// a local variable with the lineage
// (probably a little exaggerated, but the imperative style is also possible)
DomainBloodline.Verify (_. InvoiceEntry, "Dto")
var invoiceEntryDto = ...

I can then implement some custom code analysis rules

  1. make sure that each identifier in the namespace (or in the project / solution) is annotated with the Bloodline attribute.
  2. ensure that all annotated identifiers are synchronized with its "Bloodline" attribute

and custom refactoring:

  • If a member of DomainBloodlineTerms is renamed, the associated credentials will be renamed as well.

With the two rules above, I can rest assured that everything is renamed. for example. DomainBloodlineTerms.InvoiceEntry has been renamed DocumentEntry – and the associated class, method, parameter, and even variable will also be renamed.

Other benefits:

  • disambiguation of identical terms from different subdomains
  • protection against typos

Other suggestions?

I do not like the idea of ​​"lineage attribute" for several reasons:

  1. it's tedious and time-consuming
  2. "Find the symbol" can already facilitate some parts (except the search for typos)
  3. it is not useful to identify related artifacts outside the code (SQL schema scripts, string resources, etc.)
  4. I have the impression that I reinvent the wheel

Do you know of existing solutions or better ideas to achieve the same result? .NET or any other language / ecosystem is a good source of inspiration.