The Fusion domain kitValue objects that can't be invalid
No narration yet
Module 8 · Lesson 214 min

Value objects that can't be invalid

A value object wraps a primitive (or a few) in a type that cannot exist in an invalid state. A FlightNumberVo is a string that is guaranteed non-blank, because the only way to make one is through a factory that validates. Contrast the TS habit of passing a bare string everywhere and validating at each use site: the VO validates once, at construction, and every downstream consumer trusts it.

The mechanism is a private constructor plus a static Create factory that returns Result<T>. You can't call new FlightNumberVo(...) from outside; you call Create, which either hands you a valid VO or a failure. An invalid VO is literally unconstructable. Anti-pattern #25 is exactly this: no public constructors on domain types, use the Create factory.

A single-value VO (real FlightNumberVo)
public sealed class FlightNumberVo : SingleValueObject<FlightNumberVo, string>
{
    private FlightNumberVo(string value) : base(value) { }

    public static Result<FlightNumberVo> Create(string? value) =>
        Result<FlightNumberVo>
            .FailIf(string.IsNullOrWhiteSpace(value),
                new EjError(nameof(Value), "Flight number is required."))
            .OrSuccess(() => new FlightNumberVo(value!.Trim()));

    // Shorthand for domain-internal code and tests; throws on invalid. NOT for app boundaries.
    public static FlightNumberVo From(string value) => Create(value).Value;
}

Two base classes carry the equality plumbing so you never write it. SingleValueObject<TSelf, TValue> is for a one-primitive wrapper: it gives you .Value, value equality, and ToString() for free. ValueObject<T> is for multi-field VOs: you override GetEqualityComponents() and yield return each field, and equality falls out. DisruptionDelayVo from the last lesson uses the multi-field base.

Practice

Try it yourself

Do

Write a value object from the pattern

Produce the pattern yourself, because you'll write these constantly.

Tick every step to confirm you did it.

Quiz

Why the constructor is private

Why does a Fusion value object make its constructor private?