class Money {
private constructor(readonly amount: number, readonly currency: string) {}
static of(amount: number, currency: string) {
if (!Number.isInteger(amount)) throw new Error("amount in minor units");
return new Money(amount, currency);
}
add(o: Money) {
if (o.currency !== this.currency) throw new Error("currency mismatch");
return new Money(this.amount + o.amount, this.currency);
}
equals(o: Money) { return o.amount === this.amount && o.currency === this.currency; }
}
An entity has an identity that stays the same over time and through changes. A value object has no identity. Only its values define it.
Entity: two customers with the same name are still different customers. A customer who changes their email is still the same customer. Two entities are equal when their ids are equal.
Value object: two value objects with the same values are interchangeable. It should be immutable: its operations return a new instance instead of changing the old one. Examples in most domains:
MoneyEmailAddressDateRangeAddress
Value objects are a strong tool against primitive obsession. This means using plain strings and numbers for domain ideas. For example, Money validates itself once when it is created and carries its currency. It also keeps the arithmetic rules in one place, so the rest of the code can't add euros to dollars by accident.
The domain decides whether something is an entity or a value object. An address is usually a value object, but for a postal service it may be an entity.
- An entity has an identity and a lifecycle, and two entities are equal when their ids match.
- A value object is defined by its values, and two are equal when their values match.
- Value objects are immutable and validate themselves.
- The same concept can be either one, depending on the domain.