using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian; using ZB.MOM.WW.OtOpcUa.Runtime.Historian; namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway; /// /// Host-callable factories that build the gateway-backed historian seams against the single /// ServerHistorian gateway: for the read path (the Host's /// AddServerHistorian wiring) and for the alarm-write path /// (the Host's AddAlarmHistorian wiring). Both keep the concrete package-client dependency /// inside this driver project — the Host references only the driver, not the package client directly. /// public static class GatewayHistorian { /// /// Builds a over a lazily connected /// mapped from the bound /// . Resolves an and the data /// source's from , falling back to /// the null implementations when absent (e.g. minimal test providers). Performs no network I/O — /// the underlying channel dials on first use. /// /// The bound ServerHistorian configuration. /// The resolving service provider (used only to locate logging services). /// The gateway-backed . public static IHistorianDataSource CreateDataSource(ServerHistorianOptions options, IServiceProvider services) { ArgumentNullException.ThrowIfNull(options); ArgumentNullException.ThrowIfNull(services); var loggerFactory = services.GetService() ?? NullLoggerFactory.Instance; var logger = services.GetService>() ?? NullLogger.Instance; return new GatewayHistorianDataSource( HistorianGatewayClientAdapter.Create(options, loggerFactory), logger); } /// /// Builds a over a lazily connected /// mapped from the bound /// — the same single gateway the read path /// () targets. The Host's AddAlarmHistorian wiring supplies /// this as the concrete the durable /// SqliteStoreAndForwardSink drain worker delegates to, sourcing the connection from the /// ServerHistorian section (endpoint/key/TLS) rather than the legacy Wonderware-shaped /// AlarmHistorian host/port. Resolves an and the writer's /// from , falling back to the null /// implementations when absent. Performs no network I/O — the underlying channel dials on first send. /// /// /// This deliberately constructs its own — a /// second gRPC channel to the same gateway as the read path. Collapsing the two onto one shared /// channel would require the container to own a singleton and /// the read-side to stop owning + disposing its client, /// regressing the read cutover's dispose ownership (and its tests). A second channel to a co-located /// sidecar is cheap — the gateway pools and amortizes the underlying historian sessions server-side — /// so each path keeps its own channel with a clean, independent lifetime. /// /// The bound ServerHistorian configuration (endpoint, key, TLS posture). /// The resolving service provider (used only to locate logging services). /// The gateway-backed . public static IAlarmHistorianWriter CreateAlarmWriter(ServerHistorianOptions options, IServiceProvider services) { ArgumentNullException.ThrowIfNull(options); ArgumentNullException.ThrowIfNull(services); var loggerFactory = services.GetService() ?? NullLoggerFactory.Instance; var logger = services.GetService>() ?? NullLogger.Instance; return new GatewayAlarmHistorianWriter( HistorianGatewayClientAdapter.Create(options, loggerFactory), logger); } }