Orleans Grains¶
Consystence uses Microsoft Orleans as its distributed actor runtime. Every stateful entity in the platform — organisations, devices, page sessions, audit logs — is modelled as an Orleans grain.
Why Orleans¶
Orleans provides:
- Virtual actors — grains are created on demand and garbage-collected when idle. No explicit lifecycle management.
- Single-threaded execution — each grain processes one message at a time, eliminating concurrency bugs inside the grain.
- Location transparency — grains can be addressed by key from anywhere in the cluster. The runtime handles placement and routing.
- Persistent state — grain state survives restarts via pluggable storage providers.
Grain key conventions¶
Each grain type uses a consistent key format:
| Grain | Key Format | Example |
|---|---|---|
OrgDirectoryGrain | "global" | "global" |
SiteDirectoryGrain | "{orgId}" | "a3f1c9e0-..." |
TypeRegistryGrain | "global" | "global" |
DeviceInstanceGrain | "{siteId}/{instanceId}" | "d4e5f6a7-.../pump-01" |
EdgeDeviceGrain | "{siteId}/{edgeId}" | "d4e5f6a7-.../orin-north" |
PlcSimulatorGrain | "{siteId}/{processId}" | "d4e5f6a7-.../demo-pump-station" |
PageSessionGrain | "{connectionId}" | "abc123xyz" |
CommandAuditGrain | "{siteId}" | "d4e5f6a7-..." |
Tip
Composite keys use / as a separator. This keeps keys human-readable in logs and debugging tools.
Key grain types¶
OrgDirectoryGrain¶
Singleton grain that maintains the index of all organisations. Handles org creation, lookup by slug, and domain association queries.
SiteDirectoryGrain¶
One per organisation. Maintains the index of sites within that org. Handles site creation, lookup, and licence validation.
TypeRegistryGrain¶
Singleton grain that manages the device type catalogue. Handles type registration, versioning, marketplace publishing, and dependency resolution.
DeviceInstanceGrain¶
One per device instance at a site. Holds the current tag values, alarm states, and configuration. Receives tag updates from the edge device grain and notifies subscribers (scene graph bindings, alarm evaluator, historian).
EdgeDeviceGrain¶
One per physical edge device. Manages the gRPC connection to the Orin, handles store-and-forward reconciliation, and coordinates edge ML model deployments.
PlcSimulatorGrain¶
One per process (when no real PLC is connected). Generates realistic simulated tag data — fluctuating levels, cycling pump speeds, occasional faults — so the operator interface works without physical equipment.
PageSessionGrain¶
One per active browser connection. Manages the GUI DSL component tree for that session, tracks which tags are subscribed, and computes differential HTML/SVG updates when state changes.
CommandAuditGrain¶
One per site. Records every operator command (start, stop, reset, setpoint change) with timestamp, user identity, device, and outcome. Provides the audit trail for regulatory compliance.
Storage¶
Grain state is persisted using the ADO.NET storage provider with PostgreSQL (Npgsql):
siloBuilder.AddAdoNetGrainStorage("Default", options =>
{
options.Invariant = "Npgsql";
options.ConnectionString = connectionString;
});
All grain state DTOs use [GenerateSerializer] for Orleans' source-generated serialization:
[GenerateSerializer]
public record DeviceInstanceState
{
[Id(0)] public string DeviceTypeId { get; init; } = "";
[Id(1)] public Dictionary<string, TagValue> Tags { get; init; } = new();
[Id(2)] public Dictionary<string, AlarmState> Alarms { get; init; } = new();
}
Clustering¶
| Environment | Provider | Configuration |
|---|---|---|
| Development | Localhost | Single silo, in-process |
| Production (site) | ADO.NET (PostgreSQL) | Single-tenant cluster, one or more silos per site |
| Production (cloud) | ADO.NET (PostgreSQL) | Multi-tenant cluster on Azure |
Streams¶
Orleans Streams provide event pub/sub between grains and external consumers:
| Stream | Namespace | Use |
|---|---|---|
| Tag updates | "tags/{siteId}" | Device instance → page sessions, alarm evaluator, historian |
| Alarm events | "alarms/{siteId}" | Alarm evaluator → alarm list components, notification service |
| Audit log | "audit/{siteId}" | Command audit grain → event log, compliance export |
| Edge status | "edge/{siteId}" | Edge device grain → site dashboard, connection monitor |
Streams use the memory stream provider in development and persistent streams (backed by PostgreSQL) in production to guarantee delivery across silo restarts.