feat(health): scaffold ZB.MOM.WW.Health solution + Task 4 (tags + three-tier MapZbHealth)

Consolidates the library into the scadaproj repo (matching the ZB.MOM.WW.Auth
convention — tracked in-parent, not a nested repo). 3 dependency-split packages
(core / .Akka / .EntityFrameworkCore) + 3 test projects, .slnx, central PM.
Task 4: ZbHealthTags + MapZbHealth (/health/ready,/active,/healthz). 8/8 tests.
This commit is contained in:
Joseph Doherty
2026-06-01 06:35:39 -04:00
parent 07d5907258
commit 18be42d0e2
14 changed files with 941 additions and 0 deletions
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>true</IsPackable>
<PackageId>ZB.MOM.WW.Health.Akka</PackageId>
<Authors>ZB.MOM.WW</Authors>
<Description>Akka.Cluster health-check extensions for the ZB.MOM.WW SCADA family.</Description>
<PackageProjectUrl>https://gitea.dohertylan.com/dohertj2/zb-mom-ww-health</PackageProjectUrl>
<RepositoryUrl>https://gitea.dohertylan.com/dohertj2/zb-mom-ww-health</RepositoryUrl>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ZB.MOM.WW.Health\ZB.MOM.WW.Health.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Akka.Cluster" />
</ItemGroup>
</Project>
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>true</IsPackable>
<PackageId>ZB.MOM.WW.Health.EntityFrameworkCore</PackageId>
<Authors>ZB.MOM.WW</Authors>
<Description>Entity Framework Core health-check extensions for the ZB.MOM.WW SCADA family.</Description>
<PackageProjectUrl>https://gitea.dohertylan.com/dohertj2/zb-mom-ww-health</PackageProjectUrl>
<RepositoryUrl>https://gitea.dohertylan.com/dohertj2/zb-mom-ww-health</RepositoryUrl>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ZB.MOM.WW.Health\ZB.MOM.WW.Health.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" />
</ItemGroup>
</Project>
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>true</IsPackable>
<PackageId>ZB.MOM.WW.Health</PackageId>
<Authors>ZB.MOM.WW</Authors>
<Description>Core ASP.NET Core health-check extensions for the ZB.MOM.WW SCADA family.</Description>
<PackageProjectUrl>https://gitea.dohertylan.com/dohertj2/zb-mom-ww-health</PackageProjectUrl>
<RepositoryUrl>https://gitea.dohertylan.com/dohertj2/zb-mom-ww-health</RepositoryUrl>
</PropertyGroup>
<ItemGroup>
<!--
Microsoft.AspNetCore.App is a shared framework, not a NuGet package. It brings in the
ASP.NET Core health-checks middleware, IHealthCheck, HealthCheckService, and the full
Microsoft.Extensions.* surface. Referencing the shared framework is the supported path
for net10.0 libraries that target ASP.NET Core.
-->
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<!-- Abstractions for IHealthCheck / HealthCheckResult (also transitively provided by the
framework ref above, but declared explicitly so the dependency is visible to consumers). -->
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" />
<PackageReference Include="Grpc.Net.Client" />
</ItemGroup>
</Project>
@@ -0,0 +1,58 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace ZB.MOM.WW.Health;
/// <summary>
/// Maps the canonical ZB.MOM.WW three-tier health endpoints in one call.
/// </summary>
public static class ZbHealthEndpointExtensions
{
/// <summary>
/// Maps the three health tiers:
/// <list type="bullet">
/// <item><description><c>/health/ready</c> — runs only checks tagged <see cref="ZbHealthTags.Ready"/>.</description></item>
/// <item><description><c>/health/active</c> — runs only checks tagged <see cref="ZbHealthTags.Active"/>.</description></item>
/// <item><description><c>/healthz</c> — bare process liveness; runs no checks (always 200 while the process is up).</description></item>
/// </list>
/// All three are anonymous. Status mapping is the ASP.NET Core default:
/// Healthy/Degraded → 200, Unhealthy → 503.
/// </summary>
/// <remarks>
/// Does NOT call <c>services.AddHealthChecks()</c> — the caller registers probes and their tags.
/// The response writer is the framework default (plain-text status); a later task swaps in a
/// canonical JSON writer via <see cref="HealthCheckOptions.ResponseWriter"/> (see <paramref name="options"/>).
/// </remarks>
/// <returns>
/// The <see cref="IEndpointConventionBuilder"/> for the readiness (<c>/health/ready</c>) endpoint.
/// A single tier is returned (rather than a composite) to keep the API simple; conventions
/// applied to the result affect only the readiness endpoint.
/// </returns>
public static IEndpointConventionBuilder MapZbHealth(
this IEndpointRouteBuilder endpoints,
ZbHealthEndpointOptions? options = null)
{
ArgumentNullException.ThrowIfNull(endpoints);
options ??= new ZbHealthEndpointOptions();
var ready = endpoints.MapHealthChecks(options.ReadyPath, new HealthCheckOptions
{
Predicate = static c => c.Tags.Contains(ZbHealthTags.Ready),
}).AllowAnonymous();
endpoints.MapHealthChecks(options.ActivePath, new HealthCheckOptions
{
Predicate = static c => c.Tags.Contains(ZbHealthTags.Active),
}).AllowAnonymous();
// Liveness: run no checks. The endpoint returns 200 as long as the process can respond.
endpoints.MapHealthChecks(options.LivePath, new HealthCheckOptions
{
Predicate = static _ => false,
}).AllowAnonymous();
return ready;
}
}
@@ -0,0 +1,17 @@
namespace ZB.MOM.WW.Health;
/// <summary>
/// Options for <see cref="ZbHealthEndpointExtensions.MapZbHealth"/>. Lets callers override the
/// three tier route paths. The default paths match the ZB.MOM.WW health contract.
/// </summary>
public sealed class ZbHealthEndpointOptions
{
/// <summary>Path for the readiness tier (runs only checks tagged <see cref="ZbHealthTags.Ready"/>).</summary>
public string ReadyPath { get; set; } = "/health/ready";
/// <summary>Path for the active-node tier (runs only checks tagged <see cref="ZbHealthTags.Active"/>).</summary>
public string ActivePath { get; set; } = "/health/active";
/// <summary>Path for the bare liveness tier (runs no checks; 200 while the process is up).</summary>
public string LivePath { get; set; } = "/healthz";
}
@@ -0,0 +1,19 @@
namespace ZB.MOM.WW.Health;
/// <summary>
/// Canonical health-check tag constants for the ZB.MOM.WW three-tier health pattern.
/// Use these when registering checks, e.g.
/// <c>AddCheck("db", check, tags: [ZbHealthTags.Ready])</c>, so that
/// <see cref="ZbHealthEndpointExtensions.MapZbHealth"/> routes each check to the right tier.
/// </summary>
public static class ZbHealthTags
{
/// <summary>Readiness checks (dependencies needed before the node can serve traffic).</summary>
public const string Ready = "ready";
/// <summary>Active-node checks (leader / active-singleton gating).</summary>
public const string Active = "active";
/// <summary>Liveness — process is up. Reserved tag; the liveness endpoint runs no checks.</summary>
public const string Live = "live";
}