Functional Architecture and Dependency Injection

Other patterns: the Reader monad (environment passing), free monads/interpreters, Elmish/MVU for UI state (Module 48), and event sourcing with unions as events.

// 1. Functional core, imperative shell: pure domain logic, I/O at the edges
let calculateDiscount (total: decimal) (loyaltyYears: int) =        // pure
    if loyaltyYears > 5 then total * 0.10m elif total > 100m then total * 0.05m else 0m

let processOrder (loadCustomer: int -> Async<int>) (saveTotal: decimal -> Async<unit>) customerId total =
    async {                                                          // impure shell
        let! years = loadCustomer customerId
        let discount = calculateDiscount total years
        do! saveTotal (total - discount)
    }

// 2. Dependency injection by partial application
let realLoad (id: int) = async { return 7 }
let realSave (t: decimal) = async { printfn "saved %M" t }
let processOrderLive = processOrder realLoad realSave               // wire dependencies at the composition root
processOrderLive 1 200m |> Async.RunSynchronously

// 3. Dependencies as a record of functions ("environment")
type Deps = { LoadCustomer: int -> Async<int>; SaveTotal: decimal -> Async<unit>; Log: string -> unit }
let processWith (deps: Deps) id total = async {
    deps.Log $"processing {id}"
    let! years = deps.LoadCustomer id
    do! deps.SaveTotal
…