One of the questions I’m occasionally asked is how should null value object properties be handled? For clarification, the usual reason that warrants this question is because a domain entity property is optional and its type is a Value Object.

The principles of Value Objects clearly state VOs are an integral unit of data and must be valid at all times. This last part “must be valid at all times” essentially means null properties should be unacceptable. Let’s dive right in and learn about the common mistakes I see and compare it to the best solution.

If you haven’t already go back and read Value Objects: The Basics.

First things first, though, let’s get clear on the code examples used throughout this article.

Throughout this article I refer to the User entity and UserLegalName Value Object. You can find these class definitions in many of the previous articles of this series such as this one. The User entity and UserLegalName VO as designed in that article are driven by the following simple requirements.

Every user must have a valid name. Valid names consist of a non-empty first and last name.

In developer speak: the User entity requires a UserLegalName VO during instantiation and UserLegalName requires both first and last name with non-empty values.

Making a User’s Name Optional

Let’s change the previous business requirement so the user’s name is optional.

Users can be created without a name. When a user changes their name it must be valid. Valid names consist of a non-empty first and last name.

In developer speak: the User entity doesn’t require a UserLegalName VO during instantiation, but when setting their name UserLegalName requires both first and last name with non-empty values.

Great, so we clearly understand names are optional, but when they are set they must be valid. How the heck do we address this in our model?

NULL Value Object Properties

Many developers would update the VO to allow null values in the first and last name properties as illustrated below.

namespace Company.Domain.User;

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 (name == null)
        {
            return;
        }

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

        LastName = name;
    }

    private void SetFirst(string name)
    {
        if (name == null)
        {
            return;
        }

        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;
    }
}

Unfortunately, as tempting as this approach is it invalidates our business requirement that states when a user changes their name it must be valid. Valid names consist of a non-empty first and last name.

Anyhow, let’s see how we’d integrate with the changes to UserLegalName.

namespace Company.Domain.User;

public class User : IAggregateRoot
{
    public Guid Id { get; private set; }
    public UserLegalName Name { get; private set; }

    public User()
    {
        Id = Guid.NewGuid();
        // Name is optional, so set a VO reference with null properties
        Name = new UserLegalName(null, null);
    }

    public void ChangeName(UserLegalName name)
    {
        Name = name;
        DomainEvents.Raise(new UserLegalNameChangedEvent(Id));
    }
}

Sadly, I’ve seen developers do this even though it invalidates business requirements. They rely on the consumer of the domain (e.g. the front end application) to enforce the name validity requirement and go forth with their next task.

NULL Value Object Reference

The way to handle optional properties that are of a value object type is simpler than you may have thought. Just set the property referencing the VO to null. Let’s see how this approach changes our model.

namespace Company.Domain.User;

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;
    }
}

As you can see, UserLegalName has been restored to an ideal design that meets all requirements. The User entity changes are clean and simple too.

namespace Company.Domain.User;

public class User : IAggregateRoot
{
    public Guid Id { get; private set; }
    public UserLegalName Name { get; private set; }

    public User()
    {
        Id = Guid.NewGuid();
        // Name is optional, so we leave it's default reference NULL
    }

    public void ChangeName(UserLegalName name)
    {
        Name = name;
        DomainEvents.Raise(new UserLegalNameChangedEvent(Id));
    }
}

Simple, clean and hardened. Our business rules remain in the domain and we didn’t have to write “kludge” code to make the user’s name optional.

A note on ORM mapping: Good ORMs that map your domain entities to databases will natively handle Value Objects (immutable types) and will handle NULL value object references. For .NET applications I’d suggest NHibernate because it has excellent Value Object support unlike Entity Framework… But I’ll leave that topic for another discussion.

Conclusion

Well, there you have it. When a domain entity property is optional and it’s type is a Value Object don’t update the VO to allow its properties to be null. This goes against the principles of VOs and defeats their purpose. Instead simply set the VO reference property to null and make sure you use a good ORM.