Value Object instead of primitive types

March 05, 2022

The Value Object pattern is one of the several common ways to represent objects in Domain-Driven Design. It represents an object considered to be a value. As such, it is identified by its state (meaning that two value objects of the same type with the same state are considered equal) and is immutable (any change creates a copy of it).

Examples of values objects are addresses, money, full names, dates, etc.

This pattern is very useful to prevent the use of primitive types all over the code (aka primitive obsession). Why avoid using primitives? Because they often allow more values than your actual domain/business logic. For example, is it okay to represent an email as a string? Not really, because it could easily contain invalid emails. Validation can help, of course, but by using a dedicated type (the value object) you can be sure that the value is always valid, and better communicate your intent.

A base implementation can be found here.

Implementing an address as a value object seems pretty straightforward, and even logical: it is a class, with a bunch of properties and a constructor. It implements the base class ValueObject, and thus gets all the goodies that come with it. It also fits naturally with the usual mental model of objects. But what about simple primitives, that you want to wrap to restrict their use? A date for example, or a postal code. Let’s implement one.

public class PostalCode : ValueObject
{
    public string Code { get; }

    public PostalCode(string code)
    {
        Code = code;
    }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Code;
    }
}

This is a very simple value object representing a postal code. It is immutable and it has comparison built-in (thanks to the base class ValueObject). But it doesn’t have much more to it. Let’s add some validation, to make it actually useful!

using System.Text.RegularExpressions;

public class PostalCode : ValueObject
{
    public string Code { get; }

    public PostalCode(string code)
    {
        if (!Regex.IsMatch(code, @"\d{4}"))
        {
            throw new InvalidPostalCodeException($"'{code}' is not a valid postal code in Belgium");
        }

        Code = code;
    }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Code;
    }
}

Perfect. Now we can use it in our domain, confident that it will never contain an invalid postal code like empty, null or some gibberish.

But I think we can do even better. Because it is a simple primitive wrapper, why not hide the actual implementation? That could help with encapsulation, abstraction, and prevent other code from looking directly into it. Let’s try.

public class PostalCode : ValueObject
{
    private readonly string _code;

    public PostalCode(string code)
    {
        // Validation code removed for conciseness
        _code = code;
    }

    // rest of the code
}

Okay, that was easy! But now it has become a lot harder to integrate with the rest of the code. Even though it makes a lot of sense for our domain, we still need at some point to return that postal code in a DTO, or save it in the database. And for that, we need to convert it back to a common type, string.

public class PostalCode : ValueObject
{
    private readonly string _code;

    public PostalCode(string code)
    {
        // Validation code removed for conciseness
        _code = code;
    }

    public static implicit operator string(PostalCode postalCode)
    {
        return postalCode._code;
    }

    // rest of the code
}

And there you have it! A simple an easy way to convert a postal code to a string. Overriding ToString() could be interesting too (but the implicit conversion makes it more straightforward). And why not implement an explicit conversion from a string?

New C# record types are a nice way to built simple value objects too, because they can provide immutability and value comparison out-of-the-box. However, among other limitations, in this case they would prevent us from hiding the internal state, so I prefer the ValueObject base class approach.


Profile picture

Written by Jonathan Hiben who lives in Belgium and works in Luxembourg building useful things in the cloud.