Deprecated Behaviour

The inane, sometimes insane, ramblings from the mind of Brenton Alker.

Value Objects in JavaScript

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
function Distance(value, unit) {
  this.value = value;
  this.unit = unit;
}

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
function Distance(value, unit) {
  this.value = value;
  this.unit = unit;
  return Object.freeze(this);
}

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
function Distance(value, unit) {
  if (parseInt(value, 10) !== value) {
    return new Error('Distance Value is not a decimal number');
  }
  if (Distance.units.indexOf(unit) === -1) {
    return new Error('Distance Unit is not is not valid');
  }

  this.value = value;
  this.unit = unit;
  return Object.freeze(this);
}
Distance.units = ['cm', 'm', 'km'];

The decision to return the 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. 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
Distance.multipliers = [1, 100, 100000];
Distance.prototype.toCentimetres = function () {
  var multiplier = Distance.multipliers[Distance.units.indexOf(this.unit)];
  return this.value * multiplier;
};
Distance.prototype.isCloserThan = function (other) {
  return this.toCentimetres() < other.toCentimetres();
};

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
Distance.walkable = new Distance(5, 'km');
Distance.prototype.isWalkable = function () {
  return this.isCloserThan(Distance.walkable);
};

Then, when I decide I’m too lazy to walk 5km, it only needs changing in one place.