fix(health): M2.16 review nit — real idempotency guard for SiteEventLog health bridge (#30)
AddSiteEventLogHealthMetricsBridge registered via AddHostedService(factory-lambda), which sets ImplementationFactory and leaves ImplementationType null. The prior ImplementationType == guard was therefore silently dead — a second call would spin up a second SiteEventLogFailureCountReporter. Fix: add a private SiteEventLogHealthMetricsBridgeMarker singleton and guard on its ServiceType instead. Also corrects the cycle-path comment in both ServiceCollectionExtensions.cs and SiteEventLogFailureCountReporter.cs: StoreAndForward.csproj does reference SiteEventLogging.csproj, so the transitive path HealthMonitoring → StoreAndForward → SiteEventLogging is real, but adding a direct HealthMonitoring → SiteEventLogging reference would NOT create a cycle (SiteEventLogging has no back-edge to HealthMonitoring). The Func<long> seam is a coupling-avoidance measure, not a cycle-breaker. Adds AddSiteEventLogHealthMetricsBridgeTests.AddSiteEventLogHealthMetricsBridge_IsIdempotent_DoesNotDoubleRegister_HostedService as a regression test (builds provider and asserts exactly one reporter via GetServices<IHostedService>().OfType<T>()).
This commit is contained in:
@@ -13,15 +13,17 @@ namespace ZB.MOM.WW.ScadaBridge.HealthMonitoring;
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Why a Func<long> and not ISiteEventLogger directly.</b>
|
||||
/// <c>HealthMonitoring</c> does not (and cannot) reference
|
||||
/// <c>SiteEventLogging</c> — <c>HealthMonitoring → StoreAndForward →
|
||||
/// SiteEventLogging</c> already exists in the transitive graph, so adding a
|
||||
/// direct reference would create a cycle. The <see cref="Func{TResult}"/>
|
||||
/// delegate seam breaks the coupling: the caller (Host site wiring) captures
|
||||
/// <c>ISiteEventLogger.FailedWriteCount</c> as a lambda at registration
|
||||
/// time, and this service reads only the numeric result. The delegate
|
||||
/// approach is a standard pattern for counter bridges and keeps the
|
||||
/// registration path self-documenting.
|
||||
/// A direct <c>HealthMonitoring → SiteEventLogging</c> reference is avoided
|
||||
/// to prevent an undesirable low-level coupling: <c>SiteEventLogging</c> is a
|
||||
/// leaf component that should not pull in higher-level infrastructure. Note that
|
||||
/// <c>HealthMonitoring → StoreAndForward → SiteEventLogging</c> already
|
||||
/// exists as a transitive path (confirmed: <c>StoreAndForward.csproj</c> references
|
||||
/// <c>SiteEventLogging.csproj</c>), so a direct reference would NOT introduce a
|
||||
/// cycle — the delegate is purely a coupling-avoidance measure. The
|
||||
/// <see cref="Func{TResult}"/> seam lets the caller (Host site wiring) capture
|
||||
/// <c>ISiteEventLogger.FailedWriteCount</c> as a lambda at registration time; this
|
||||
/// service reads only the numeric result. The delegate approach is a standard
|
||||
/// pattern for counter bridges and keeps the registration path self-documenting.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Cadence.</b> 30 s by default — the same cadence as
|
||||
|
||||
Reference in New Issue
Block a user