Functional DDD with C# Part 4: Value Objects

Functional DDD with C# Part 4: Value Objects

Introduction

Now is the time to tackle DDD... and let's start with Value Objects. We will study them using a classic object approach and then we will switch to a more functional approach.

Value objects in a few words

Value objects are the building blocks of any development made using DDD thinking. Value objects model things that don't have an identity: an amount of money, a quantity, an address, or a price... And since they have no identity, the equality of two Values Objects is achieved by comparing all the values they contain.

Another very important characteristic of Value objects is their immutability. They are not "variables", they are, as their name suggests, values. So to modify a Value Object, you need to create a new one.

An object approach

Let's take an example to illustrate this article. Here is a Quantity Value Object made using an object approach. It contains two fields: Value and Unit.

public class Quantity
{
    public readonly int Value;
    public readonly string Unit;

    public Quantity(int Quantity, string unit)
    {
        Value = Quantity;
        Unit = unit;
    }
}

As you can see, the Quantity Value Object is immutable because of its read-only attributes. So to modify one of its properties, you need to create a new one.

Let's enrich this object by adding a business rule. Let's say a quantity can never be more than 100. So we add a rule in the constructor, ensuring that no Quantity object will ever exist in our application with a value superior to 100. What's very interesting is that Value Objects Value Object helps us make illegal states unrepresentable in our application.

public Quantity(int quantity, string unit)
{
    if (quantity > MAX_QUANTITY)
        throw new ValueObjectException($"A quantity cannot be more than {MAX_QUANTITY}.");

    Value = quantity;
    Unit = unit;
}

We need now to implement the equality comparison. Remember, in case of Value Objects, we need to compare their members one by one. To do this, we will use the ValueObject base class as shown on Microsoft's website.

For the purposes of this article, we'll also add a function to increment the value. The final code of our quantity Value Object looks like this:

public class Quantity : ValueObject
{
    public readonly int Value;
    public readonly string Unit;

    public const int MAX_QUANTITY = 100;

    public Quantity(int quantity, string unit)
    {
        if (quantity > MAX_QUANTITY)
            throw new Exception($"A quantity cannot be more than {MAX_QUANTITY}.");

        Value = quantity;
        Unit = unit;
    }

    public Quantity Add(int increment) {
        return new Quantity(Value + increment, Unit);
    }

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

Now it is time to move to a more functional approach.

A functional Value Object

The functional approach requires us to make some modifications in our Quantity Value Object:

  • Inheritance being absent from functional concepts, we will remove the inheritance from the ValueObject class.

  • Functional programming separating data and functions: we will move the Add method into an extension method.

  • In functional programming, functions need to be honest. So we will remove the Exception thrown in the constructor and we will use another mechanism.

  • And finally, we will define a container to facilitate function chaining.

Removing inheritance and using records

In functional programming, there is normally no inheritance (keep a pragmatic attitude about this statement and use inheritance if necessary): we can remove the one using the ValueObject class. We will instead use records that provide equality comparison out of the box.

public record Quantity
{
    public readonly int Value;
    public readonly string Unit;

    public const int MAX_QUANTITY = 100;

    public Quantity(int quantity, string unit)
    {
        if (quantity > MAX_QUANTITY)
            throw new Exception($"A quantity cannot be more than {MAX_QUANTITY}.");

        Value = quantity;
        Unit = unit;
    }

    public Quantity Add(int increment) {
        return new Quantity(Value + increment, Unit);
    }
}

In the previous code, I let the attributes Quantity and Value readonly. If I turned them in {get; init;} properties like this:

public int Value { get; init; }

Then a developer could use the with copy constructor like the following example, thus violating the rule (the invariant) stating than a Quantity cannot be more than 100 because in such a case, the normal positional constructor is not called:

q = q with { Value = 110 }

So you have to be really really careful when using {get; init;} properties.

Behaviors

Now let's have a look to the Add() function. In functional programming, we don't have objects so behavior does not exist, because, in object programming, a behaviour implies modifying the state of an object. And remember, in functional programming, everything is immutable.

So we need to move the Add() function from the record to a static extension class:

public static class QuantityExtensions {
    public static Quantity Add(this Quantity quantity, int increment) {
        return new Quantity(quantity.Value + increment, quantity.Unit);
    }
}

Some may think of Martin Fowler's Anemic Data Model reading this: for me, it is not a problem as long as the methods are named using domain knowledge and as long as the codebase remains well structured.

A ValueObject container

To chain function as studied in the previous part of this series, we need a container. Let's have a look.

Get the code

To follow this article, you will need the ValueObject<T> container in my Grenat.Functional.DDD project repository.

A ValueObject with two states: one valid, one invalid

A classic programming approach leads to the use of exceptions. However, exceptions should only be used to handle exceptional cases. What's more, they are forbidden in functional programming (see honest functions in the first article of this series).

So ValueObject<T> is a two states container:

  • A valid state: the inner value of ValueObject<T> is accessible.

