Search code examples
domain-driven-design

Domain Driven Design Identities and Value Objects


In Vaughn Vernon Implementing Domain-driven Design book and code, he uses a mix of Identity and ValueObject for identifiers.

public interface IIdentity
{
    string Id { get; }
}

public abstract class Identity : IEquatable<Identity>, IIdentity
{
    protected Identity() => this.Id = Guid.NewGuid().ToString();
    protected Identity(string id) => this.Id = id;
    // Currently for Entity Framework, set must be protected, not private. will be fixed in EF 6.
    public string Id { get; protected set; }

    public bool Equals(Identity id)
    {
        if (object.ReferenceEquals(this, id)) return true;
        if (object.ReferenceEquals(null, id)) return false;
        return this.Id.Equals(id.Id);
    }

    public override bool Equals(object anotherObject) => Equals(anotherObject as Identity);
    public override int GetHashCode() => (this.GetType().GetHashCode() * 907) + this.Id.GetHashCode();
    public override string ToString() => this.GetType().Name + " [Id=" + Id + "]";
}

And value objects:

public abstract class ValueObject
{
    // Returns all components of a value objects which constitute its identity.
    protected abstract IEnumerable<object> GetEqualityComponents();

    public override bool Equals(object obj)
    {
        if (object.ReferenceEquals(this, obj)) return true;
        if (object.ReferenceEquals(null, obj)) return false;
        if (this.GetType() != obj.GetType()) return false;
        var vo = obj as ValueObject;
        return GetEqualityComponents().SequenceEqual(vo.GetEqualityComponents());
    }

    public override int GetHashCode() => HashCodeHelper.CombineHashCodes(GetEqualityComponents());
}

I see in some cases a ValueObject is used as an identity:

public class TaskId : ValueObject
{
    public TaskId()
        : this(Guid.NewGuid().ToString().ToUpper().Substring(0, 8))
    {
    }

    public TaskId(string id)
    {
        AssertionConcern.AssertArgumentNotEmpty(id, "The id must be provided.");
        AssertionConcern.AssertArgumentLength(id, 8, "The id must be 8 characters or less.");
        this.Id = id;
    }

    public string Id { get; private set; }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return this.Id;
    }
}

And other cases they are a pure Identity:

public class ForumId : SaaSOvation.Common.Domain.Model.Identity
{
    public ForumId(string id) : base(id) { }
}

To me, all values should be a value object, including identities, so I don't understand the differentiation here in having an explicit Identity type.

With his demo code, how/why is he choosing the above approaches to modeling identity and is there any advantage/disadvantage over the other?


Solution

  • Both implementations are comparing properties for equality, so both follow the concept of a value object. The Identity class is dedicated to be a reusable value object for custom identity types which still share the same requirements (using an arbitrary string, usually created from a GUID/UUID). Still a value object,but if no other properties than a string id are needed this one can be used in such cases while still strongly typing different types of identities with separate derived classes (like ForumId).

    The ValueObject class on the other hand is dealing with arbitrary properties that need to be compared in the respective derived classes.

    I think the intention to derive TaskId from ValueObject rather than from Identity becomes clearer if you look at the constructor. In this case a specific business rule - the id must be eight characters or less - needs to be fulfilled upon construction which would not make sense for the definition of the general identity implementation.