Module 93: Domain-Driven Design
Entities and value objects
🏠The everyday picture
Two ₹500 notes are interchangeable: you don't care which one you get. But your passport is yours: even if your address changes, it's the same passport with the same number.
📘What it really is
-
Entities have an identity that lasts over time, even as their details change: a
Student, aTutor, aBooking. Two bookings with identical details are still two different bookings; they're compared by ID. -
Value objects are defined only by their values, have no identity, and are immutable (never changed; replaced instead):
Money(amount and currency),TimeRange(start and end),EmailAddress,PhoneNumber,Subject. -
Value objects carry rules and make wrong states impossible:
export class Money {
readonly paise: number;
constructor(paise: number) {
if (!Number.isInteger(paise) || paise < 0) throw new Error("Invalid amount");
this.paise = paise;
}
add(other: Money): Money { return new Money(this.paise + other.paise); }
format(): string { return `₹${(this.paise / 100).toFixed(2)}`; }
}
export class TimeRange {
readonly start: Date;
readonly end: Date;
constructor(start: Date, end: Date) {
if (end <= start) throw new Error("End must be after start");
this.start = start;
this.end = end;
}
overlaps(other: TimeRange): boolean {
return this.start < other.end && other.start < this.end;
}
}
-
Storing money as whole paise (integers) avoids floating-point mistakes (Module 15); a
TimeRangecan't be backwards; anEmailAddressis always valid once created. Rules live in one place, not scattered as checks around the code. -
Avoid "primitive obsession": passing raw numbers and strings everywhere (
price: number,start: string), where any value, including wrong ones, can slip in. -
Time zones: store times in UTC, display in the user's zone (Asia/Kolkata), and keep the zone in the
TimeRangeor alongside it where it matters.
🖱️Try it
Implement Money, TimeRange and EmailAddress value objects for the booking platform, with unit tests for their rules (negative
money rejected, backwards ranges rejected, overlap detection, invalid emails rejected). Use TimeRange.overlaps to check a new
availability rule doesn't clash with existing ones.
✅Quick check
What's the difference between an entity and a value object?
Show the answer
An entity has a lasting identity; a value object is defined only by its values and is immutable.
Why store money as whole paise?
Show the answer
Integers avoid floating-point rounding mistakes in money calculations.
