Domain-Driven Design: The Identifier Type Pattern

Gara Mohamed
4 min readMar 27, 2019

--

A Typescript version

Photo by Victor Dementiev on Unsplash

Introduction

The key difference between an Entity and a Value Object is the identity. Entities contains an intrinsic identifier but Value Objects have no identity.

An entity’s identifier can be described using one of the following techniques:

  • Primitive type
  • Special type defined as a value object or an alias type

The Domain-Driven Design mindset has largely promoted the second technique. So, let’s see some of its advantages.

Primitive Identifier vs. Type Identifier

Before speaking of the advantages of a dedicated id type, we first introduce the codebase that will be used in our analysis. Our example is very straightforward. We have two modules:

  • A customer module containing a Customer class.
  • An order module containing an Order class with a refrence to a customer.

Version 1

In the first version, we use a string as the customer id type.

cutomer.ts module
order.ts module

Version 2

In the second version, we use an alias type CustomerId as the customer id type.

customer.ts module
order.ts module

After, introducing our example, let’s start analyzing it.

Why we prefer the dedicated type pattern?

In this section, we will try to enumerate some advantages of creating a special type for entities identifiers.

Avoid the classic bug

In the first version, we spot the primitive obsession code smell. This code smell favors the occurance of the following classic bug.

Suppose that we have this function:

a method with two string ids

If we do the following mistake (we inverse the parameters order), the compiler would not help us and the problem will be unremarked at compile time.

a function call with inversed parameters

Allow function/method overloading

In this section, we will try to use the previous function findOrdersBy with a single parameter. So, if we want to find orders for a customer using a primitive id, we should define the following method:

Now, if we want to overload the method to fetch orders by creator, we can’t do it as the type of the customerId and creatorId have the same type. We have two options: we either use two different functions (findOrdersByCustomer and findOrdersByCreator) or use overloads:

So, the special id type allow to write a less verbose and elegant code.

Make logical dependencies physical

In the first version, the order class has a customerId. The customer has also the same information. So, there is a logical relationship between the customer and order classes but the two classes are statically independent.

In his famous book Clean Code, Uncle Bob states that:

If one module depends upon another, that dependency should be physical, not just logical.

Our second version materializes the logical dependency by creating a physical one. So, if we change the type of the customer id, the compiler will stop us if we forget to change the customerId type in the Order class. However, in the first version, we didn’t have this security.

Validate id

When using a value object, we can be sure that the id value is valid by adding a guard condition in the constructor.

Value Object Validation

Using this approach, we are sure that all the id instances are valid.

Integrate applications

Generally, an entreprise application doesn’t live isolited from other applications. So, the same Entity can transite between the different applications. Sometimes the same entity identifier has a different format and representation in the different applications.

For example, our application can communicate with two other applications A and B:

  • In our application, the customer id is stored as a string.
  • In A, the id is stored as a number.
  • In B, the id is stored as a string of 15 digits with leading zeros.

Using a value object, we can control the format conversion in a single place independently of the number of occurence of the customer id in the integration APIs.

If the application A or B change its customer id representation, we will have a single place to edit in our code.

Conclusion

I’ve seen this pattern more in the Java world but less in the Typescript one. So, I’ve wrote this article to promote this pattern in the front-end projects.

As we have seen this pattern has many advantages. Its main disadvantage is that we have to make a navigation to access to the raw value, but this is not a real problem as we don’t need to do it if we use only the value wrapper.

--

--