How To: Functional Orleans Application
Build a typed Orleans application with the current Orleans.FSharp API.
How To: Build a Functional Orleans Application
Section titled “How To: Build a Functional Orleans Application”This tutorial builds a small typed counter with the current Orleans.FSharp API.
1. Create the project
Section titled “1. Create the project”dotnet new install Orleans.FSharp.Templatesdotnet new orleans-fsharp -n MyDistributedAppcd MyDistributedAppThe template creates an F# application and tests. You do not need to write a C# proxy interface or a source-generation bridge.
2. Define the typed API
Section titled “2. Define the typed API”open System.Threading.Tasksopen Orleans.FSharp
type CounterActor = private CounterActor of unit
[<NoEquality; NoComparison>]type CounterApi = { increment: unit -> Task<int> value: unit -> Task<int> }
module CounterApi = let contract = grainContract<CounterActor, string, CounterApi> { grainType "counter" version 1 stringKey readOnly (_.value) }
let ref = FunctionalGrain.ref contractThe actor brand keeps unrelated contracts distinct. grainType is the durable wire identity, and stringKey defines how application keys map to Orleans grain keys.
3. Define behavior
Section titled “3. Define behavior”let counterDefinition = grainFor CounterApi.contract { defaultState (fun () -> 0)
handle (_.increment) (fun _ctx count () -> task { let next = count + 1 return next, next })
handleQuery (_.value) (fun _ctx count () -> task { return count }) }Each record field has one handler. State transitions are explicit values; handleQuery returns a reply without replacing state.
4. Configure and register the silo
Section titled “4. Configure and register the silo”open Microsoft.Extensions.Hostingopen Orleans.Hostingopen Orleans.FSharp.Runtime
let config = siloConfig { useLocalhostClustering addMemoryStorage "Default" }
let builder = HostApplicationBuilder()SiloConfig.applyToHost config builder
builder.UseOrleans(fun siloBuilder -> siloBuilder.AddFunctionalGrain(counterDefinition) |> ignore)|> ignore
let host = builder.Build()host.Start()A client-only process calls AddFunctionalGrainClient() on its Orleans client builder instead.
5. Call the actor
Section titled “5. Call the actor”open Microsoft.Extensions.DependencyInjectionopen Orleans
let factory = host.Services.GetRequiredService<IGrainFactory>()let counter = CounterApi.ref factory "visits"
let first = counter.increment().GetAwaiter().GetResult()let current = counter.value().GetAwaiter().GetResult()The call site is the API record itself. There is no boxed command or untyped reply.
6. Test through a real activation
Section titled “6. Test through a real activation”Register the same definition in an Orleans TestingHost fixture, obtain CounterApi.ref fixture.Client "test", and assert replies through the public API. Keep context-free handler functions named separately when you also want fast pure unit tests.
See Testing for a complete fixture.
7. Add production capabilities
Section titled “7. Add production capabilities”- Durable state: create a
PersistentStatedescriptor and attach it withstateFrom. - Event sourcing: replace
grainForwithjournaledGrainForand provide a pureapplyfold. - Streams and broadcasts: add
onStreamoronBroadcastto the definition. - Timers and reminders: add
onTimeroronReminder. - Dashboard: add the package,
addDashboard, and map the dashboard endpoint.