The unconstructable-invalid idiom
A value object is a small immutable thing defined entirely by its values: a flight number, a percentage, a note. It has no identity of its own. The DDD move that matters most in Fusion is this: a value object cannot exist in an invalid state. You make that true by hiding the constructor and forcing every caller through a validating factory.
Concretely: the constructor is private, and the only way in is public static Result<T> Create(...). The factory validates and either hands back a valid instance or a failure. Because there’s no public new, an invalid value object simply cannot be built anywhere in the codebase. The type system carries the invariant.
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()));
}Compare this to the Nest/Next instinct: a class-validator DTO with public fields, validated by decorators that a caller has to remember to run, or a plain object you new and hope was checked upstream. In that world, an invalid object exists the moment you construct it and validity is a runtime hope. Here, validity is a construction-time guarantee.
Try it yourself
Make an invalid VO impossible
Feel the guarantee in your hands. Sketch a VO and try to break it.
- Write a
sealed class AirportCodeVowith aprivateconstructor taking astring - Add
public static Result<AirportCodeVo> Create(string? value)that fails if the value isn't 3 letters, elseOrSuccess - Now try to write
var a = new AirportCodeVo("XX");somewhere else and note the compiler refuses (the ctor is private) - Confirm the ONLY way to get one is through
Create, which returns a Result you must inspect
Why the private constructor
State the guarantee it buys.
Why does a Fusion value object make its constructor private and expose only a static Create that returns a Result?
Reveal answer
So an invalid value object is unconstructable. With no public new, every caller must go through the validating factory and handle the Result, so the invariant is enforced by the type system rather than by everyone remembering to validate. That's anti-pattern #25.