Skip to content

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.

Terminal window
dotnet new install Orleans.FSharp.Templates
dotnet new orleans-fsharp -n MyDistributedApp
cd MyDistributedApp

The template creates an F# application and tests. You do not need to write a C# proxy interface or a source-generation bridge.

open System.Threading.Tasks
open 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 contract

The actor brand keeps unrelated contracts distinct. grainType is the durable wire identity, and stringKey defines how application keys map to Orleans grain keys.

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.

open Microsoft.Extensions.Hosting
open Orleans.Hosting
open 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.

open Microsoft.Extensions.DependencyInjection
open 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.

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.

  • Durable state: create a PersistentState descriptor and attach it with stateFrom.
  • Event sourcing: replace grainFor with journaledGrainFor and provide a pure apply fold.
  • Streams and broadcasts: add onStream or onBroadcast to the definition.
  • Timers and reminders: add onTimer or onReminder.
  • Dashboard: add the package, addDashboard, and map the dashboard endpoint.