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.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScriptChoosing an Approach
Axial has one dependency model: workflows read an explicit environment, reusable helpers name service contracts, and layers build the environment at the boundary.
Use this order:
- Use records plus
Flow.envWithfor most application code. - Declare a per-service contract for reusable named services.
- Use
LayerandLayer.provideto build environments and own resource cleanup. - Use
ServiceProvider.getonly at .NET host edges where directIServiceProviderlookup is intentional.
Default Shape
Plain F# records make dependencies visible and let tests supply fakes.
Show page setup
// Setup for the checked examples on this page.
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.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScript
/// Fails the docs test when an example's result differs from the value shown.
let shouldEqual expected actual =
if actual <> expected then failwithf "Expected %A but got %A" expected actualtype Order = { Id: int }
type OrderError = DuplicateOrder of int
type IOrderRepo =
abstract Save: Order -> Result<unit, OrderError>
type IEmailSender =
abstract SendConfirmation: Order -> unit
type ApiDeps = { Orders: IOrderRepo; Email: IEmailSender }
let placeOrder (order: Order) : Flow<ApiDeps, OrderError, unit> =
flow {
let! orders = Flow.envWith _.Orders
let! email = Flow.envWith _.Email
do! orders.Save order
email.SendConfirmation order
}
FsLiveDocsGeneratedPage18_DBB10DAB81D5.OrderId: intintAn abbreviation for the CLI type . Basic Types
FsLiveDocsGeneratedPage18_DBB10DAB81D5.OrderErrorDuplicateOrderFsLiveDocsGeneratedPage18_DBB10DAB81D5.IOrderRepoSave: IOrderRepo -> Order -> Result<unit,OrderError>Microsoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
FsLiveDocsGeneratedPage18_DBB10DAB81D5.IEmailSenderSendConfirmation: IEmailSender -> Order -> unitFsLiveDocsGeneratedPage18_DBB10DAB81D5.ApiDepsOrders: IOrderRepoEmail: IEmailSenderplaceOrder: Order -> Flow<ApiDeps,OrderError,unit>order: OrderAxial.Flow`3flow: FlowBuilderThe universal flow { } computation expression.
orders: IOrderRepoAxial.FlowenvWith: ('env -> 'value) -> Flow<'env,'error,'value>Projects one value from the current environment. This is the primary way to access app dependencies, configuration, or request metadata stored in env. The projection runs only when the flow is executed, so constructing the flow is still pure and side-effect free. Prefer small projections over passing a large environment deeper into reusable helpers. A function that extracts a value from the environment. A containing the projected value. let currentTime () = Flow.envWith (fun (environment: BaseRuntime) -> environment.Clock.UtcNow())
_arg1: ApiDepsemail: IEmailSender_arg3: ApiDepsSave: Order -> Result<unit,OrderError>SendConfirmation: Order -> unitA test supplies the same record with fakes:
type MemoryOrders() =
let gate = obj()
let mutable saved = []
member _.Saved = lock gate (fun () -> List.rev saved)
interface IOrderRepo with
member _.Save order =
lock gate (fun () ->
if List.contains order.Id saved then
Error(DuplicateOrder order.Id)
else
saved <- order.Id :: saved
Ok())
type MemoryEmail() =
let gate = obj()
let mutable sent = []
member _.Sent = lock gate (fun () -> List.rev sent)
interface IEmailSender with
member _.SendConfirmation order = lock gate (fun () -> sent <- order.Id :: sent)
FsLiveDocsGeneratedPage18_DBB10DAB81D5.MemoryOrdersgate: obj``.ctor``: unit -> unitInitializes a new instance of the class.
saved: int list_: MemoryOrdersSaved: MemoryOrders -> unit -> int listlock: 'Lock -> (unit -> 'T) -> 'TExecute the function as a mutual-exclusion region using the input value as a lock. The object to be locked. The action to perform during the lock. The resulting value. open System.Linq /// A counter object, supporting unlocked and locked increment type TestCounter () = let mutable count = 0 /// Increment the counter, unlocked member this.IncrementWithoutLock() = count <- count + 1 /// Increment the counter, locked member this.IncrementWithLock() = lock this (fun () -> count <- count + 1) /// Get the count member this.Count = count let counter = TestCounter() // Create a parallel sequence to that uses all our CPUs (seq {1..100000}).AsParallel() .ForAll(fun _ -> counter.IncrementWithoutLock()) // Evaluates to a number between 1-100000, non-deterministically because there is no locking counter.Count let counter2 = TestCounter() // Create a parallel sequence to that uses all our CPUs (seq {1..100000}).AsParallel() .ForAll(fun _ -> counter2.IncrementWithLock()) // Evaluates to 100000 deterministically because the increment to the counter object is locked counter2.Count
Microsoft.FSharp.Collections.ListModuleContains operations for working with values of type . Operations for collections such as lists, arrays, sets, maps and sequences. See also F# Collection Types in the F# Language Guide.
rev: 'T list -> 'T listReturns a new list with the elements in reverse order. The input list. The reversed list. let inputs = [ 0; 1; 2 ] inputs |> List.rev Evaluates to [ 2; 1; 0 ]. This is an O(n) operation, where n is the length of the list.
FsLiveDocsGeneratedPage18_DBB10DAB81D5.IOrderRepoSave: MemoryOrders -> Order -> Result<unit,OrderError>order: Ordercontains: 'T -> 'T list -> boolTests if the list contains the specified element. The value to locate in the input list. The input list. True if the input list contains the specified element; false otherwise. [1..9] |> List.contains 0 Evaluates to false. [1..9] |> List.contains 3 Evaluates to true. let input = [1, "SpongeBob"; 2, "Patrick"; 3, "Squidward"; 4, "Mr. Krabs"] input |> List.contains (2, "Patrick") Evaluates to true. let input = [1, "SpongeBob"; 2, "Patrick"; 3, "Squidward"; 4, "Mr. Krabs"] input |> List.contains (22, "Patrick") Evaluates to false. This is an O(n) operation, where n is the length of the list.
Id: intErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
DuplicateOrder(::)OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
FsLiveDocsGeneratedPage18_DBB10DAB81D5.MemoryEmailsent: int list_: MemoryEmailSent: MemoryEmail -> unit -> int listFsLiveDocsGeneratedPage18_DBB10DAB81D5.IEmailSenderSendConfirmation: MemoryEmail -> Order -> unitlet orders = MemoryOrders()
let email = MemoryEmail()
let deps = { Orders = orders; Email = email }
placeOrder { Id = 1 } |> Flow.run deps |> shouldEqual (Exit.Success())
placeOrder { Id = 1 } |> Flow.run deps |> shouldEqual (Exit.Failure(Cause.Fail(DuplicateOrder 1)))
email.Sent |> shouldEqual [ 1 ]
orders: MemoryOrders``.ctor``: unit -> MemoryOrdersemail: MemoryEmail``.ctor``: unit -> MemoryEmaildeps: ApiDepsOrders: IOrderRepoEmail: IEmailSenderplaceOrder: Order -> Flow<ApiDeps,OrderError,unit>Id: int(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Axial.Flowrun: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
shouldEqual: 'a -> 'a -> unitAxial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
SuccessThe workflow completed successfully.
FailureThe workflow failed due to a specific cause.
Axial.Cause`1Represents the cause of a failed workflow. The type of the domain-specific failure value.
FailAn expected domain-specific failure.
DuplicateOrderSent: int listUse a concrete record for the boundary. Add a named abstraction only when more than one implementation or caller needs it.
Service Contracts
Reusable helpers can ask for a named service without forcing every application to use the same record shape:
type IHasOrders =
abstract OrderRepo: IOrderRepo
let orderRepo<'env, 'error when 'env :> IHasOrders> : Flow<'env, 'error, IOrderRepo> =
Flow.envWith _.OrderRepo
let save (order: Order) : Flow<#IHasOrders, OrderError, unit> =
flow {
let! orders = orderRepo
do! orders.Save order
}
FsLiveDocsGeneratedPage18_DBB10DAB81D5.IHasOrdersOrderRepo: IHasOrders -> unit -> IOrderRepoFsLiveDocsGeneratedPage18_DBB10DAB81D5.IOrderRepoorderRepo: Flow<'env,'error,IOrderRepo>enverrorAxial.Flow`3Axial.FlowenvWith: ('env -> 'value) -> Flow<'env,'error,'value>Projects one value from the current environment. This is the primary way to access app dependencies, configuration, or request metadata stored in env. The projection runs only when the flow is executed, so constructing the flow is still pure and side-effect free. Prefer small projections over passing a large environment deeper into reusable helpers. A function that extracts a value from the environment. A containing the projected value. let currentTime () = Flow.envWith (fun (environment: BaseRuntime) -> environment.Clock.UtcNow())
_arg1: 'envOrderRepo: IOrderReposave: Order -> Flow<'a,OrderError,unit>order: OrderFsLiveDocsGeneratedPage18_DBB10DAB81D5.OrderFsLiveDocsGeneratedPage18_DBB10DAB81D5.OrderErrorunitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
orders: IOrderRepoSave: Order -> Result<unit,OrderError>Any environment that implements IHasOrders can run save:
type CheckoutEnv =
{ Orders: IOrderRepo }
interface IHasOrders with
member this.OrderRepo = this.Orders
FsLiveDocsGeneratedPage18_DBB10DAB81D5.CheckoutEnvOrders: IOrderRepoFsLiveDocsGeneratedPage18_DBB10DAB81D5.IOrderRepoFsLiveDocsGeneratedPage18_DBB10DAB81D5.IHasOrdersthis: CheckoutEnvOrderRepo: CheckoutEnv -> unit -> IOrderReposave { Id = 7 } |> Flow.run { Orders = MemoryOrders() } |> shouldEqual (Exit.Success())
save: Order -> Flow<'a,OrderError,unit>Id: int(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Axial.Flowrun: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
Orders: IOrderRepo``.ctor``: unit -> MemoryOrdersshouldEqual: 'a -> 'a -> unitAxial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
SuccessThe workflow completed successfully.
Layers
Layers build explicit environments and own cleanup through Scope. Use layer { } when application startup needs to
combine several services into one environment.
type AppEnv = { Runtime: BaseRuntime; Orders: IOrderRepo }
let ordersLayer : Layer<unit, OrderError, IOrderRepo> = Layer.succeed (MemoryOrders())
let appLayer : Layer<unit, OrderError, AppEnv> =
layer {
let! runtime = BaseRuntime.live |> Layer.widenError
and! orders = ordersLayer
return { Runtime = runtime; Orders = orders }
}
FsLiveDocsGeneratedPage18_DBB10DAB81D5.AppEnvRuntime: BaseRuntimeAxial.PlatformService.BaseRuntimeGroups the standard operational services commonly used by workflow hosts.
Orders: IOrderRepoFsLiveDocsGeneratedPage18_DBB10DAB81D5.IOrderRepoordersLayer: Layer<unit,OrderError,IOrderRepo>Axial.Layers.Layer`3Represents a provisioning step that builds an explicit environment inside a scope. The input environment required to build the layer. The typed failure produced during provisioning. The environment or service bundle produced by the layer.
unitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
FsLiveDocsGeneratedPage18_DBB10DAB81D5.OrderErrorAxial.Layers.LayerModulesucceed: 'output -> Layer<'input,'error,'output>Creates a layer that succeeds with a fixed output value.
``.ctor``: unit -> MemoryOrdersappLayer: Layer<unit,OrderError,AppEnv>layer: LayerBuilderThe layer { } computation expression for provisioning explicit service environments.
runtime: BaseRuntimeAxial.PlatformService.BaseRuntimeModuleHelpers for constructing the standard explicit service bundle used by workflow hosts.
live: Layer<unit,Never,BaseRuntime>Builds the standard live base runtime as an explicit service bundle.
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
widenError: Layer<'input,Never,'output> -> Layer<'input,'error,'output>Gives a layer that cannot fail with a typed error any error type. Layer.provide and the layer combinators require one error type. Widen a layer that cannot fail, such as BaseRuntime.live, to combine it with layers or workflows that can. let runtime : Layer<unit, string, int> = Layer.succeed 1 |> Layer.widenError
orders: IOrderRepoLayer.provide builds the environment, runs the workflow in it, and releases whatever the layers acquired:
let saveInApp : Flow<AppEnv, OrderError, unit> = save { Id = 3 } |> Flow.localEnv (fun app -> { Orders = app.Orders })
saveInApp |> Layer.provide appLayer |> Flow.run () |> shouldEqual (Exit.Success())
saveInApp: Flow<AppEnv,OrderError,unit>Axial.Flow`3FsLiveDocsGeneratedPage18_DBB10DAB81D5.AppEnvFsLiveDocsGeneratedPage18_DBB10DAB81D5.OrderErrorunitThe type 'unit', which has only one value "()". This value is special and always uses the representation 'null'. Basic Types
save: Order -> Flow<'a,OrderError,unit>Id: int(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Axial.FlowlocalEnv: ('outerEnvironment -> 'innerEnvironment) -> Flow<'innerEnvironment,'error,'value> -> Flow<'outerEnvironment,'error,'value>Runs a flow against an environment derived from the outer environment. Use this to embed a smaller workflow inside a larger application environment without changing the smaller workflow's type. The mapping is applied at execution time. This is useful for preserving narrow helper signatures while still running everything from one app boundary. A function that maps the outer environment to the inner environment. The flow to run with the inner environment. A flow that expects the outer environment. let flow = Flow.succeed 1 |> Flow.localEnv (fun outer -> outer)
app: AppEnvOrders: IOrderRepoAxial.Layers.LayerModuleprovide: Layer<'input,'error,'environment> -> Flow<'environment,'error,'value> -> Flow<'input,'error,'value>Builds an environment with a layer, runs a downstream flow, and always closes the layer scope. This is the provisioning boundary. It creates a fresh scope, builds the supplied layer inside that scope, runs the downstream flow with the built environment, and finalizes all acquired resources when the downstream flow completes or fails. The layer that builds the downstream environment. The flow to run with the provided environment. A flow that requires only the input environment of the layer. let program () = let runtimeLayer = Layer.succeed "production" let workflow = Flow.envWith String.length Layer.provide runtimeLayer workflow
appLayer: Layer<unit,OrderError,AppEnv>run: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
shouldEqual: 'a -> 'a -> unitAxial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
SuccessThe workflow completed successfully.
Use layers when construction can fail, when resources need cleanup, or when a host container should be validated once at
startup. Plain let! is sequential and dependent; sibling and! bindings are independent and use Layer.merge.
Tutorials
Start with App Record and Layers.

