Chapter 3. Shared Identifiers

I am Giacomo Tesio. "Giacomo Tesio" represents a convention to call me.

It’s just a string, an expression that human beings can tell, write or type.

There are a lot of identifiers pointing to me: tax code, vat code and even babblings like "papo" [3] as my lovely doughter used to call me when she was six month old.

As you know, identifiers are key concepts in domain driven design.

Most of newcomers thinks that identifiers are just used to identify.

They are not. They are first class citizen in the ubiquitous language. They serve to comunicate.

All people that develop, use or maintain a domain model must share the identifiers, or the language will loose its ubiquitousness.

No matter how fast are integer indices in a database, to identify an entity you must use an identifier that the users know.

In the sample domain model of cargo shipping, we used TrakingId to identify a cargos, UnLocode to identify locations and VoyageNumber to identify a voyage. UnLocode was the only one requiring strong business rules that we embedded in its constructor.

You can share what you have

To share something you need to own it.

Owning an identifier means to be able to express it in the domain model. Thus you should always model identifiers with dedicated classes.

Identifiers are contracts by definition, so they do not need to be interfaces.

Moreover they are immutable since mutability would damage their use.

This makes strings and integers a sweet temptation to avoid. The reason is simple you can’t sum two identifiers and most of times you can’t get an identifier from another.

So you have to design entities' identifiers as custom value objects. When they follow business rules (like ISBN-13) you should put them in their code. Moreover you should make them sealed to avoid dirty temptations that could lead you to a bloodbath.

[Tip]

ISBN is an interesting identifier: for a bookshop ISBNs are the main candidates to identify books, but what about a library? Library’s employees distinguish between different copies of the same book, so that an ISBN is not enough. Nevertheless, public catalogs use ISBN to allow library’s users to find what they need.

So what?

This is a simple case of identifiers shared in different contexts: both users and employees understand ISBNs when looking for a copy of a book, but when an employee lends you a book, he logs the individual copy’s location through its registration code.

In this situation we would have two classes for identify books: ISBN for the LibraryUser role and RegistrationCode for the LibraryEmployee one.

Avoid naming conventions

Each type of entity has it’s own identifier.

If the entity is not "syntetic" (like a web resource, for example) such identifiers is a property of the entity itself.

Name such property after its meaning, avoiding naming conventions.

For example, if you are modeling a network device, you could use its own IP. Please do not use a property named Identifier!

You might wonder why. There are at least two reasons:

  • client code will lose maintainability since each line of code will require more context to be understood,
  • different customers could require to identify differently the same entities. Think about the different security identifiers in capital markets: ISIN, CUSIP, SEDOL, RIC but also the various national security identifiers.

Sometimes you can safely choose a property as being the true entity identifier, but often you can not, so we suggest to avoid the spoilt for choice.

[Tip]

Epic does not provide any interface or attribute for entities and their identifiers. Client code will choose how to identify each entity.

Cheap placeholders

Think about a web URI.

You can type it, you can write it down, you can put it in your wallet and so on. Carrying an URI is far easier than carrying the resource!

Otherwise, getting the resource is cheap as long as you have access to the web (and you are allowed to get it).

The web works like a cheap repository.

URIs are just specialized identifiers, but the same is true to any other kind of identifier: whenever you have access to a repository, you can always get entities in return for identifiers.

This means that often you can replace an entity with its identifier.

[Caution]

Such replacement works very well in contexts where an entity can be identified by just one property. Otherwise you could choose one identifying property of convenience: for example you could choose RIC to point to financial instruments. An alternative would be a complex use of generics, delegates and abstractions in dependant code. This proved to be feasible, but rather expensive.
If you find yourself identifying an entity by two different properties, chances are that you need a new exploration whirpool: may be you need to define two different entities.

If you model the user of a library, for example, such LibraryUser do not need to keep a reference to all the lent books: their ReferenceCode are enough.

It might seem a bit "relational", but it’s not: LibraryUser's methods like Lend() or Return() should not accept identifiers, just books. Only the ReadingBooks property should be an enumerable of ReferenceCodes.

Decoupling entities

Given such cheap placeholders, you can avoid to keep an entity in the state of another one (whenever they do not share the same livecycle).

This way you will have decoupled classes and smaller and fewer aggregate roots.

In the classic Northwind Linq sample, the customer class gives you the set of his orders. Direct references seem cool at first, but with complex domains they could become a pain when you get deeper insights.

We noted that reference by identifiers works as well but it allow the various entities to keep immutable state only.

Decoupling contexts

Identifiers are typical components of shared kernels.

We found convenient to keep their classes along with base interfaces for entities. For example the MIC class could be in a financial kernel along with a simple (almost empty) IMarket interface like this:

Different "bounded roles" will access different views of the market concept, each specialized for the target context. But it will be really easy to move from a context to another: you have a simple and effective anti-corruption layer for free!

Coding conventions

A few coding conventions and best practices derive from the role that identifiers play in the ubiquitous language:

  • identifiers are public, concrete contracts (they do not need to have interfaces and should have public constructors);
  • identifiers must override ToString();
  • identifiers of the same type must be IEquatable among themselves;
  • identifiers must be usable as keys (thus you must override both Equals(object) and GetHashCode());
  • it should be possible to get an identifier from a string (a public constructor able to parse a string would be enough);
  • it would be kind for clients to define the == and the != operators for them;
  • formally defined identifiers (like MIC, ISBN or ISIN) should be sealed.

Such rules have proved to work very well, and they are rather easy to be followed. Currently, Epic does not provide a base class for identifiers since we do not want that your domain model depends on Epic itself. However writing such a base class should be quite easy, as you can see in the StringIdentifier<TIdentifier> class provided with the cargo sample.



[3] Something like "daddee", in italian.