Skip to content

Orleans.FSharp vs Alternatives — F# Actor Frameworks Compared

Comparison of Orleans.FSharp, raw C# Microsoft Orleans, Akkling (Akka.NET), and Proto.Actor for F# distributed systems

Choosing an actor framework for F# distributed systems? This page compares Orleans.FSharp with the main alternatives: using Microsoft Orleans directly from C#/F#, Akkling (F# API for Akka.NET), and Proto.Actor.

Orleans.FSharpC# Orleans (from F#)Akkling (Akka.NET)Proto.Actor
Actor modelVirtual actorsVirtual actorsClassic actorsVirtual + classic
F# APITyped API records with grainContract / grainFor; siloConfig {} for hostingManual interop (class inheritance)Native CEs (actorOf, spawnAnonymous)None (C# API)
State persistenceTyped facets (usePersistentState)Automatic (attribute)ManualManual
Type safetyCompile-time checked API records, DU stateRuntime errorsTyped messagesRuntime errors
ClusteringBuilt-in (Redis, Azure, Kubernetes)Built-inAkka.ClusterBuilt-in
.NET version.NET 10.NET 10.NET 6+.NET 6+
TestingGrainArbitrary + FsCheckManual mockingTestKitManual mocking
Backed byCommunity (MIT)MicrosoftCommunityCommunity
MaintenanceActiveActiveMaintenance modeActive

Orleans.FSharp vs C# Microsoft Orleans (used from F#)

Section titled “Orleans.FSharp vs C# Microsoft Orleans (used from F#)”

You can use Microsoft Orleans directly from F# — but you end up writing C#-style code in F# syntax: class inheritance, mutable state, imperative patterns. Orleans.FSharp replaces that with immutable state, pattern matching, and computation expressions instead.

AspectC# Orleans from F#Orleans.FSharp
Grain definitionHand-written interface + inherit Grain() classcontract<string, CounterApi> { ... } + grainFor
State transitionsMutable fields / this.StatePure handlers returning newState, reply
Client proxiesC# source generator (needs a C# shim project)Precompiled in the package — nothing to generate
Configurationbuilder.UseOrleans(fun siloBuilder -> ...)siloConfig { useLocalhostClustering; addMemoryStorage "Default" }
SerializationManual [<GenerateSerializer>] on classesSame attribute, but on DUs — the natural F# choice
TestingWrite C#-style mocksGrainArbitrary.forCommands<'Cmd>() + FsCheck

C# Orleans from F# (class inheritance):

type ICounterGrain =
inherit IGrainWithStringKey
abstract Increment: unit -> Task<int>
abstract Value: unit -> Task<int>
// ...plus a C# shim project in the solution, because Orleans'
// proxy source generator does not run on F# projects.
type CounterGrain() =
inherit Grain()
let mutable count = 0
interface ICounterGrain with
member _.Increment() =
count <- count + 1
Task.FromResult count
member _.Value() = Task.FromResult count

Orleans.FSharp (functional grain runtime):

type CounterApi =
{ increment: unit -> Task<int>
value: unit -> Task<int> }
let counterContract =
contract<string, CounterApi> {
grainType "counter"
version 1
stringKey
readOnly (_.value)
}
let counter =
grainFor counterContract {
defaultState (fun () -> 0)
handle (_.increment) (fun _ctx n () -> task { return n + 1, n + 1 })
handleQuery (_.value) (fun _ctx n () -> task { return n })
}

Same two operations on both sides. The functional version is immutable, the compiler checks every handler against CounterApi’s field types, and sealing the definition verifies each operation has exactly one handler — with no proxy-generation step anywhere.

Akkling provides an idiomatic F# API for Akka.NET — a port of the JVM Akka actor framework. The fundamental difference is the actor model: Microsoft Orleans uses virtual actors (always addressable, auto-activated), while Akka.NET uses classic actors (explicit lifecycle management).

