Dotnet-skills dotnet-orleans
Build or review distributed .NET applications with Orleans grains, silos, persistence, streaming, reminders, placement, transactions, serialization, event sourcing, testing, and cloud-native hosting.
git clone https://github.com/managedcode/dotnet-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/managedcode/dotnet-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/catalog/Frameworks/Orleans/skills/dotnet-orleans" ~/.claude/skills/managedcode-dotnet-skills-dotnet-orleans && rm -rf "$T"
catalog/Frameworks/Orleans/skills/dotnet-orleans/SKILL.mdMicrosoft Orleans
Trigger On
- building or reviewing
code that uses.NET
,Microsoft.Orleans.*
,Grain
,IGrainWith*
,UseOrleans
,UseOrleansClient
,IGrainFactory
,JournaledGrain
, or Orleans silo/client buildersITransactionalState - testing Orleans code with
,InProcessTestCluster
,Aspire.Hosting.Testing
, or shared AppHost fixturesWebApplicationFactory - modeling high-cardinality stateful entities such as users, carts, devices, rooms, orders, digital twins, sessions, or collaborative documents
- choosing between grains, streams, broadcast channels, reminders, stateless workers, persistence providers, placement strategies, transactions, event sourcing, and external client/frontend topologies
- deploying or operating Orleans with Redis, Azure Storage, Cosmos DB, ADO.NET, .NET Aspire, Kubernetes, Azure Container Apps, or built-in/dashboard observability
- designing grain serialization contracts, versioning grain interfaces, configuring custom placement, or implementing grain call filters and interceptors
Workflow
-
Decide whether Orleans fits. Use it when the system has many loosely coupled interactive entities that can each stay small and single-threaded. Do not force Orleans onto shared-memory workloads, long batch jobs, or systems dominated by constant global coordination.
-
Model grain boundaries around business identity. Prefer one grain per user, cart, device, room, order, or other durable entity. Never create unique grains per request — use
for stateless fan-out. Grain identity types:[StatelessWorker]
— globally unique entitiesIGrainWithGuidKey
— relational DB integrationIGrainWithIntegerKey
— flexible string keysIGrainWithStringKey
/IGrainWithGuidCompoundKey
— composite identity with extension stringIGrainWithIntegerCompoundKey
-
Design coarse-grained async APIs. All grain interface methods must return
,Task
, orTask<T>
. UseValueTask<T>
for streaming responses. AvoidIAsyncEnumerable<T>
,.Result
, blocking I/O, lock-based coordination. Use.Wait()
for parallel cross-grain calls. ApplyTask.WhenAll
on interface methods when needed.[ResponseTimeout("00:00:05")] -
Choose the right state pattern:
withIPersistentState<TState>
for named persistent state (preferred)[PersistentState("name", "provider")]- Multiple named states per grain for different storage providers
for event-sourced grainsJournaledGrain<TState, TEvent>
for ACID transactions across grainsITransactionalState<TState>
is legacy — use only when constrained by existing codeGrain<TState>
-
Pick the right runtime primitive deliberately:
- Standard grains for stateful request/response logic
for pure stateless fan-out or compute helpers[StatelessWorker]- Orleans streams for decoupled event flow and pub/sub with
[ImplicitStreamSubscription] - Broadcast channels for fire-and-forget fan-out with
[ImplicitChannelSubscription]
for activation-local periodic work (non-durable)RegisterGrainTimer- Reminders via
for durable low-frequency wakeupsIRemindable - Observers via
andIGrainObserver
for one-way push notificationsObserverManager<T>
-
Configure serialization correctly:
on all state and message types[GenerateSerializer]
on each serialized member for stable identification[Id(N)]
for safe type renaming[Alias("name")]
to skip copy overhead on immutable types[Immutable]- Use surrogates (
) for types you don't ownIConverter<TOriginal, TSurrogate>
-
Handle reentrancy and scheduling deliberately:
- Default is non-reentrant single-threaded execution (safe but deadlock-prone with circular calls)
on grain class for full interleaving[Reentrant]
on interface method for specific method interleaving[AlwaysInterleave]
for concurrent read-only methods[ReadOnly]
for scoped reentrancyRequestContext.AllowCallChainReentrancy()- Native
support (last parameter, optional default)CancellationToken
-
Choose hosting intentionally.
for silos,UseOrleans
for separate clientsUseOrleansClient- Co-hosted client runs in same process (reduced latency, no extra serialization)
- In Aspire, declare Orleans resource in AppHost, wire clustering/storage/reminders there, use
for frontend-only consumers.AsClient() - In Aspire-backed tests, resolve Orleans backing-resource connection strings from the distributed app and feed them into the test host instead of duplicating local settings
- Prefer
withTokenCredential
for Azure-backed providersDefaultAzureCredential
-
Configure providers with production realism.
- In-memory storage, reminders, and stream providers are dev/test only
- Persistence: Redis, Azure Table/Blob, Cosmos DB, ADO.NET, DynamoDB
- Reminders: Azure Table, Redis, Cosmos DB, ADO.NET
- Clustering: Azure Table, Redis, Cosmos DB, ADO.NET, Consul, Kubernetes
- Streams: Azure Event Hubs, Azure Queue, Memory (dev only)
-
Treat placement as an optimization tool, not a default to cargo-cult.
is default since 9.2 (CPU, memory, activation count weighted)ResourceOptimizedPlacement
,RandomPlacement
,PreferLocalPlacement
,HashBasedPlacementActivationCountBasedPlacement
for role-targeted placementSiloRoleBasedPlacement- Custom placement via
+IPlacementDirector
+PlacementStrategyPlacementAttribute - Placement filtering (9.0+) for zone-aware and hardware-affinity placement
- Activation repartitioning and rebalancing are experimental
-
Make the cluster observable.
- Standard
Microsoft.Extensions.Logging
with meterSystem.Diagnostics.Metrics"Microsoft.Orleans"- OpenTelemetry export via
+AddOtlpExporterAddMeter("Microsoft.Orleans") - Distributed tracing via
with sourcesAddActivityPropagation()
and"Microsoft.Orleans.Runtime""Microsoft.Orleans.Application" - Orleans Dashboard for operational visibility (secure with ASP.NET Core auth)
- Health checks for cluster readiness
- Standard
-
Test the cluster behavior you actually depend on.
for new testsInProcessTestCluster- Shared Aspire/AppHost fixtures for real HTTP, SignalR, SSE, or UI flows that must exercise the co-hosted Orleans topology
layered over a shared AppHost when tests need Host DI services,WebApplicationFactory<TEntryPoint>
, or direct grain/runtime access while keeping real infrastructureIGrainFactory- Multi-silo coverage when placement, reminders, persistence, or failover matters
- Benchmark hot grains before claiming the design scales
- Use memory providers in test, real providers in integration tests
Architecture
flowchart LR A["Distributed requirement"] --> B{"Many independent<br/>interactive entities?"} B -->|No| C["Plain service / worker / ASP.NET Core"] B -->|Yes| D["Model one grain per business identity"] D --> E{"State pattern?"} E -->|"Persistent"| F["IPersistentState<T>"] E -->|"Event-sourced"| F2["JournaledGrain<S,E>"] E -->|"Transactional"| F3["ITransactionalState<T>"] E -->|"In-memory only"| G["Activation state"] D --> H{"Communication?"} H -->|"Pub/sub"| I["Orleans streams"] H -->|"Broadcast"| I2["Broadcast channels"] H -->|"Push to client"| I3["Observers"] H -->|"Request/response"| I4["Direct grain calls"] D --> J{"Periodic work?"} J -->|"Activation-local"| K["RegisterGrainTimer"] J -->|"Durable wakeups"| L["Reminders"] D --> M{"Client topology?"} M -->|"Separate process"| N["UseOrleansClient / .AsClient()"] M -->|"Same process"| O["Co-hosted silo+client"] F & F2 & F3 & G & I & I2 & I3 & I4 & K & L & N & O --> P["Serialization → Placement → Observability → Testing → Deploy"]
Deliver
- a justified Orleans fit, or a clear rejection when the problem should stay as plain
code.NET - grain boundaries, grain identities, and activation behavior aligned to the domain model
- concrete choices for clustering, persistence, reminders, streams, placement, transactions, and hosting topology
- serialization contracts with
,[GenerateSerializer]
, versioning via[Id]
, and immutability annotations[Alias] - an async-safe grain API surface with bounded state, proper reentrancy, and reduced hot-spot risk
- an explicit testing and observability plan for local development and production
- a test-harness choice that matches the assertion level: runtime-only, API/SignalR/UI, or direct Host DI/grain access
Validate
- Orleans is being used for many loosely coupled entities, not as a generic distributed hammer
- grain interfaces are coarse enough to avoid chatty cross-grain traffic
- no grain code blocks threads or mixes sync-over-async with runtime calls
- state is bounded, version-tolerant, and persisted only through intentional provider-backed writes
- all state and message types use
and[GenerateSerializer]
correctly[Id(N)] - timers are not used where durable reminders are required; reminders are not used for high-frequency ticks
- in-memory storage, reminders, and stream providers are confined to dev/test usage
- Aspire projects register required keyed backing resources before
orUseOrleans()UseOrleansClient() - reentrancy is handled deliberately — circular call patterns use
,[Reentrant]
, or[AlwaysInterleave]AllowCallChainReentrancy - transactional grains are marked
and use[Reentrant]
/PerformReadPerformUpdate - hot grains, global coordinators, and affinity-heavy grains are measured and justified
- tests cover multi-silo behavior, persistence, and failover-sensitive logic when those behaviors matter
- Aspire-backed tests reuse one shared AppHost fixture and do not boot the distributed topology inside individual tests
- co-hosted Host tests do not start a redundant Orleans client unless external-client behavior is the thing under test
- Host or API test factories resolve connection strings from the AppHost resource graph instead of copied local config
- deployment uses production clustering, real providers, and proper GC configuration
Load References
Open only what you need. Each reference is topic-focused for token economy:
- references/official-docs-index.md — full Orleans documentation map with direct links to the official Learn tree
- references/grains.md — grain modeling, persistence, event sourcing, reminders, transactions, versioning links
- references/grain-api.md — grain identity, placement, lifecycle, reentrancy, cancellation API details with code
- references/persistence-api.md — IPersistentState API, provider configuration, event sourcing, transactions with code
- references/streaming-api.md — streams, broadcast channels, observers, IAsyncEnumerable patterns with code
- references/serialization-api.md — GenerateSerializer, Id, Alias, surrogates, copier, immutability details
- references/hosting.md — clients, Aspire, configuration, observability, dashboard, deployment links
- references/configuration-api.md — silo/client config, GC tuning, deployment targets, observability setup with code
- references/implementation.md — runtime internals, testing, load balancing, messaging guarantees
- references/testing-patterns.md — practical Orleans test harness selection with
, shared AppHost fixtures,InProcessTestCluster
, SignalR, and PlaywrightWebApplicationFactory - references/patterns.md — grain, persistence, streaming, coordination, and performance patterns with code
- references/anti-patterns.md — blocking calls, unbounded state, chatty grains, bottlenecks, deadlocks with code
- references/examples.md — quickstarts, samples browser entries, and official Orleans example hubs
Official sources: