Value Objects are awesome! Many of the JavaScript applications I have encountered don’t explicitly define their domain objects at all, instead passing primitives or raw object literals around. Value Objects can define the values in an way that allows discussion and reasoning about the values being passed around. As well as a logical place to “put code”.
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!
Immutability
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 |
|
Now, 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, ng-repeat
).
Validation
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 return
the Error
s 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. map
, filter
, 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.
Logic
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.