Table of Contents

Health Checks

The DarkPeak.Functional.HealthChecks package integrates DarkPeak.Functional's resilience policies with ASP.NET Core's health check system, exposing circuit breaker and cache provider status via standard IHealthCheck implementations.

Installation

dotnet add package DarkPeak.Functional.HealthChecks

Basic Usage

Register a circuit breaker health check with a single extension method call:

using DarkPeak.Functional;
using DarkPeak.Functional.HealthChecks;

var breaker = CircuitBreaker
    .WithFailureThreshold(5)
    .WithResetTimeout(TimeSpan.FromSeconds(30));

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddHealthChecks()
    .AddCircuitBreakerHealthCheck("payment-gateway", breaker);

var app = builder.Build();
app.MapHealthChecks("/health");
app.Run();

Circuit Breaker Health Check

The CircuitBreakerHealthCheck monitors a CircuitBreakerPolicy via GetSnapshot() and maps its state to ASP.NET Core health statuses:

Circuit State Health Status Description
Closed Healthy Normal operation
HalfOpen Degraded Probe request allowed
Open Unhealthy Requests rejected
using DarkPeak.Functional;
using DarkPeak.Functional.HealthChecks;

var breaker = CircuitBreaker
    .WithFailureThreshold(3)
    .WithResetTimeout(TimeSpan.FromSeconds(60))
    .WithBreakWhen(error => error is ExternalServiceError);

builder.Services
    .AddHealthChecks()
    .AddCircuitBreakerHealthCheck("inventory-service", breaker);

Cache Provider Health Check

The CacheProviderHealthCheck<TKey, TValue> probes an ICacheProvider by calling GetAsync with a sentinel key. A successful call (returning Some or None) indicates the provider is reachable. An exception marks it as unhealthy.

using DarkPeak.Functional;
using DarkPeak.Functional.HealthChecks;
using DarkPeak.Functional.Redis;
using StackExchange.Redis;

var redis = ConnectionMultiplexer.Connect("localhost:6379");
var cacheProvider = new RedisCacheProvider<string, string>(
    redis.GetDatabase(), keyPrefix: "myapp:");

builder.Services
    .AddHealthChecks()
    .AddCacheProviderHealthCheck("redis-cache", cacheProvider, probeKey: "__health");

The probe key ("__health") does not need to exist in the cache — a cache miss still confirms connectivity.

Tags

Use tags to separate liveness and readiness checks. This lets Kubernetes (or any orchestrator) query different endpoints for different purposes:

builder.Services
    .AddHealthChecks()
    .AddCircuitBreakerHealthCheck(
        "payment-gateway", breaker, tags: ["ready"])
    .AddCacheProviderHealthCheck(
        "redis-cache", cacheProvider, probeKey: "__health", tags: ["ready"]);

app.MapHealthChecks("/health/ready", new()
{
    Predicate = check => check.Tags.Contains("ready")
});

app.MapHealthChecks("/health/live", new()
{
    Predicate = check => !check.Tags.Contains("ready")
});

Health Check Data

The circuit breaker health check includes structured data in its result:

Key Type Description
state string Current circuit state (Closed, HalfOpen, or Open)
failureCount int Number of consecutive failures
resetTime string ISO 8601 UTC timestamp when the circuit will transition to HalfOpen (only present when Open)

When the circuit is open, the description includes the failure count and reset timestamp:

Circuit breaker is Open. 5 consecutive failures. Resets at 2025-01-15 14:30:00Z

Full Example

A complete minimal API combining circuit breaker, Redis cache, and health checks:

using DarkPeak.Functional;
using DarkPeak.Functional.HealthChecks;
using DarkPeak.Functional.Http;
using DarkPeak.Functional.Redis;
using StackExchange.Redis;

var builder = WebApplication.CreateBuilder(args);

// Redis
var redis = ConnectionMultiplexer.Connect("localhost:6379");
var cacheProvider = new RedisCacheProvider<string, CatalogItem>(
    redis.GetDatabase(), keyPrefix: "catalog:");

// Circuit breaker
var breaker = CircuitBreaker
    .WithFailureThreshold(5)
    .WithResetTimeout(TimeSpan.FromSeconds(30))
    .WithBreakWhen(error => error is ExternalServiceError or HttpRequestError);

// Memoized + resilient fetch
var httpClient = new HttpClient { BaseAddress = new Uri("https://api.example.com") };

var cachedFetch = MemoizeResult.FuncAsync<string, CatalogItem, Error>(
    endpoint => Retry
        .WithMaxAttempts(3)
        .WithBackoff(Backoff.Exponential(TimeSpan.FromMilliseconds(200)))
        .ExecuteAsync(() =>
            breaker.ExecuteAsync(
                () => httpClient.GetResultAsync<CatalogItem>(endpoint))),
    opts => opts
        .WithExpiration(TimeSpan.FromMinutes(10))
        .WithCacheProvider(cacheProvider));

// Health checks
builder.Services
    .AddHealthChecks()
    .AddCircuitBreakerHealthCheck("catalog-api", breaker, tags: ["ready"])
    .AddCacheProviderHealthCheck("redis-cache", cacheProvider,
        probeKey: "__health", tags: ["ready"]);

var app = builder.Build();

app.MapHealthChecks("/health");
app.MapHealthChecks("/health/ready", new()
{
    Predicate = check => check.Tags.Contains("ready")
});

app.MapGet("/catalog/{id}", async (string id) =>
{
    var result = await cachedFetch($"/api/catalog/{id}");
    return result.Match(
        success: item => Results.Ok(item),
        failure: error => Results.Problem(error.Message));
});

app.Run();

record CatalogItem(string Id, string Name, decimal Price);

Request flow: L1 memory → L2 Redis → Retry → Circuit breaker → HTTP call

Health checks expose the state of each resilience component independently, so your orchestrator can route traffic away from degraded instances before end users are affected.

API Reference

Method Parameters Description
AddCircuitBreakerHealthCheck string name, CircuitBreakerPolicy circuitBreaker, IEnumerable<string>? tags = null Registers a health check that monitors a circuit breaker. Reports Healthy (Closed), Degraded (HalfOpen), or Unhealthy (Open).
AddCacheProviderHealthCheck<TKey, TValue> string name, ICacheProvider<TKey, TValue> cacheProvider, TKey probeKey, IEnumerable<string>? tags = null Registers a health check that probes a cache provider by calling GetAsync with a sentinel key. Healthy if reachable, Unhealthy if an exception occurs.