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
…