Repository F# setup
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Layers
open Axial.Console
open Axial.FileSystem
open Axial.Hosting
open Axial.Hosting.Browser
open Axial.Hosting.Node
open Axial.HttpClient
open Axial.PlatformService
open Axial.Process
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScript

Dependencies

Start with ordinary function arguments. Reach for an environment when several workflows need the same dependencies and threading them through unrelated callers has become noise.

Then pass Flow a record. A workflow states what it needs in its environment channel; you build that record and hand it over when the workflow runs:

type AppEnv =
    { Users: IUserStore
      Audit: IAuditLog }

let loadUser id : EnvFlow<AppEnv, User> =
    flow {
        let! users = Flow.envWith _.Users
        return! users.Load id
    }

let exit = loadUser userId |> Flow.run { Users = liveUsers; Audit = liveAudit }
That is the whole mechanism for most applications. There is no container, no registration, and no resolution step — a record is a record, and a test supplies a different one with fakes in place of the live services.

Two things build on it, and neither is needed to start. Contracts let a package ask for a service without knowing your record type; that is how Console.writeLine and the rest of the built-in services work, and how you would publish your own. Layers are for provisioning that is itself effectful — see layers, a separate package.

In this section

  1. The environment — what 'env actually is, and the functions that read it.
  2. Choosing an approach — arguments, records, contracts, and layers compared.
  3. Service contracts — how a package asks for a dependency it cannot name.
  4. Providing the environment — building the value at a host boundary.
  5. Tutorials — the same material worked end to end.

For the services Axial already implements — the clock, console, file system, processes, and HTTP — see built-in services.