Tutorial: Creating Reusable Services

Define your own named service contracts and consume them through IHas and Service.get.

Use a custom service when several workflows should depend on the same named contract without being tied to one concrete app record field name.

Define The Contract

open System.Threading.Tasks
open FsFlow

type IExchangeRates =
    abstract GetUsdToAud : unit -> Task<decimal>

Write A Reusable Helper

let priceInAud<'env, 'error when 'env :> IHas<IExchangeRates>>
    (usdAmount: decimal)
    : Flow<'env, 'error, decimal> =
    flow {
        let! rates = Service<IExchangeRates>.get()
        let! rate = rates.GetUsdToAud()
        return usdAmount * rate
    }

This helper no longer cares whether the caller stores the service in Rates, Runtime.ExchangeRates, or any other field. It only needs IHas<IExchangeRates>.

Provide An App Environment

type AppEnv =
    { Rates: IExchangeRates
      Region: string }

    interface IHas<IExchangeRates> with
        member this.Service = this.Rates

Use A Test Double

type FixedRates(rate: decimal) =
    interface IExchangeRates with
        member _.GetUsdToAud() = Task.FromResult rate

Now every workflow that depends on IExchangeRates can run against the same deterministic test implementation.

This is the main step from “an app record for one workflow” to “reusable helpers shared across workflows.”