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: Explicit Dependencies First
This tutorial starts one step before Flow<'env, 'error, 'value>. Define small interfaces, pass them explicitly, and compose a few operations before introducing an environment record.
Use this approach first when:
- the workflow is still local to one feature
- you want to prove the dependency boundaries before choosing an environment shape
- you want direct tests without building an environment first
1. Define The Contract
open System
open System.Threading.Tasks
type OrderId = OrderId of Guid
type Order =
{ Id: OrderId
Email: string
Total: decimal }
type PlaceOrderError =
| InvalidEmail
| OrderRejected of string
| TimedOut
| Cancelled
type IOrderRepository =
abstract Save : Order -> Task<Result<unit, string>>
type IEmailSender =
abstract SendConfirmation : Order -> Task
SystemThreadingTasks05-dependencies_05-tutorials_01-explicit-dependencies.md_page.OrderIdOrderIdSystem.GuidRepresents a globally unique identifier (GUID).
05-dependencies_05-tutorials_01-explicit-dependencies.md_page.OrderId: OrderIdEmail: stringstringAn abbreviation for the CLI type . Basic Types
Total: decimaldecimalAn abbreviation for the CLI type . Basic Types
05-dependencies_05-tutorials_01-explicit-dependencies.md_page.PlaceOrderErrorInvalidEmailOrderRejectedTimedOutCancelled05-dependencies_05-tutorials_01-explicit-dependencies.md_page.IOrderRepositorySave: IOrderRepository -> Order -> Task<Result<unit,string>>System.Threading.Tasks.Task`1Represents an asynchronous operation that can return a value. The type of the result produced by this .
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
05-dependencies_05-tutorials_01-explicit-dependencies.md_page.IEmailSenderSendConfirmation: IEmailSender -> Order -> TaskSystem.Threading.Tasks.TaskRepresents an asynchronous operation.
2. Compose Small Flows
let validateOrder (order: Order) : Result<Order, PlaceOrderError> =
if String.IsNullOrWhiteSpace order.Email then
Error InvalidEmail
else
Ok order
let saveOrder (orders: IOrderRepository) (order: Order) : Flow<unit, PlaceOrderError, Order> =
flow {
do!
Flow.fromTaskResult(fun _ -> orders.Save order)
|> Flow.mapError OrderRejected
return order
}
let sendConfirmation (email: IEmailSender) (order: Order) : Flow<unit, PlaceOrderError, unit> =
flow {
do!
ColdTask(fun _ ->
task {
do! email.SendConfirmation order
return ()
})
}
let placeOrder
(orders: IOrderRepository)
(email: IEmailSender)
(order: Order)
: Flow<unit, PlaceOrderError, OrderId> =
flow {
let! validOrder = validateOrder order
let! savedOrder = saveOrder orders validOrder
do! sendConfirmation email savedOrder
return savedOrder.Id
}
validateOrder: Order -> Result<Order,PlaceOrderError>order: Order05-dependencies_05-tutorials_01-explicit-dependencies.md_page.OrderMicrosoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
05-dependencies_05-tutorials_01-explicit-dependencies.md_page.PlaceOrderErrorSystem.StringRepresents text as a sequence of UTF-16 code units.
IsNullOrWhiteSpace: string -> boolIndicates whether a specified string is , empty, or consists only of white-space characters. The string to test. if the parameter is or , or if consists exclusively of white-space characters.
Email: stringErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
InvalidEmailOkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
saveOrder: IOrderRepository -> Order -> Flow<unit,PlaceOrderError,Order>orders: IOrderRepository05-dependencies_05-tutorials_01-explicit-dependencies.md_page.IOrderRepositoryAxial.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.
unitThe 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.
Axial.FlowfromTaskResult: (CancellationToken -> Task<Result<'value,'error>>) -> Flow<'env,'error,'value>Creates a flow from a cold task factory whose Error enters the typed error channel. The factory runs on each execution and receives the runtime cancellation token. Thrown exceptions are defects. .NET only
Save: Order -> Task<Result<unit,string>>(|>): '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
mapError: ('error -> 'nextError) -> Flow<'env,'error,'value> -> Flow<'env,'nextError,'value>Maps the error value of a synchronous flow. Transforms the error type of the flow while leaving successful values untouched. Useful for mapping internal errors into public-facing domain errors. The function to transform the error value. The source flow. A with the transformed error type. let flow = Flow.fail "error" |> Flow.mapError (fun err -> err + "!")
OrderRejectedsendConfirmation: IEmailSender -> Order -> Flow<unit,PlaceOrderError,unit>email: IEmailSender05-dependencies_05-tutorials_01-explicit-dependencies.md_page.IEmailSenderColdTasktask: TaskBuilderBuilds a task using computation expression syntax.
SendConfirmation: Order -> TaskplaceOrder: IOrderRepository -> IEmailSender -> Order -> Flow<unit,PlaceOrderError,OrderId>05-dependencies_05-tutorials_01-explicit-dependencies.md_page.OrderIdvalidOrder: OrdersavedOrder: OrderId: OrderId- pure validation stays in
Result - each dependency is passed explicitly
Flowis only used where async work and typed execution outcomes matter
3. Realistic Implementations
type SqlOrderRepository() =
interface IOrderRepository with
member _.Save order =
task {
// Imagine the real dependency here: DbConnection, EF Core, Dapper, etc.
printfn "Saving %A to the database" order.Id
return Ok ()
}
type SmtpEmailSender() =
interface IEmailSender with
member _.SendConfirmation order =
task {
// Imagine the real dependency here: SMTP client, SendGrid SDK, etc.
printfn "Sending order email to %s" order.Email
}
05-dependencies_05-tutorials_01-explicit-dependencies.md_page.SqlOrderRepository05-dependencies_05-tutorials_01-explicit-dependencies.md_page.IOrderRepository_: SqlOrderRepositorySave: SqlOrderRepository -> Order -> Task<Result<unit,string>>order: Ordertask: TaskBuilderBuilds a task using computation expression syntax.
printfn: Printf.TextWriterFormat<'T> -> 'TPrint to stdout using the given format, and add a newline. The formatter. The formatted result. See Printf.printfn (link: ) for examples.
Id: OrderIdOkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
05-dependencies_05-tutorials_01-explicit-dependencies.md_page.SmtpEmailSender05-dependencies_05-tutorials_01-explicit-dependencies.md_page.IEmailSender_: SmtpEmailSenderSendConfirmation: SmtpEmailSender -> Order -> TaskEmail: stringtype RecordingOrderRepository(saved: ResizeArray<Order>) =
interface IOrderRepository with
member _.Save order =
task {
saved.Add order
return Ok ()
}
type RecordingEmailSender(sent: ResizeArray<string>) =
interface IEmailSender with
member _.SendConfirmation order =
task {
sent.Add order.Email
}
05-dependencies_05-tutorials_01-explicit-dependencies.md_page.RecordingOrderRepositorysaved: ResizeArray<Order>ResizeArrayAn abbreviation for the CLI type
05-dependencies_05-tutorials_01-explicit-dependencies.md_page.Order05-dependencies_05-tutorials_01-explicit-dependencies.md_page.IOrderRepository_: RecordingOrderRepositorySave: RecordingOrderRepository -> Order -> Task<Result<unit,string>>order: Ordertask: TaskBuilderBuilds a task using computation expression syntax.
Add: Order -> unitAdds an object to the end of the . The object to be added to the end of the . The value can be for reference types.
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
05-dependencies_05-tutorials_01-explicit-dependencies.md_page.RecordingEmailSendersent: ResizeArray<string>stringAn abbreviation for the CLI type . Basic Types
05-dependencies_05-tutorials_01-explicit-dependencies.md_page.IEmailSender_: RecordingEmailSenderSendConfirmation: RecordingEmailSender -> Order -> TaskAdd: string -> unitAdds an object to the end of the . The object to be added to the end of the . The value can be for reference types.
Email: string5. Run The Flow
let runExample () = task {
let orders = SqlOrderRepository() :> IOrderRepository
let email = SmtpEmailSender() :> IEmailSender
let order =
{ Id = OrderId(Guid.NewGuid())
Email = "ada@example.com"
Total = 99.95m }
let! exit = placeOrder orders email order |> Flow.startTask ()
match exit with
| Exit.Success orderId ->
printfn "Placed %A" orderId
| Exit.Failure (Cause.Fail InvalidEmail) ->
printfn "The order was rejected before any dependency was called."
| Exit.Failure (Cause.Fail (OrderRejected reason)) ->
printfn "The repository rejected the order: %s" reason
| Exit.Failure Cause.Interrupt ->
printfn "The workflow was interrupted."
| Exit.Failure cause ->
printfn "Unexpected failure: %s" (Cause.prettyPrint (function OrderRejected r -> r | _ -> "domain error") cause)
}
runExample: unit -> Task<unit>task: TaskBuilderBuilds a task using computation expression syntax.
orders: IOrderRepository``.ctor``: unit -> SqlOrderRepository05-dependencies_05-tutorials_01-explicit-dependencies.md_page.IOrderRepositoryemail: IEmailSender``.ctor``: unit -> SmtpEmailSender05-dependencies_05-tutorials_01-explicit-dependencies.md_page.IEmailSenderorder: OrderId: OrderIdOrderIdSystem.GuidRepresents a globally unique identifier (GUID).
NewGuid: unit -> GuidInitializes a new instance of the structure. A new GUID object.
Email: stringTotal: decimalexit: Exit<OrderId,PlaceOrderError>placeOrder: IOrderRepository -> IEmailSender -> Order -> Flow<unit,PlaceOrderError,OrderId>(|>): '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.FlowstartTask: 'env -> Flow<'env,'error,'value> -> Task<Exit<'value,'error>>Starts the workflow immediately and returns a task handle for its final exit. The work is already in flight when this returns. Use Flow.toAsync for a cold handle. The environment used by the workflow. The workflow to start. A task that completes with the workflow exit. let running = workflow |> Flow.startTask environment
Axial.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.
orderId: OrderIdprintfn: Printf.TextWriterFormat<'T> -> 'TPrint to stdout using the given format, and add a newline. The formatter. The formatted result. See Printf.printfn (link: ) for examples.
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.
InvalidEmailOrderRejectedreason: stringInterruptAn administrative signal to stop the workflow (e.g., cancellation).
cause: Cause<PlaceOrderError>Axial.CauseprettyPrint: ('error -> string) -> Cause<'error> -> stringPretty prints a cause tree for diagnostics.
r: stringPassing two dependencies explicitly is fine. Passing five through every helper is not.
That is the point where you move to an environment record:
- the workflow code still depends on the same interfaces
- the execution boundary gets cleaner
- adding a third dependency becomes additive instead of rewriting every call site
Continue with Tutorial: App Record.