  • An invalid state: the inner value of ValueObject<T> is unaccessible and only the error causing this invalid state is available.

A static constructor for Quantity

To ensure every one will use the ValueObject<T> container, we will replace the public constructor by a static one that returns a ValueObject<Quantity>:

// Make standard constructor private
private Quantity(int quantity)
{
    Value = quantity;
    Unit = unit;
}

// Use instead a public constructor
public static ValueObject<Quantity> Create(int quantity)
{
    if (quantity > MAX_QUANTITY)
    {
        // The Error class is embeded in Grenat.Functionnal.DDD.
        // An implicit conversion is also defined in the library to convert an Error to an invalid ValueObject<T>.
        return new Error($"Quantity cannot be more than {MAX_QUANTITY}");
    }
    else 
    {
        // An implicit conversion has been defined to convert a T to a valid ValueObject<T>.
        return new Quantity(quantity);
    }
}

We also need to change the Add() extension method accordingly:

public static class QuantityExtensions {
    public static ValueObject<Quantity> Add(this Quantity quantity, int increment) {
        return Quantity.Create(quantity.Value + increment, quantity.Unit);
    }
}

Getting the inner value of ValueObject<T>

Remember the previous article, getting the inner value of a container means that you need to go back to the normal world using a Match. Because a ValueObject<T> has two states, one valid and one invalid, we will use Match() to retrieve the inner value by providing it two functions:

  • One for the valid state. The inner value of the ValueObject<T> will be injected into it.

  • One for the invalid state. An IEnumerable<T> containing the errors will be injected into it.

var quantity = Quantity.Create(10, "Liters");
var value = quantity.Match(
                Valid: v => v.Value,
                Invalid: e => 0);
Console.WriteLine(value); //10

Chaining functions with Bind()

Now let's move to binding. If necessary, I recommend you have a refresher about Bind reading the previous Article of this series: "The elevated world".

I have defined a Bind() function for ValueObject<T> in Grenat.Functional.DDD. Using it allows us chain calls to other functions like this:

var result = Quantity.Create(10, "Liters")
            .Bind((q) => q.Add(10))
            .Bind((q) => q.Add(30))
            .Match(Valid: v => new { value = v.Value, errors = Enumerable.Empty<Error>()},
                    Invalid: e => new { value = 0, errors = e});

Console.WriteLine(result.value); //50

A superpower of Bind(): easily handling errors

Bind makes function chaining much easier. It also gets rid of all the plumbing needed for handling Error cases, because in case of an error in the execution pipeline, Bind() automatically switches the normal execution path to the error execution path. This will prevent you from writing tons of conditional logic.

Let's take an example using a functional approach:

var resultInError = Quantity.Create(10, "Liters")
                        .Bind((q) => q.Add(10))
                        .Bind((q) => q.Add(100))
                        .Bind((q) => q.Add(10))
                        .Match(Valid: v => new { Value = v.Value, Errors = Enumerable.Empty<Error>()},
                                Invalid: e => new { Value = 0, Errors = e});

Console.WriteLine(resultInError.Errors); //Error { Message = A quantity cannot be more than 100 }

Now let's try to rewrite an equivalent using imperative approaches.

Attempt 1: a pure imperative way, without using a ValueObject<T> container:

Quantity q;
try 
{
    q = new Quantity(10, "Liters");
    q = q.Add(10);
    q = q.Add(100);
    q = q.Add(10);
}
catch (Exception e) 
{
    Console.WriteLine(e);
    q = new Quantity(0,""); //wrote this to avoid the following code that needs quantity to crash
}

Attempt 2: handling a ValueObject<T> container in an imperative way:

var quantity = Quantity.Create(10, "Liters");

if (quantity.IsValid)
{
    quantity = quantity.Value.Add(10);
    if (quantity.IsValid) 
    {
        quantity = quantity.Value.Add(100);
        if (quantity.IsValid)
        {
            quantity = quantity.Value.Add(10);
        }
        else 
        {
            Console.WriteLine(quantity.Errors);
        }
    }
    else
    {
        Console.WriteLine(quantity.Errors);
    }
}
else 
{
    Console.WriteLine(quantity.Errors);
}

So, which approach do you prefer? I think the purely functional approach using Bind is simply the best and the clearest. What's more, we've been using far fewer lines of code.

Summary

  • Value Objects are the building blocks of any application made using DDD thinking.

  • To create a functional approach of Value Objects:

    • We removed inheritance and we used records.

    • The functional Value Object no longer throws an exception.

    • Behaviors are moved to static extension methods.

    • Constructors return a Value Object wrapped in a ValueObject<T> container. This will make it easier to chain functions and make it easier to write conditional logic with Bind.

    • If ValueObject<T> is invalid, then the encapsulated value is inaccessible thus forcing the programmer to handle the error case.

About the cover image

An aurora near Kiruna in Swedish Lapland. The solar wind particles, very energetic, reached the lower layers of the atmosphere: the bottom part of the aurora was rosy (due to a combination of red - ionized nitrogen - and blue - nitrogen -). This color was perfectly visible to the naked eye.

See it in large size in my portfolio.