Today I want to talk about an idea that brings a paradigm shift to the way you identify domain entities. How do you feel about using Identity Value Objects for entity ID types in your domain? If you have two entities, say Company and Product you’d have two Identity Value Objects, CompanyId and ProductId respectively.

Some developers are usually in disagreement with this pattern because they argue it’s over engineering and adds unnecessary complexity.

Note: If you’re not familiar with the basic concept of Value Objects go back and read Value Objects: The Basics. Otherwise if you’re familiar with Identity Value Objects already you can learn more about Identity Value Object Types.

“It’s Over Engineering”

When I ask for elaboration it’s usually the same: “Wrapping a <insert the primitive type of your entity IDs> is unnecessary”.

But why is it unnecessary? Because an additional class is defined per entity? Extra memory consumption by additional types? Perhaps extra ORM configuration needs to be setup?

The fact is you’re under engineering your software by not using Identity Value Objects. Writing fewer classes at the expense of looser validation is usually never OK.

Let’s dive into this pattern and see a few of the benefits it brings to your domain model.

Identity Value Validity

First, as with anything represented using Value Objects, we’re guaranteed validity. We know the value of the identifier is always valid.

Because Identity Value Objects are value objects they will always have validation rules in place to protect the incoming identity value. For example string identifiers would protect against null, empty and white space only. GUID identifiers guard against null and Guid.Empty. int / long identifiers will protect against 0 and negative numbers.

Represents Complex Identifiers

Value Objects are a wonderful way to represents your complex identifiers. A complex identifier is an identifier made by joining multiple values together. This same concatenated ID value can be parsed at a later time to extract the same data that went into creating it.

A great example of this is the ProductId below that’s formulated from a foreign entity identifier CompanyId and a unique string that together form a unique ProductId. The same complex identity value object extracts and returns the CompanyId to the consumer upon request.

// Represents a complex Product ID identifier.
public class ProductId : StringIdentityValueObject
{
    private string _companyId;

    public CompanyId CompanyId {
        get => 
        {
            if (_companyId == null)
            {
                _companyId = Identity.Split('-')[0];
            }

            return new CompanyId(_companyId);
        }
    }

    // Parameterless constructor required by ORM
    private ProductId() { }

    public ProductId(CompanyId companyId, string productId)
    {
        _setCompanyProductId(companyId, productId);
    }

    private void _setCompanyProductId(CompanyId companyId, string productId)
    {
        if (string.IsNullOrWhitespace(productId))
        {
            throw new DomainException($"Product ID cannot be empty.");
        }

        // Optionally, add string length validation

        _setId($"{companyId}-{productId}");
    }
}

Note: StringIdentityValueObject is an abstract Value Object type defined in a different article, Identity Value Object Types.

This type of identifier usually contain one or more references to foreign entities to speed up software execution by reducing database round trips. It also has the beneficial side effect of making domain identifiers more meaningful to teams within your organization.

Clearer Code Signature

public interface IProductRatingService
{
    RateProduct(int companyId, int productId, int userId, int rating);
}

This method signature relies on the developers to provide clear and concise method parameter names because intellisense and other code hinting software renders this as IProductRatingService.RateProduct(int, int, int, int).

My personal experience has proved over time that relying on perfectly named parameters is unreliable. Naming conventions change from one developer to another and team to team thus naming inconsistencies pop up everywhere. Sometimes the parameter names are so bad or there are so many of them that you have to dig into the source to understand what data you need to pass and at what parameter position.

What are the cons of Identity Value Objects?

Ok, so it’s time to level the playing field and remove all biases I have toward Value Objects. Let’s get clear on the two downsides to this pattern that I’m aware of.

The first is you must create one additional class per entity identifier in your model. If you have 50 entities in your model then you will have 50 additional classes to create. Some of this concern can be alleviated by using abstract value object types as the base types for your identifiers to reduce copy-paste validation code.

The second is you must add additional ORM mapping in your database / infrastructure layer.

All in all I strongly believe the benefits of Identity Value Objects outweigh the cons that I’m aware of.

Conclusion

To conclude, developers who unintentionally (or intentionally) write anemic models are usually against this pattern even though without it business rules are invalidated (invalid identifier values can make it into the domain model in memory). The only way to protect from invalid identifiers in your domain model is to either copy-paste validation logic across your entities and services or put in a little extra work to define additional classes to represent the different identifiers in your domain. The latter will always result in less copy-paste validation logic so for me Identity Value Objects are the way to go.