Enterprise Architecture Patterns Tutorial 0/65 lessons ~6 min read Lesson 16

    Value Objects

    Value objects describe attributes without identity — immutable, compared by value, and safe to share.

    Course progress0%
    Focus
    18 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Value objects describe attributes without identity — immutable, compared by value, and safe to share. Amazon fulfillment uses value objects for Money, Address, SKU dimensions, and delivery windows — preventing primitive obsession bugs that lose currency units or mix inches with centimeters.

    Real production story

    An Amazon warehouse routing service stored weight as raw double pounds and dimensions as three floats — a European vendor submission in kilograms caused sortation robots to misroute oversize packages to standard chutes, jamming three fulfillment centers. Root cause: primitives instead of value objects with unit enforcement. Introducing Weight, Dimensions, and Money value objects with factory validation, immutable fields, and explicit unit conversion eliminated unit-mismatch class incidents. Sortation misroute rate dropped 99%; code review time on shipping logic halved because invariants live in types.

    Business problem

    Amazon logistics combines global vendors, multi-currency settlements, and dimensional weight pricing. Primitive strings and floats push validation to scattered if-statements — one missed check becomes physical world failure.

    • Operational cost: Misrouted packages burn labor and SLAs — not just software exceptions.
    • Financial accuracy: Money without currency type caused settlement discrepancies in marketplace payouts.
    • Code clarity: Primitive obsession hides domain rules — every service revalidates differently.

    Architecture overview

    Value object rules: immutable after creation; equality by all attributes; no identity field; factory methods validate; operations return new instances (e.g., Money.add returns new Money).

    • Examples: Money, EmailAddress, GeoCoordinate, DateRange, SKU, Percentage.
    • Composition: Address contains City, PostalCode VOs — validate at construction.
    • Persistence: Often embedded columns in entity row — no separate VO table needed.
    • vs Entity: Two Money instances both $10 are interchangeable — entities with same balance are not.

    Architecture motivation

    Value objects encapsulate validation once and compose into entities and aggregates. Staff architects replace stringly-typed IDs and floats with tiny immutable types — cheap in Java/TypeScript, expensive when skipped.

    • Force: Global units, currencies, and address formats vary — centralize rules.
    • Constraint: Legacy APIs expose primitives — ACL converts at boundary to VO.
    • Outcome: Invalid states unrepresentable — compiler and tests enforce.

    Internal architecture

    Amazon shipment value object composition inside Shipment aggregate:

    • VOs are immutable — withWeight returns new Shipment instance or builder.
    • Unit conversion only inside VO — never at call sites.
    text
    Shipment (entity / aggregate root)
    ├── ShipmentId
    ├── Weight (VO: amount + Unit.KG | LB)
    ├── Dimensions (VO: length,width,height + Unit)
    ├── Money freightCharge (VO: amount + Currency)
    ├── DeliveryWindow (VO: start,end TimeZone-aware)
    └── OriginAddress / DestAddress (VO trees)
    Invalid: shipment.weight = 12.5 // primitive
    Valid: shipment.withWeight(Weight.ofPounds(12.5))

    Data flow

    Inbound: API DTO primitives → factory validates → VO constructed → entity accepts only VOs → ORM embeds VO fields as columns. Outbound: VO.toPublishedFormat() for partner APIs.

    • Create: ShipmentRequest JSON → ShipmentFactory → Weight/Dimensions VOs → Shipment.create.
    • Price: Money.add/multiply returns new Money — never mutate.
    • Sortation: Robot API gets normalized metric VOs — conversion hidden inside.

    System design diagram

    Two diagrams show the Value Objects topology and the primary request/event path used in production at scale.

    Value Objects — system view
    API DTO
    Edge
    VO factory
    Core
    Entity
    Data
    DB embed
    Async
    High-level topology for Value Objects.
    Value Objects — request / event flow
    Raw input
    Ingress
    Validate VO
    Store
    Domain op
    Store
    Persist embed
    Emit
    Follow this path when reviewing production designs.

    Production code example

    Money and Weight value objects — TypeScript immutable types Amazon teams use:

    • BigInt minor units for money — never floating point currency math.
    • Private constructor forces factory validation — invalid gram weight unrepresentable.
    typescript
    export class Money {
    private constructor(
    readonly amountMicros: bigint,
    readonly currency: CurrencyCode
    ) {}
    static ofMajor(amount: string, currency: CurrencyCode): Money {
    const [whole, frac = "00"] = amount.split(".");
    if (!/^\d+$/.test(whole) || !/^\d{2}$/.test(frac.padEnd(2, "0").slice(0, 2))) {
    throw new InvalidMoneyError(amount, currency);
    }
    const micros = BigInt(whole) * 1_000_000n + BigInt(frac.padEnd(2, "0").slice(0, 2)) * 10_000n;
    return new Money(micros, currency);
    }
    add(other: Money): Money {
    if (this.currency !== other.currency) {
    throw new CurrencyMismatchError(this.currency, other.currency);
    }
    return new Money(this.amountMicros + other.amountMicros, this.currency);
    }
    }
    export class Weight {
    private constructor(readonly grams: number) {}
    static ofPounds(lbs: number): Weight {
    if (lbs < 0) throw new InvalidWeightError(lbs);
    return new Weight(lbs * 453.59237);
    }
    static ofKilograms(kg: number): Weight {
    if (kg < 0) throw new InvalidWeightError(kg);
    return new Weight(kg * 1000);
    }
    }

    Enterprise case study

    Amazon sortation unit safety program: Primitive floats caused physical misroutes and robot jams.

    • Before: Raw doubles; 400 misroutes/week across three FCs during EU vendor onboarding.
    • Decision: Weight/Dimensions/Money VOs, factory validation, embed in Shipment aggregate, lint ban raw floats in domain package.
    • After: Misroutes 99% down; settlement currency bugs eliminated in marketplace lane.

    Trade-offs

    • VO overhead vs primitives: More types and mapping — buys correctness at Amazon physical scale.
    • Embedded vs separate table: VOs embed in entity row — avoid normalizing VO to own table unless reuse across aggregates heavily.
    • Validation strictness: Reject bad input early vs sanitize — money never silently rounds without explicit policy VO.
    • Legacy interop: ACL converts primitives at edge — do not leak primitives into domain core.

    Security considerations

    VOs validate at trust boundaries: EmailAddress VO enforces normalization preventing homograph login bugs; Money prevents negative amount tricks if constructed only via factory.

    • Input sanitization: VO factory is single choke point — fuzz test factories.
    • PII: Address VO redact() method for logs — structured scrubbing.
    • Injection: SKU VO alphanumeric constraint — block path traversal in warehouse APIs.

    Scalability analysis

    Value objects are cheap at scale — immutable and thread-safe, no identity sync. Serialization size matters for events — use compact encoded forms.

    • Event payloads: Money as {amount_micros, currency} not formatted strings.
    • Cache keys: VO value equality enables safe cache keys — Address VO normalizes formatting.
    • GC pressure: Short-lived VO instances fine — avoid megabyte VOs in hot loops.

    Failure scenarios

    Primitive obsession failures: unit confusion, currency float rounding, null address fields partially filled.

    • Float money: Never use double for Money — use integer minor units in VO.
    • Partial address: Address VO rejects missing postal code for countries that require it.
    • Timezone window: DeliveryWindow without TZ caused same-day miss — ZonedDateTime inside VO.

    Staff engineer insights

    • If your domain model has String amount and String currency, you do not have a money concept — you have a future incident.
    • Value objects are the cheapest DDD tactic with highest ROI — start here before event sourcing fantasies.
    • Make invalid states unrepresentable — constructors that throw beat validators scattered in controllers.

    Interview questions

    Interview Prep

    Practice concise answers, then expand each card for the explanation.

    3 questions
    1AdvancedQuestionWhen would you promote a value object to an entity?+

    Answer

    When business tracks individual instance over time with same values but different history — e.g., VerifiedEmailAddress with verification audit trail. Or when reference equality matters for workflow assignment. Rare — default to VO until lifecycle proves otherwise.

    Follow-up

    Is Color in product catalog VO or entity?
    2AdvancedQuestionHow do value objects persist in relational DB?+

    Answer

    Embed columns: amount_micros, currency_code on parent row. ORM embeddable type or manual mapping in repository. No VO identity table unless many-to-many share complex VO across aggregates — uncommon.

    Follow-up

    How serialize Money in JSON events?
    3AdvancedQuestionDesign value objects for international shipping address.+

    Answer

    Address VO with CountryCode, region rules factory validates postal formats, normalized lines, immutable. Separate DeliveryInstructions VO optional. Factory rejects US address without ZIP+4 when required. ACL converts legacy string addresses on import.

    Follow-up

    How test VO equality across locales?

    Architecture review questions

    • Are money, dimensions, and addresses modeled as VOs — not primitives?
    • Are VOs immutable with factory validation?
    • Is equality value-based across all attributes?
    • Are unit conversions encapsulated inside VO only?
    • Do API/ACL layers convert primitives at boundary?
    • Are VO serialization formats documented for events?

    Summary

    Value objects at Amazon encode units, money, and addresses as immutable validated types — stopping sortation misroutes and currency bugs that primitives invite when validation is scattered across services.

    Ready to mark this lesson complete?Track your journey across the entire course.