Concept · Architecture & domain modeling
Value objects
Small immutable types defined by their values — Money, Email, DateRange — that validate themselves and replace error-prone primitives.
A value object is a type defined entirely by its attributes, with no identity of its own. Two Money values of 25,000 IDR are interchangeable, just as two copies of the number 7 are. Contrast an entity, such as a customer, which keeps its identity while its attributes change.
Properties
- Equality by value: compare all fields, not identity.
- Immutable: operations return new values (
a.Add(b)returns a newMoney) instead of mutating. - Always valid: a constructor validates input, so an invalid instance can't exist.
- Behaviour lives with the data: formatting, arithmetic, comparisons and conversions belong on the type.
type Email struct{ value string }
func ParseEmail(s string) (Email, error) {
addr, err := mail.ParseAddress(s)
if err != nil || addr.Name != "" {
return Email{}, fmt.Errorf("invalid email %q", s)
}
return Email{value: strings.ToLower(addr.Address)}, nil
}
func (e Email) String() string { return e.value }In Go, unexported fields plus a constructor enforce validity, and comparable structs give value equality with == for free. In TypeScript, use readonly fields or a branded type (type Email = string & { readonly __brand: 'Email' }) produced only by a validating function.
Why use them
Replacing primitives — the primitive obsession smell — removes whole classes of bugs:
Moneystored as integer minor units with a currency prevents float rounding errors and adding rupiah to ringgit.Email,PhoneNumberandOrderIDtypes stop arguments being passed in the wrong order, because a function taking(CustomerID, OrderID)won't accept them swapped.DateRangeputs "does this range contain that time?" and "do these overlap?" in one tested place, ending inclusive/exclusive boundary bugs.- Validation happens once, at the edge, instead of defensively everywhere.
Good candidates: money, quantities, percentages, coordinates, addresses, date ranges, identifiers, and any string with a format. Value objects are the building blocks of aggregates in domain-driven design, and a common target in the refactoring lesson.