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
…