Goal

Recently I read Vaughn Vernon’s Implementing Domain-Driven Design book, in it he uses specific classes for identifiers instead of using basic types for example Guid or String.

It is good because the code will be more readable using for example ProductId and CustomerId types and it help avoiding mistakes of using e.g. product id instead of customer id as method parameter, because the compiler will complain.

Instead of this:

interface IOrder
{
    Guid OrderId { get; }
    Guid ProductId { get; }
    Guid CustomerId { get; }
}

It looks better:

interface IInvoice
{
    OrderId OrderId { get; }
    ProductId ProductId { get; }
    CustomerId CustomerId { get; }
}

If you call this method, you can easily swap parameters without any compiler error:

void SaveOrder(Guid orderId, Guid productId, Guid customerId);

But if you use explicit types, the compiler will complain:

void SaveOrder(OrderId orderId, ProductId productId, CustomerId customerId);

Solution

In C#, I created a Guid backed base class for these identifiers. I would use static convenience methods for returning empty id and generating new identifiers. For example:

CustomerId id = CustomerId.GenerateNew ();
CustomerId emptyId = CustomerId.Empty;

For returning instances of the derived type I needed to implement these methods in all derived classes:

abstract class Id
{
    private Guid mUnderlyingId;

    protected Id() {}

    protected static T GenerateNew<T>() where T : Id, new()
    {
        Guid guid = Guid.NewGuid ();
        var id = new T();
        id.mUnderlyingId = guid;

        return id;
    }

    protected static T Empty<T>() where T : Id, new()
    {
        var id = new T ();
        return id;
    }

	/*
    	...
    */
}

class CustomerId : Id
{
    public static CustomerId GenerateNew()
    {
        return GenerateNew<CustomerId>();
    }

    public static CustomerId Empty
    {
        get { return Empty<CustomerId>(); }
    }
}

It is not too bad, but there should be better solution. I found the Curiously recurring template pattern, which can be used to refer the derived class from the base class. This pattern might cause confusion and for complex uses it is not recommended, but I think for the static factory pattern it is very convenient:

class Base<T> where T : Base<T>, new()
{
    public static T CreateNew()
    {
        return new T();
    }
}

class Derived1 : Base<Derived1>
{
}

class Derived2 : Base<Derived2>
{
}

You can create derived instances using static factory of the base class:

Derived1 instance1 = Derived1.CreateNew ();
Derived2 instance2 = Derived2.CreateNew ();

Applying this for the abstract Id class:

abstract class Id<TDerived> where TDerived : Id<TDerived>, new()
{
    private Guid mUnderlyingId;

    protected Id() {}

    public static TDerived GenerateNew()
    {
        Guid guid = Guid.NewGuid ();
        var id = new TDerived();
        id.mUnderlyingId = guid;

        return id;
    }

    public static TDerived Empty
    {
        get
        {
            var id = new TDerived();
            return id;
        }
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((TDerived) obj);
    }

    protected bool Equals(TDerived other)
    {
        return mUnderlyingId.Equals(other.mUnderlyingId);
    }

    public override int GetHashCode()
    {
        return mUnderlyingId.GetHashCode();
    }

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

By using this you can easily define new derived Id classes:

class CustomerId : Id<CustomerId>
{
}

class ProductId : Id<ProductId>
{
}

class OrderId : Id<OrderId>
{
}

And you can use the static methods (properties):

OrderId orderId = OrderId.GenerateNew ();
ProductId productId = ProductId.GenerateNew ();
CustomerId customerId = CustomerId.GenerateNew ();

OrderId orderId = OrderId.Empty;
ProductId productId = ProductId.Empty;
CustomerId customerId = CustomerId.Empty;