Entities are not defined by their attributes in the same way as Value Objects, instead they have an enduring “Identity”. The canonical example is a person. A person isn’t defined by their name, their age, their height, or any other attribute. Any of these can change and they are still the same person (barring any deep philisophical discussion). In software systems, this is usually implemented by assigning an arbitrary key that uniquely identifies the entity for its lifetime.
Where this key comes from is irrelevant to this discussion, but it is common for it to be a value that is generated in the persistence layer when the data is saved.
1 2 3 4 5 6 7 8 9 10 11 12
But, I just said that entities are mutable, so, why am I still calling
Object.freeze? Because, I want this object – the Entity – to be immutable, its
id can’t change but it’s
fields are still mutable; I’m taking advantage of the shallow freeze semantics.
Side Note: If you’ve been paying close attention, you may have noticed I’m
throwing exceptions here instead of
returning them like last time. This is because I consider passing the wrong type to be a developer error, which is (hopefully) an exceptional situation, as opposed to a user entering an invalid value, which is to be expected.
TrailId, which will be a Value Object similar to the previous post. The only interesting part left is the
TrailFields object. This will be slightly different to the Value Object and the Entity itself because it’s mutable. So, its validation must be performed in the setters instead of the constructor. Using the common setter/getter pattern, it looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
Even though we now have setters, all of the values are still required in the constructor. This makes it impossible* to have a partially instantiated instance running around.
Another way of modeling the
TrailEntity is to have the fields as properties of the entity but leave the
null until it is persisted, then updating it. The advantage of explicitly defining
TrailFields is, once again, less mutation and preventing the creation of incomplete instances.
Before the data is persisted we have only
TrailFields. Once it is persisted then we have a
TrailEntity. The different types represent the different stages in the lifecycle and the two states are less easily confused.