Value Objects and Immutability

Following on from my previous post on value objects,
immutability is often given as a core aspect of value objects. There is good reason for this, but it is not immutability that make an object a value object. It is equality based on value not identity that is essential.

That said, immutability makes sense for the domain concept in most cases. Going back to the Takeaway example from my previous post and the Distance value object used for the radius of the DeliveryArea. If the delivery area was expanded then it would be to a new distance, the distance itself would not change. If the radius was 5km and it was increased to 8km then it is a different distance, 5km is still 5km, it's just that the radius is now 8km.

In fact, since we are thinking like that, the DeliveryArea itself is a different delivery area if we change the distance. So it makes sense to set the delivery area property of the Takeaway to a new DeliveryArea rather than mutate the existing DeliveryArea.

So how do we implement this? The objects are immutable as they stand anyway. They are constructed with values and there is no way of mutating these from outside. Where we need something more that this though is if, rather than just changing the distance, we wanted to be able to add to it. For example, if we have a distance of 5km and we want to add 1 km to this. We could do this externally to the value object by getting its current value adding the new value to this and then creating a new one. We could simplify this though by getting telling the object what you want to add to it and it returning a new object with the total:

class Distance  
{
    private $quantity;
    private $unit;

    public function __construct($quantity, $unit)
    {
        $this->quantity = $quantity;
        $this->unit = $unit;        
    }

    public function equals(Distance $toCompare)
    {
        return $this->quantity == $toCompare->quantity
           && $this->unit == $toCompare->unit;
    }
    
    public function add(Distance $toAdd)
    {
    	if ($this->unit != $toAdd->unit) {
        	throw new \RuntimeException(
            	'Cannot add distances with different units'
            );
        }
        
        return new Distance(
	        $this->quantity + $toAdd->quantity,
            $this->unit
        );
    }
}

There may be times though when an object is immutable in terms of the domain but technical constraints mean that it is not actual immutable. This is can occur when construction needs to be done through setter methods in order to allow integration with something else, such as a framework's way of constructing objects from a form. Whilst we want to stay decoupled from frameworks as much as possible, value objects can still be something that are used at the boundaries between the model and the framework. In this case it may make sense to make it mutable to avoid jumping through hoops. It can still then be treated as immutable within the domain model.