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.
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.
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: |
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.
Epic does not provide any interface or attribute for entities and their identifiers. Client code will choose how to identify each entity. |
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.
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 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
.
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.
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!
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)
andGetHashCode()
); - 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.