Provider authoring architecture
An Orleans provider adapts an external system or alternate runtime implementation to a defined contract. Storage, clustering, reminders, grain directories, and persistent streams have different data-plane interfaces, but their hosting architecture follows the same pattern, built on the .NET options pattern and dependency injection:
- bind a named configuration section;
- register named services and options;
- validate configuration during host startup;
- participate in lifecycle when resources need initialization; and
- surface failures instead of silently degrading to another backend.
Configuration-driven provider builders
Section titled “Configuration-driven provider builders”IProviderBuilder<T> is the bridge from Orleans configuration to a silo or client builder:
Provider packages associate a provider type string and category with a builder using assembly metadata. The host selects that builder from configuration, passes the provider name and section, and lets the builder call the same public registration API used by code-first configuration.
The Azure Queue stream implementation demonstrates this pattern: AzureQueueStreamProviderBuilder implements builders for both ISiloBuilder and IClientBuilder. See its implementation.
Named-service composition
Section titled “Named-service composition”Many provider kinds allow multiple instances. The provider name is therefore part of service identity, options identity, logging identity, and runtime lookup. NamedServiceConfigurator and its derived configurators register keyed components and named options without creating a private service provider.
A registration extension should:
- require a non-empty name when the provider category is named;
- register options through
AddOptions<T>(name)or an Orleans configurator; - register the contract and implementation under the same key;
- use TryAdd only for truly shared defaults; and
- include the name in validators and diagnostics.
Resolving an unkeyed singleton for a named component can make the first provider’s options leak into every other provider.
Separate control plane and data plane
Section titled “Separate control plane and data plane”The provider builder and options are the control plane. The runtime contract is the data plane:
| Provider kind | Data-plane contract |
|---|---|
| Cluster membership | IMembershipTable |
| Grain storage | IGrainStorage |
| Grain directory | IGrainDirectory |
| Reminder table | IReminderTable |
| Persistent streams | IQueueAdapterFactory and its adapter components |
Do not let configuration concerns weaken the data-plane contract. For example, a membership provider must preserve conditional updates and ordered versions regardless of whether credentials came from a connection string, a keyed SDK client, or managed identity.
Validation
Section titled “Validation”Options validation should fail before the silo joins the cluster or starts accepting traffic. Orleans providers commonly register an IConfigurationValidator so validation can include named options and service dependencies which ordinary data-annotation validation cannot express.
Validate at least:
- required endpoint, client, or credential source;
- mutually exclusive configuration forms;
- provider-specific naming and range constraints;
- compatibility between paired components; and
- capabilities required by the runtime contract.
Avoid broad exception handling which turns an inaccessible backend into an empty result. An empty membership table, missing grain state, or empty stream queue has domain meaning and must not represent a swallowed infrastructure failure.
Lifecycle and ownership
Section titled “Lifecycle and ownership”Providers which allocate clients, receivers, leases, or background agents should implement or register an ILifecycleParticipant<T>. Initialize after required runtime services are ready and stop before those services disappear.
Ownership must be explicit. If the application supplies a keyed SDK client, the provider generally should not dispose an object it does not own. If the provider creates receivers per queue, it should stop and dispose them when queue ownership moves.
The persistent stream provider illustrates staged lifecycle composition: it creates the adapter during initialization, starts pulling agents at the active stage, then stops agents before closing. See PersistentStreamProvider.Participate and its implementation.
Membership callers use the Async-suffixed, cancellation-aware methods of IMembershipTable for initialization, reads, and writes. Built-in providers forward tokens to backend APIs which support cancellation and bound waits on tokenless SDK operations while observing late faults. Custom providers can implement these methods directly; their default implementations adapt existing tokenless providers by canceling the caller’s wait while the operation completes. The original tokenless method names remain as obsolete compatibility entry points. Cancellation can race with a committed write, so conditional writes and table versions continue to govern subsequent updates.
Return completed backend results, and observe cancellation before starting further I/O. Mapping an already-returned result preserves that operation’s outcome.
Shared membership refreshes live until the membership manager is disposed. Each caller owns its wait, while periodic maintenance and its queued cleanup requests stop with the silo lifecycle. This keeps membership reads available during shutdown.
Membership table RPCs retain their existing operation aliases and application-argument payloads, with cancellation propagated separately. The original generated request types remain available for calls through obsolete tokenless methods. During rolling upgrades, each receiver uses its implementation’s cancellation behavior.
When a lifecycle callback must execute its cancellation or cleanup logic, schedule it with Run and pass the cancellation token to the operation inside the callback. The callback then owns how cancellation completes its work.
Testing a provider
Section titled “Testing a provider”Contract tests should cover more than successful round trips:
- concurrent conditional updates and stale version rejection;
- duplicate registration or delivery behavior;
- cancellation and timeout propagation;
- startup validation;
- backend unavailability without silent fallback;
- resource cleanup after lifecycle stop;
- multiple named instances with isolated options; and
- rolling-upgrade compatibility of stored or transmitted data.
Use TestingHost architecture to understand which runtime services a test cluster substitutes. Provider tests which depend on a real backend should state those preconditions and should not treat an emulator’s weaker consistency as proof of the production contract.
Membership provider conformance
Section titled “Membership provider conformance”Microsoft.Orleans.Clustering.TestKit supplies framework-neutral tests for IMembershipTable. Create a fresh fixture for each direct scenario, with independent provider handles sharing a backend and using the fixture’s isolated cluster IDs. The fixture owns initialization and teardown.
Supply the fixture’s read-only deletion probe over the original backing scope. The kit checks populated data before deletion and verifies native deletion or store invalidation before owner disposal. Deletion ends that history; subsequent generated cases use fresh fixtures and owners.
Expose the direct runner methods as individual tests and run the Accordant-generated suite with a fresh-fixture factory. The tests cover atomic table-version CAS on existing rows, coherent canonical reads, heartbeat noninterference with the table version and previously captured update inputs, Dead-row cleanup, and cluster isolation. Row ETags are provider-defined metadata and can change after a heartbeat. Raw liveness reads can lag, and full-row writes can overwrite a heartbeat. Provider-native instrumentation verifies that each periodic heartbeat performs one blind liveness write with zero prerequisite reads or compare-and-swap operations. Retain failure diagnostics to reproduce generated histories. The package README provides factory examples and the scenario inventory.
