# Value objects

> Small immutable types defined by their values — Money, Email, DateRange — that validate themselves and replace error-prone primitives.

Concept · Architecture & domain modeling · https://levelcode.org/concepts/value-objects

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 new `Money`) 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.

```go
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](https://levelcode.org/concepts/code-smells) — removes whole classes of bugs:

- `Money` stored as integer minor units with a currency prevents float rounding errors and adding rupiah to ringgit.
- `Email`, `PhoneNumber` and `OrderID` types stop arguments being passed in the wrong order, because a function taking `(CustomerID, OrderID)` won't accept them swapped.
- `DateRange` puts "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](https://levelcode.org/concepts/aggregates) in domain-driven design, and a common target in the [refactoring lesson](https://levelcode.org/roadmaps/software-engineer/refactoring).
