feat(historian): backend registry, ServerHistorian:Backend, Host wiring via provider

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-09 13:05:53 -04:00
parent 65e6352dad
commit 31d342eff3
7 changed files with 186 additions and 9 deletions
+15 -9
View File
@@ -229,11 +229,21 @@ if (hasDriver)
if (builder.Configuration.GetValue<bool>("AlarmHistorian:Enabled") && !serverHistorianOptions.Enabled)
Log.Warning(
"AlarmHistorian is enabled but ServerHistorian is disabled — the alarm-history sink is sourcing its " +
"gateway connection (endpoint/key/TLS) from the disabled ServerHistorian section; HistoryReads stay GoodNoData.");
"{Backend} backend connection from the disabled ServerHistorian section; HistoryReads stay GoodNoData.",
serverHistorianOptions.Backend);
// Backend selection: resolve the provider once (unknown name => OptionsValidationException naming the
// allowed values) and build every historian seam through it.
var historianProvider = HistorianBackendRegistry.Resolve(serverHistorianOptions, builder.Configuration);
builder.Services.AddSingleton<IHistorianBackendProvider>(historianProvider);
// HistoryUpdate is gated like HistoryRead: None unconditionally, the real holder only when enabled.
builder.Services.AddSingleton(HistorianUpdaterHolder.None);
if (serverHistorianOptions.Enabled)
builder.Services.AddSingleton(sp => new HistorianUpdaterHolder(historianProvider.CreateUpdater(sp)));
builder.Services.AddAlarmHistorian(
builder.Configuration,
(_, sp) => GatewayHistorian.CreateAlarmWriter(serverHistorianOptions, sp));
(_, sp) => historianProvider.CreateAlarmWriter(sp));
// Node-local LocalDb: caches the deployed-configuration artifact so this node can boot from
// its last-known-good config when central SQL is unreachable, and optionally replicates that
@@ -260,7 +270,7 @@ if (hasDriver)
// and the ServerHistorianOptions -> client-options mapping); Runtime owns the gating.
builder.Services.AddServerHistorian(
builder.Configuration,
(opts, sp) => GatewayHistorian.CreateDataSource(opts, sp));
(_, sp) => historianProvider.CreateDataSource(sp));
// Config-gated historian tag provisioning. When the ServerHistorian section is enabled this registers the
// gateway-backed GatewayTagProvisioner (overriding the NullHistorianProvisioning default) so that deploying
@@ -271,7 +281,7 @@ if (hasDriver)
// ServerHistorian:Enabled flag as the read path — both target the same single gateway.
builder.Services.AddHistorianProvisioning(
builder.Configuration,
(opts, sp) => GatewayHistorian.CreateProvisioner(opts, sp));
(_, sp) => historianProvider.CreateProvisioning(sp));
// Continuous historization of driver (non-Galaxy) tag values. Gated on ContinuousHistorization:Enabled
// AND the ServerHistorian gateway being configured: the recorder drains driver-tag live values to the
@@ -309,11 +319,7 @@ if (hasDriver)
// Gateway-backed live-value writer over its OWN gRPC channel to the same single gateway (a second
// channel to a co-located sidecar is cheap — the gateway pools the historian sessions server-side).
builder.Services.AddSingleton<IHistorianValueWriter>(sp =>
new GatewayHistorianValueWriter(
HistorianGatewayClientAdapter.Create(
serverHistorianOptions, sp.GetRequiredService<ILoggerFactory>()),
sp.GetRequiredService<ILogger<GatewayHistorianValueWriter>>()));
builder.Services.AddSingleton<IHistorianValueWriter>(sp => historianProvider.CreateValueWriter(sp));
}
// Bind every cross-platform driver factory before AddAkka resolves IDriverFactory — replaces