When learning software development it’s highly probable you learned to leverage built-in programming language value types (e.g GUID, string, int, bool, decimal, etc) to pass around data within your business domain model. This is common practice, but we can improve it by using Value Objects as Entity properties.

What are Value Objects? Value Objects are deeply immutable objects representing an integral unit of data. Domain entities can use them to describe, quantify and measure themselves. Sometimes it’s helpful to return them from methods.

Value Objects inside Entities

Value Objects really flex their muscles when used by entities to group properties into cohesive units. Let’s use a real world example that’s quite common across many businesses.

Users must have valid first and last names and when changing one they both must be set and valid.

Let’s define unit tests using XUnit that assert these business requirements.

public class UserTests
{
    [Fact]
    public void SettingOnlyFirstNameFails()
    {
        var user = new User();

        Assert.ThrowsAny<Exception>(() => user.FirstName = "Jason");
    }

    [Fact]
    public void SettingFirstNameToWhiteSpaceFails()
    {
        var user = new User();

        Assert.Throws<FirstNameException>(() => user.FirstName = "  ");
    }

    [Fact]
    public void SettingFirstNameToEmptyFails()
    {
        var user = new User();

        Assert.Throws<FirstNameException>(() => user.FirstName = string.Empty);
    }

    [Fact]
    public void SettingOnlyLastNameFails()
    {
        var user = new User();

        Assert.ThrowsAny<Exception>(() => user.LastName = "Harris");
    }

    [Fact]
    public void SettingLastNameToWhiteSpaceFails()
    {
        var user = new User();

        Assert.Throws<LastNameException>(() => user.LastName = "  ");
    }

    [Fact]
    public void SettingLastNameToEmptyFails()
    {
        var user = new User();

        Assert.Throws<LastNameException>(() => user.LastName = string.Empty);
    }
}

Let’s move on to see the most common way I’d see this implemented outside of a DDD application.

The Most Common Implementation

Generally developers would implement this user entity using two properties; one for First Name and one for Last Name. However, our business rules are impossible to enforce using this design because it’s possible one name won’t be set. Since we can’t enforce setting both property values at the same time we’ll find ourselves in a validation loop.

See the implementation below.

public class Customer
{
    private string _lastName;
    private string _firstName;

    public Guid Id { get; private set; }

    public string FirstName
    {
        get => _firstName;
        set
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                throw new FirstNameException("First name must not be empty.");
            }

            _firstName = value;
        }
    }

    public string LastName
    {
        get => _lastName;
        set
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                throw new LastNameException("Last name must not be empty.");
            }

            _lastName = value;
        }
    }
}

The above entity implementation seems reasonable, but what you will find is the tests SettingOnlyFirstNameFails and SettingOnlyLastNameFails fail the test. This is because when First Name is set Last Name is still equal to string.Empty therefore it fails the business requirement assertion. The same logic applies when first setting the Last Name property first.

Value Objects as a Hardened Solution

Solving this design flaw with Value Objects is simple. First, we need to design an immutable object taking both first and last names in the constructor. Second, this immutable object will validate both name values during instantiation. Finally, set the entity property to the reference.

If you’re not familiar with the rules of Values Objects, go back and read them. They must have side-effect free behavior!

Before implementing the value object let’s update our XUnit test to assert the business requirements.

public class UserTests
{
    [Fact]
    public void SettingOnlyFirstNameFails()
    {
        var user = new User();
        
        // It's now impossible to set only first name without setting last name too
        Assert.ThrowsAny<Exception>(() => user.ChangeName(new UserLegalName("Jason", string.Empty)));
    }

    [Fact]
    public void SettingFirstNameToWhiteSpaceFails()
    {
        var user = new User();

        Assert.Throws<FirstNameException>(() => user.ChangeName(new UserLegalName("  ", "Harris")));
    }

