This article reviews some of the different Identity Value Object Types and is an addition to Identity Value Objects. We’re going to go over abstract implementations for some common ones.

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

Let’s move right into the meat of this post.

GUID Identifiers

The first Identity Value Object type is a wrapped GUID identifier.

// Represents a company ID using a globally unique identifier.
public class CompanyId : GuidIdentityValueObject
{
    // Parameterless constructor required by ORM
    private CompanyId() { }

    public CompanyId(Guid companyId)
    {
        _setId(companyId);
    }
}

The GuidIdentityValueObject base class reduces duplicate code and can be seen below.

public abstract class GuidIdentityValueObject : ValueObjectBase where T : class
{
    public Guid Identity { get; private set; }

    protected void _setId(Guid id)
    {
        if (id == Guid.Empty)
        {
            throw new DomainException($"{nameof(T)} cannot be empty.");
        }

        Identity = id;
    }

    public override string ToString()
    {
        return Identity.ToString();
    }

    protected override IEnumerable<object> GetAtomicValues()
    {
        yield return Identity;
    }
}

Note: ValueObjectBase has been defined in a previous article Value Objects: The Basics.

Auto-Increment Identifiers

The second Identity Value Object type wraps an int or long.

// Represents a company ID using an int/long (e.g. auto-incremented) identifier.
public class CompanyId : LongIdentityValueObject
{
    // Parameterless constructor required by ORM
    private CompanyId() { }
public CompanyId(long companyId)
{
    _setId(companyId);
}
}

The LongIdentityValueObject base class can be seen below.

public abstract class LongIdentityValueObject : ValueObjectBase where T : class
{
    public long Identity { get; private set; }
protected void _setId(long id)
{
    if (id < 1)
    {
        throw new DomainException($"{nameof(T)} cannot be less than '1'.");
    }

    Identity = id;
}

public override string ToString()
{
    return Identity.ToString();
}

protected override IEnumerable<object> GetAtomicValues()
{
    yield return Identity;
}
}

String Identifiers

The third Identity type wraps a string.

// Represents a company ID using a string identifier.
public class CompanyId : StringIdentityValueObject
{
    // Parameterless constructor required by ORM
    private CompanyId() { }
public CompanyId(string companyId)
{
    _setId(companyId);
}
}

The StringIdentityValueObject base class can be seen below.

public abstract class StringIdentityValueObject : ValueObjectBase where T : class
{
    public string Identity { get; private set; }
protected void _setId(string id)
{
    if (string.IsNullOrWhitespace(id))
    {
        throw new DomainException($"{nameof(T)} cannot be empty.");
    }

    // Optionally, add string length validation

    Identity = id;
}

public override string ToString()
{
    return Identity;
}

protected override IEnumerable<object> GetAtomicValues()
{
    yield return Identity;
}
}

Complex Identifiers

The final Identity Value Object type I’d like to cover is a complex identifier. Before diving into this one let’s understand the business requirements and model a little bit more.

This is for an eCommerce marketplace that allows a Company to create multiple Products (a Product entity) to sell. The team (development, stake holders, etc) decide Products should have meaningful identifiers to lookup the Company or Product without first fetching the Product from the database.

After reviewing the requirements the team decides Product identifiers are constructed using the CompanyId and a random string concatenated with a hyphen. The final ProductId format is “<CompanyId>-<UniqueString>”.

// 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}");
    }
}

As you can see the complex identity value object scenario is helpful for the software as well as many teams within your organization. You have less round trips to the database. It’s easier for support/development/debugging teams to identify the owning Company at a glance. Finally, the end result of this pattern and its benefits produce more meaningful domain identifiers.

However, think you should strive to use Identity Value Objects throughout your model, even though most of the time they will simply wrap a primitive type like Guid, int, string, etc.