To explore their use, lets start by defining a simple Value Object – Distance – because I don’t claim to be creative in these matters.
About the simplest way of defining a Value Object is like this.
1 2 3 4
This is a good start and really isn’t much more difficult that using object literals. Given the other advantages (that can be added incrementally), I don’t see a down side.
But, but we can do better!
My first step would be to make it immutable. Immutability is a great property to have, especially for things being passed around a lot. For Value Objects, I think it is a reasonable default.
1 2 3 4 5
Object.freeze isn’t perfect. It only freezes the object itself; nested properties can still be mutated. But, I still think it’s worth it to prevent accidental mutation, or third party code mutating your objects without your knowledge (I’m looking at you,
The next issue to look at is validation of the fields.
new Distance(12, 'parsecs'); would ostensibly give a valid
Distance, but it probably wouldn’t be very useful. We can use the constructor function to check the parameters, ensuring only valid values can be created. In this case, say – A distance is a positive whole number measured in centimetres, metres or kilometres.
It might be implemented this way:
1 2 3 4 5 6 7 8 9 10 11 12 13
The decision to
Errors instead of
throw them is probably debatable, but I don’t think validation errors are “exceptional” and find it composes better with higher order functions (eg.
reduce) which I generally use a lot of. Either way, the point is it won’t get back a valid
Distance object. And, since they are immutable, a valid
Distance only ever be valid.
Once a Value Object is defined, it becomes a great place to put some logic. Normalization and comparison logic is a good candidate. For example, adding methods to compare two distances.
1 2 3 4 5 6 7 8
It’s also a good place to add domain logic; the application may define how far is considered “walkable”. By having the Value Object, it provides somewhere for this knowledge to go and prevents
if (distance < 5) being duplicated across the application.
1 2 3 4
Then, when I decide I’m too lazy to walk 5km, it only needs changing in one place.