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.JavaScriptTutorial: Composing Built-in Services
Every page in this section shows one service in isolation, constrained by its own IHasX interface. A real
application wants several of them at once, alongside its own dependencies — and it wants to build that combined
environment without repeating itself. This tutorial builds that environment.
The problem
BaseRuntime bundles the five platform services and already implements
IHasClock, IHasLog, IHasRandom, IHasGuid, and IHasEnvironmentVariables. Embedding it as a field of your
own record does not carry those interface implementations with it — F# has no mechanism for one type to forward
another type's interfaces automatically. Your own environment record has to state, once per service, where that
service lives:
open Axial.PlatformService
type AppEnv =
{ Runtime: BaseRuntime }
interface IHasClock with
member this.Clock = this.Runtime.Clock
interface IHasLog with
member this.Log = this.Runtime.Log
interface IHasEnvironmentVariables with
member this.EnvironmentVariables = this.Runtime.EnvironmentVariables
AxialPlatformService06-services_06-existing-services.md_page.AppEnvRuntime: BaseRuntimeAxial.PlatformService.BaseRuntimeGroups the standard operational services commonly used by workflow hosts.
Axial.PlatformService.IHasClockDeclares that an environment supplies the clock service.
this: AppEnvClock: AppEnv -> unit -> IClockClock: IClockAxial.PlatformService.IHasLogDeclares that an environment supplies the logging service.
Log: AppEnv -> unit -> ILogLog: ILogAxial.PlatformService.IHasEnvironmentVariablesDeclares that an environment supplies the environment-variable service.
EnvironmentVariables: AppEnv -> unit -> IEnvironmentVariablesEnvironmentVariables: IEnvironmentVariablesEach line is a delegation, not a computation — member this.Clock = this.Runtime.Clock just tells the compiler
which field satisfies which contract. Declare an interface member for every service the application actually uses;
skip the ones it does not, the same way you would skip a field it does not need. This is boilerplate, but it is
boilerplate you write once, at the boundary, rather than something that spreads through the workflow code.
Use the services
Nothing about calling a service changes because it arrived through Runtime instead of a top-level field. The
workflow names the contract, not the storage:
let loadMode : Flow<AppEnv, EnvironmentVariableError, string> =
flow {
let! now = Clock.utcDateTime
let! mode = EnvironmentVariable.get "APP_MODE"
do! Log.info $"[{now:O}] starting in mode {mode}"
return mode
}
loadMode: Flow<AppEnv,EnvironmentVariableError,string>Axial.Flow`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
06-services_06-existing-services.md_page.AppEnvAxial.PlatformService.EnvironmentVariableErrorstringAn abbreviation for the CLI type . Basic Types
flow: FlowBuilderThe universal flow { } computation expression.
now: DateTimeAxial.PlatformService.ClockHelpers for the clock service.
utcDateTime: Flow<'env,'error,DateTime>Reads the current UTC date/time from an explicit clock service.
mode: stringAxial.PlatformService.EnvironmentVariableHelpers for reading and parsing environment variables through an explicit service.
get: string -> Flow<'env,EnvironmentVariableError,string>Reads a raw string environment variable through an explicit service.
Axial.PlatformService.LogHelpers for the logging service.
info: string -> Flow<'env,'error,unit>Writes an informational log message through an explicit logging service.
loadMode does not know Clock and Log both come from the same Runtime field while EnvironmentVariable
does too — it only knows the three interfaces. Swap AppEnv for any other type that implements them and the
workflow is unchanged.
Add your own dependencies alongside it
AppEnv is an ordinary record, so extending it with an application-specific dependency is the same pattern as
the app record tutorial — add a field, add an interface if other
helpers should depend on the contract rather than the field name directly:
type AppEnv =
{ Runtime: BaseRuntime
Orders: IOrderRepository }
interface IHasClock with
member this.Clock = this.Runtime.Clock
interface IHasLog with
member this.Log = this.Runtime.Log
interface IHasEnvironmentVariables with
member this.EnvironmentVariables = this.Runtime.EnvironmentVariablesA workflow that reads Orders directly (Flow.envWith _.Orders) is coupled to this record's field name. If you
want Orders reusable behind a named contract instead — the same way Clock and Log are — see
Tutorial: Creating Reusable Services.
Run it
let run () = task {
let env = { Runtime = BaseRuntime.liveValue; Orders = SqlOrderRepository() }
let! exit = loadMode |> Flow.startTask env
printfn "%A" exit
}Test it
Nothing changes about substituting test doubles either. BaseRuntime's fields each accept the fixed value shown in
deterministic implementations, so a test builds the
whole environment as one value literal, with no interface to reimplement:
let testEnv =
{ Runtime =
{ Clock = Clock.fromValue (DateTimeOffset.Parse "2026-01-01T00:00:00Z")
Log = Log.live
Random = Random.fromValue 7
Guid = Guid.fromValue (Guid.Parse "00000000-0000-0000-0000-000000000001")
EnvironmentVariables = EnvironmentVariables.fromPairs [ "APP_MODE", "diagnostic" ] }
Orders = RecordingOrders(ResizeArray()) }If you already have several standard services in play, wrapping them once in an app environment like this is usually the cleanest boundary. Continue with Tutorial: Creating Reusable Services when you need your own service contract alongside the built-in ones, or with Layers when building the environment itself becomes effectful.

