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.JavaScriptService Contracts
A record works because you own both sides: the workflow names AppEnv, and you supply an AppEnv. A package
author cannot do that. Axial.Console is compiled long before your AppEnv exists, so Console.writeLine cannot
mention it.
A contract is how the package asks anyway. It is an ordinary interface — one named IHasFoo, exposing a single
member Foo:
type IHasOrders =
abstract Orders : IOrderRepository[<RequireQualifiedAccess>]
module Orders =
let service<'env, 'error when 'env :> IHasOrders> : Flow<'env, 'error, IOrderRepository> =
Flow.envWith _.Orders
let save order : Flow<#IHasOrders, CheckoutError, unit> =
flow {
let! orders = Orders.service
do! orders.Save order
}Supplying one
Your record implements the contracts it satisfies. One line each, and the fields keep whatever names you gave them:
type AppEnv =
{ Orders: IOrderRepository
Email: IEmailSender }
interface IHasOrders with member this.Orders = this.Orders
interface IHasEmail with member this.Email = this.EmailNeeding more than one
Contracts are distinct interfaces, so a workflow can require several and the constraints merge on their own:
let submit<'env when 'env :> IHasOrders and 'env :> IHasEmail> order : Flow<'env, CheckoutError, unit> =
flow {
let! orders = Orders.service
let! email = Email.service
do! orders.Save order
do! email.SendConfirmation order
}type ICheckoutEnv =
inherit IHasOrders
inherit IHasEmail
let submit order : Flow<#ICheckoutEnv, CheckoutError, unit> = ...When to declare one
For application code, don't. A record is simpler, needs no interface, and is what the environment documents.
Declare a contract when you are publishing a helper whose callers you will never see — a shared library, or a
package like Axial.FileSystem. That is the case a record genuinely cannot cover, and it is the whole reason the
mechanism exists.
Writing a package that ships services is covered in providing services from a package.

