Domain Modeling with Types

Type signatures become living documentation of the domain. The compiler enforces business rules, and many unit tests become unnecessary.

// Make illegal states unrepresentable (Scott Wlaschin, "Domain Modeling Made Functional")

// 1. Constrained types with private constructors and smart constructors
type EmailAddress = private EmailAddress of string
module EmailAddress =
    let create (s: string) =
        if s.Contains "@" && s.Length <= 254 then Ok (EmailAddress s)
        else Error $"Invalid email: {s}"
    let value (EmailAddress s) = s

type Quantity = private Quantity of int
module Quantity =
    let create n = if n > 0 && n <= 1000 then Ok (Quantity n) else Error "Quantity must be 1-1000"
    let value (Quantity n) = n

// 2. Unions for states that are mutually exclusive
type ContactInfo =
    | EmailOnly of EmailAddress
    | PhoneOnly of phone: string
    | EmailAndPhone of EmailAddress * phone: string     // "at least one contact method" is enforced

// 3. State machines as types
type UnvalidatedOrder = { Id: string; Lines: (string * int) list }
type ValidatedOrder = { Id: string; Lines: (string * Quantity) list }
type PricedOrder = { Id: string; Lines: (string * Quantity * decimal) list; Total: decimal }

type Order =
    | Unvalidated of UnvalidatedOrder
    | Validated of ValidatedOrder
    | Priced 
…