    [Fact]
    public void SettingFirstNameToEmptyFails()
    {
        var user = new User();

        Assert.Throws<FirstNameException>(() => user.ChangeName(new UserLegalName(string.Empty, "Harris")));
    }

    [Fact]
    public void SettingOnlyLastNameFails()
    {
        var user = new User();
        
        // It's now impossible to set only last name without setting first name too
        Assert.ThrowsAny<Exception>(() => user.ChangeName(new UserLegalName(string.Empty, "Harris")));
    }

    [Fact]
    public void SettingLastNameToWhiteSpaceFails()
    {
        var user = new User();

        Assert.Throws<LastNameException>(() => user.ChangeName(new UserLegalName("Jason", "  ")));
    }

    [Fact]
    public void SettingLastNameToEmptyFails()
    {
        var user = new User();

        Assert.Throws<LastNameException>(() => user.ChangeName(new UserLegalName("Jason", string.Empty)));
    }
}

Note: My decision to use a public method ChangeName(UserLegalName) and private setter is a personal preference that encourages baking in the business’s ubiquitous language into the entity. You’re more than welcome to keep things a little leaner by declaring the Name property as { get; set; }

Finally here’s the UserLegalName value object.

public class UserLegalName : ValueObjectBase<UserLegalName>
{
    public string FirstName { get; private set; }
    public string LastName { get; private set; }

    // ORMs (e.g Entity Framework) need an empty constructor
    private UserLegalName() { }

    public UserLegalName(string firstName, string lastName)
    {
        SetFirst(firstName);
        SetLast(lastName);
    }

    private void SetLast(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
        {
            throw new LastNameException("Last name must not be empty.");
        }

        LastName = name;
    }

    private void SetFirst(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
        {
            throw new FirstNameException("First name must not be empty.");
        }

        FirstName = name;
    }

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

Conclusion

Value Objects provide lots of value to software. They make validating multiple properties as a cohesive unit possible.

That wraps up this topic for now. If you’d like to look through the implementation of IValueObject and ValueObjectBase<T> that was mentioned in the above code samples you can find them below.

IValueObject is a marker interface. ValueObjectBase<T> defines basic equality logic that would otherwise be duplicated across multiple concrete value object types.

/// <summary>
/// A marker interface for value objects.
/// </summary>
public interface IValueObject
{

}
/// <summary>
/// Abstract base type for Value Objects. Value Objects do not have Unique Identifiers like entities.
/// Usage of this class requires an implementation for the EqualsCore Method, which is the Equals Method.
/// </summary>
/// <typeparam name="T">The value object type to assign</typeparam>
public abstract class ValueObjectBase<T> : IValueObject where T : class
{
    public override bool Equals(object obj)
    {
        if (obj == null || obj.GetType() != GetType())
        {
            return false;
        }

        var other = (ValueObjectBase<T>)obj;
        var thisValues = GetAtomicValues().GetEnumerator();
        var otherValues = other.GetAtomicValues().GetEnumerator();
        try
        {
            while (thisValues.MoveNext() && otherValues.MoveNext())
            {
                if (ReferenceEquals(thisValues.Current, null) ^
                    ReferenceEquals(otherValues.Current, null))
                {
                    return false;
                }

                if (thisValues.Current != null &&
                    !thisValues.Current.Equals(otherValues.Current))
                {
                    return false;
                }
            }
        }
        finally
        {

            thisValues.Dispose();
            otherValues.Dispose();
        }

        return !thisValues.MoveNext() && !otherValues.MoveNext();
    }

    public override int GetHashCode()
    {
        return GetAtomicValues()
            .Select(x => x != null ? x.GetHashCode() : 0)
            .Aggregate((x, y) => x ^ y);
    }

    public static bool operator ==(ValueObjectBase<T> left, ValueObjectBase<T> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(ValueObjectBase<T> left, ValueObjectBase<T> right)
    {
        return !Equals(left, right);
    }

    protected abstract IEnumerable<object> GetAtomicValues();
}