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: IEnvironmentVariablesUse 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.
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.EnvironmentVariablesRun it
let run () = task {
let env = { Runtime = BaseRuntime.liveValue; Orders = SqlOrderRepository() }
let! exit = loadMode |> Flow.startTask env
printfn "%A" exit
}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()) }
