Skip to content

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.