AspectOrleans.FSharpAkkling (Akka.NET)
Actor lifecycleVirtual — always exists, activated on demandExplicit — must spawn, supervise, and restart
State persistenceusePersistentState facetsManual Akka.Persistence integration
Failure handlingAutomatic reactivation on another siloSupervision trees (manual configuration)
Location transparencyBuilt-in grain directoryAkka.Cluster + shard regions
Stream processingStream.getStream + Stream.publishAkka.Streams
Concurrency modelSingle-threaded turns (with optional reentrancy)Mailbox processing
  • You need fine-grained actor supervision hierarchies
  • Your team already has Akka/Akka.NET experience
  • You want the Akka.Streams API for complex stream processing
  • You want virtual actors — no lifecycle management overhead
  • You need automatic state persistence without boilerplate
  • You want property-based testing with auto-generated command sequences
  • You are targeting .NET 10
  • You want built-in Kubernetes clustering support

Proto.Actor is a cross-platform actor framework supporting both virtual and classic actor models. It does not have an F# API — you use the C# API directly.

AspectOrleans.FSharpProto.Actor
F# APINative computation expressionsC# API only
Virtual actorsYes (Microsoft Orleans)Yes (Proto.Cluster)
SerializationF# DUs with [<GenerateSerializer>]Protobuf (code generation)
State persistenceusePersistentState facetsManual provider integration
EcosystemMicrosoft Orleans ecosystem (Azure, Dashboard)Standalone (gRPC-based)
TestingGrainArbitrary + FsCheckManual
  • You need cross-language support (Go, C#, Kotlin, Python)
  • You want gRPC as the transport layer
  • Your system is polyglot
  • You are building a pure F#/.NET distributed system
  • You want idiomatic F# with computation expressions
  • You need the Microsoft Orleans ecosystem (Azure integration, Dashboard, extensive providers)
FeatureOrleans.FSharpC# OrleansAkklingProto.Actor
F# computation expressionsYes (137 operations across 8 builders)NoYesNo
DU state machinesYesNoPartialNo
Property-based testingGrainArbitraryNoNoNo
Grain timersonTimer keywordRegisterTimerSchedulerManual
Grain remindersonReminder keywordIRemindableN/AN/A
Event sourcingjournaledGrainFor { }JournaledGrainAkka.PersistenceManual
Transactionstransactional + transactionalStateFrom[Transaction] + TransactionalStateSaga patternManual
StreamingStream module, onStream / onBroadcastIAsyncStreamAkka.StreamsN/A
TLS/mTLSuseTls keywordManual configAkka.Remote TLSgRPC TLS
KubernetesuseKubernetesClusteringKubernetes packageAkka.DiscoveryKubernetes provider
DashboardaddDashboard keywordOrleansDashboardPetabridge.CmdN/A
Health checksenableHealthChecks keywordManual registrationN/AgRPC health
OpenTelemetryOrleans’ own activity sources and meterManual registrationPhobosManual

Orleans.FSharp runs on the Orleans runtime unchanged; it adds a dispatch layer, not a second transport.

  • Where the work happens: a contract and a definition are sealed once, when the module that declares them initialises — not per call. An API shape is built once per record type and cached process-wide, and each operation’s argument and reply closures are precomputed at that point.
  • Per call: one dictionary lookup and one preclosed delegate call on top of the Orleans call itself. The repository’s own dispatch benchmark holds that below 5% of calling the handler function directly, over 1,000,000 iterations.
  • Network latency: dominates all real-world scenarios (microseconds to milliseconds).
  • C# facade callers additionally pay DispatchProxy’s per-call boxing — see Calling from C#.
Use caseRecommended
New F# distributed systemOrleans.FSharp
Existing C# Orleans codebase, adding F#Orleans.FSharp (interop is seamless)
Existing Akka.NET codebaseAkkling (unless migrating to Orleans)
Polyglot system (Go + C# + Python)Proto.Actor
Learning actor model with F#Orleans.FSharp (simplest mental model)
  • Getting Started — zero to working grain in 15 minutes
  • How To — step-by-step distributed system tutorial
  • FAQ — common questions about Orleans.FSharp
  • Legacy API — maintenance documentation for earlier authoring models