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.JavaScript

Service 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
The helper constrains its environment to that interface instead of naming a type:
[<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
    }
Read `Flow<#IHasOrders, …>` as "any environment that can give me an `IOrderRepository`". This is still just [`Flow.envWith`](the-environment.html) — the interface only says which member it may read.

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.Email
`member this.Orders = this.Orders` is not recursive. F# interface implementations are always explicit, so the interface member is not in scope on the record itself and the right-hand side resolves to the field. A record with no such field fails to compile rather than looping.

Needing 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
    }
When a combination recurs, name it and a single constraint covers it:
type ICheckoutEnv =
    inherit IHasOrders
    inherit IHasEmail

let submit order : Flow<#ICheckoutEnv, CheckoutError, unit> = ...
`#IHasOrders` carries exactly one constraint, which is why the aggregate exists. Use the explicit `<'env when 'env :> … and 'env :> …>` form when you need several, or when the environment appears more than once in a signature — each occurrence of `#T` is a separate type variable.

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.