using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Core.Hosting; /// /// Process-singleton registry of factories keyed by /// DriverInstance.DriverType string. Each driver project ships a DI /// extension (e.g. services.AddGalaxyProxyDriverFactory()) that registers /// its factory at startup; the bootstrapper looks up the factory by /// DriverInstance.DriverType + invokes it with the row's /// DriverInstanceId + DriverConfig JSON. /// /// /// Closes the gap surfaced by task #240 live smoke — DriverInstance rows in /// the central config DB had no path to materialise as registered /// instances. The factory registry is the seam. /// public sealed class DriverFactoryRegistry { private readonly Dictionary> _factories = new(StringComparer.OrdinalIgnoreCase); private readonly object _lock = new(); /// /// Register a factory for . Throws if a factory is /// already registered for that type — drivers are singletons by type-name in /// this process. /// /// Matches DriverInstance.DriverType. /// /// Receives (driverInstanceId, driverConfigJson); returns a new /// . Must NOT call /// itself — the bootstrapper calls it via /// so the host's per-driver retry semantics apply uniformly. /// public void Register(string driverType, Func factory) { ArgumentException.ThrowIfNullOrWhiteSpace(driverType); ArgumentNullException.ThrowIfNull(factory); lock (_lock) { if (_factories.ContainsKey(driverType)) throw new InvalidOperationException( $"DriverType '{driverType}' factory already registered for this process"); _factories[driverType] = factory; } } /// /// Try to look up the factory for . Returns null /// if no driver assembly registered one — bootstrapper logs + skips so a /// missing-assembly deployment doesn't take down the whole server. /// public Func? TryGet(string driverType) { ArgumentException.ThrowIfNullOrWhiteSpace(driverType); lock (_lock) return _factories.GetValueOrDefault(driverType); } public IReadOnlyCollection RegisteredTypes { get { lock (_lock) return [.. _factories.Keys]; } } }