dotnet-cqrs/Svrnty.CQRS.Events/ServiceCollectionExtensions.cs

693 lines
30 KiB
C#

using System;
using Svrnty.CQRS.Events.Metrics;
using Svrnty.CQRS.Events.Decorators;
using Svrnty.CQRS.Events.Abstractions.Streaming;
using Svrnty.CQRS.Events.HealthCheck;
using Svrnty.CQRS.Events.Configuration;
using Svrnty.CQRS.Events.Abstractions.Delivery;
using Svrnty.CQRS.Events.Abstractions.Schema;
using Svrnty.CQRS.Events.Abstractions.Storage;
using Svrnty.CQRS.Events.Abstractions.Correlation;
using Svrnty.CQRS.Events.Delivery;
using Svrnty.CQRS.Events.InMemory;
using Svrnty.CQRS.Events.Schema;
using Svrnty.CQRS.Events.Core;
using Svrnty.CQRS.Events.Subscriptions;
using Svrnty.CQRS.Events.Abstractions.Subscriptions;
using Svrnty.CQRS.Events.Abstractions.EventHandlers;
using Svrnty.CQRS.Events.Abstractions.Models;
using Svrnty.CQRS.Events.Abstractions.EventStore;
using FluentValidation;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Svrnty.CQRS.Abstractions;
using Svrnty.CQRS.Abstractions.Discovery;
using Svrnty.CQRS.Events.Abstractions;
using Svrnty.CQRS.Events.Abstractions.Discovery;
using Svrnty.CQRS.Events.Discovery;
using Svrnty.CQRS.Events.Services;
using Svrnty.CQRS.Events.Storage;
using InMemoryEventStreamStore = Svrnty.CQRS.Events.Storage.InMemoryEventStreamStore;
using InMemoryConsumerRegistry = Svrnty.CQRS.Events.Storage.InMemoryConsumerRegistry;
using InMemorySubscriptionStore = Svrnty.CQRS.Events.Storage.InMemorySubscriptionStore;
using EventDeliveryService = Svrnty.CQRS.Events.Delivery.EventDeliveryService;
namespace Svrnty.CQRS.Events;
/// <summary>
/// Service collection extensions for registering event services.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Add core event services (emitter, subscription service, delivery service).
/// Note: Storage implementations (IEventStore, ISubscriptionStore) must be registered separately.
/// </summary>
public static IServiceCollection AddSvrntyEvents(this IServiceCollection services)
{
services.TryAddTransient<IEventEmitter, EventEmitter>();
services.TryAddTransient<IEventSubscriptionService, EventSubscriptionService>();
services.TryAddTransient<IEventDeliveryService, EventDeliveryService>();
// Phase 1.4: Subscription client for consuming events
services.TryAddSingleton<IEventSubscriptionClient, EventSubscriptionClient>();
// Phase 3.2: Exactly-once delivery decorator
services.TryAddSingleton<ExactlyOnceDeliveryDecorator>();
return services;
}
/// <summary>
/// Add default event discovery service.
/// </summary>
public static IServiceCollection AddDefaultEventDiscovery(this IServiceCollection services)
{
services.TryAddTransient<IEventDiscovery, EventDiscovery>();
return services;
}
/// <summary>
/// Register an event type for discovery.
/// This allows the event to be discovered by IEventDiscovery and code generators.
/// </summary>
public static IServiceCollection AddEvent<TEvent>(
this IServiceCollection services,
string? description = null)
where TEvent : class, ICorrelatedEvent
{
var eventType = typeof(TEvent);
var meta = new EventMeta(eventType, description);
services.AddSingleton<IEventMeta>(meta);
return services;
}
/// <summary>
/// Add in-memory storage implementations for events and subscriptions.
/// Suitable for development and testing. Data is lost on application restart.
/// </summary>
/// <remarks>
/// <para>
/// <strong>Registered Services:</strong>
/// - <see cref="IEventStore"/> - Legacy persistent event storage
/// - <see cref="IEventStreamStore"/> - New stream-based storage (Phase 1.3)
/// - <see cref="ISubscriptionStore"/> - Subscription configuration storage
/// - <see cref="ICorrelationStore"/> - Correlation ID tracking
/// - <see cref="IConsumerRegistry"/> - Active consumer tracking (Phase 1.3)
/// </para>
/// </remarks>
public static IServiceCollection AddInMemoryEventStorage(this IServiceCollection services)
{
// Legacy event storage (persistent)
services.TryAddSingleton<IEventStore, InMemoryEventStore>();
// Phase 1.3: Stream-based storage (ephemeral)
services.TryAddSingleton<IEventStreamStore, InMemoryEventStreamStore>();
// Subscription and consumer management
services.TryAddSingleton<ISubscriptionStore, InMemorySubscriptionStore>();
services.TryAddSingleton<IConsumerRegistry, InMemoryConsumerRegistry>();
// Correlation tracking
services.TryAddSingleton<ICorrelationStore, InMemoryCorrelationStore>();
// Phase 3.1: Idempotency store for exactly-once delivery
services.TryAddSingleton<IIdempotencyStore, InMemoryIdempotencyStore>();
// Phase 3.3: Read receipt store for consumer progress tracking
services.TryAddSingleton<IReadReceiptStore, InMemoryReadReceiptStore>();
return services;
}
/// <summary>
/// Register a command handler that automatically manages event emission with correlation IDs.
/// Events are strongly-typed via the TEvents base type or marker interface.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <typeparam name="TEvents">The base type or marker interface for events this command can emit.</typeparam>
/// <typeparam name="THandler">The handler type implementing ICommandHandlerWithEvents.</typeparam>
public static IServiceCollection AddCommandWithEvents<TCommand, TResult, TEvents, THandler>(this IServiceCollection services)
where TCommand : class
where TEvents : ICorrelatedEvent
where THandler : class, ICommandHandlerWithEvents<TCommand, TResult, TEvents>
{
// Register the actual handler
services.AddTransient<THandler>();
// Register metadata for discovery
var commandMeta = new CommandMeta(typeof(TCommand), typeof(THandler), typeof(TResult));
services.AddSingleton<ICommandMeta>(commandMeta);
// Register the decorator as the ICommandHandler that the framework uses
services.AddTransient<ICommandHandler<TCommand, TResult>>(sp =>
{
var handler = sp.GetRequiredService<THandler>();
var eventEmitter = sp.GetRequiredService<IEventEmitter>();
var correlationStore = sp.GetService<ICorrelationStore>(); // Optional
return new CommandHandlerWithEventsDecorator<TCommand, TResult, TEvents>(handler, eventEmitter, correlationStore);
});
return services;
}
/// <summary>
/// Register a command handler with FluentValidation that automatically manages event emission.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <typeparam name="TEvents">The base type or marker interface for events this command can emit.</typeparam>
/// <typeparam name="THandler">The handler type implementing ICommandHandlerWithEvents.</typeparam>
/// <typeparam name="TValidator">The FluentValidation validator type.</typeparam>
public static IServiceCollection AddCommandWithEvents<TCommand, TResult, TEvents, THandler, TValidator>(this IServiceCollection services)
where TCommand : class
where TEvents : ICorrelatedEvent
where THandler : class, ICommandHandlerWithEvents<TCommand, TResult, TEvents>
where TValidator : class, IValidator<TCommand>
{
// Register validator
services.AddTransient<IValidator<TCommand>, TValidator>();
// Register command with events
return services.AddCommandWithEvents<TCommand, TResult, TEvents, THandler>();
}
/// <summary>
/// Register a command handler (no result) that automatically manages event emission.
/// Events are strongly-typed via the TEvents base type or marker interface.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <typeparam name="TEvents">The base type or marker interface for events this command can emit.</typeparam>
/// <typeparam name="THandler">The handler type implementing ICommandHandlerWithEvents.</typeparam>
public static IServiceCollection AddCommandWithEvents<TCommand, TEvents, THandler>(this IServiceCollection services)
where TCommand : class
where TEvents : ICorrelatedEvent
where THandler : class, ICommandHandlerWithEvents<TCommand, TEvents>
{
// Register the actual handler
services.AddTransient<THandler>();
// Register metadata for discovery
var commandMeta = new CommandMeta(typeof(TCommand), typeof(THandler));
services.AddSingleton<ICommandMeta>(commandMeta);
// Register the decorator as the ICommandHandler that the framework uses
services.AddTransient<ICommandHandler<TCommand>>(sp =>
{
var handler = sp.GetRequiredService<THandler>();
var eventEmitter = sp.GetRequiredService<IEventEmitter>();
var correlationStore = sp.GetService<ICorrelationStore>(); // Optional
return new CommandHandlerWithEventsDecoratorNoResult<TCommand, TEvents>(handler, eventEmitter, correlationStore);
});
return services;
}
/// <summary>
/// Register a command handler that returns both the result and correlation ID.
/// Use this for multi-step workflows where the correlation ID needs to be returned to the caller
/// so it can be passed to follow-up commands.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <typeparam name="TEvents">The base type or marker interface for events this command can emit.</typeparam>
/// <typeparam name="THandler">The handler type implementing ICommandHandlerWithEventsAndCorrelation.</typeparam>
public static IServiceCollection AddCommandWithEventsAndCorrelation<TCommand, TResult, TEvents, THandler>(this IServiceCollection services)
where TCommand : class
where TEvents : ICorrelatedEvent
where THandler : class, ICommandHandlerWithEventsAndCorrelation<TCommand, TResult, TEvents>
{
// Register the actual handler
services.AddTransient<THandler>();
// Register metadata for discovery (with the wrapped result type)
var commandMeta = new CommandMeta(typeof(TCommand), typeof(THandler), typeof(CommandResultWithCorrelation<TResult>));
services.AddSingleton<ICommandMeta>(commandMeta);
// Register the decorator as the ICommandHandler that the framework uses
services.AddTransient<ICommandHandler<TCommand, CommandResultWithCorrelation<TResult>>>(sp =>
{
var handler = sp.GetRequiredService<THandler>();
var eventEmitter = sp.GetRequiredService<IEventEmitter>();
var correlationStore = sp.GetService<ICorrelationStore>(); // Optional
return new CommandHandlerWithEventsAndCorrelationDecorator<TCommand, TResult, TEvents>(handler, eventEmitter, correlationStore);
});
return services;
}
/// <summary>
/// Register a command handler that returns both the result and correlation ID, with FluentValidation support.
/// Use this for multi-step workflows where the correlation ID needs to be returned to the caller.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <typeparam name="TEvents">The base type or marker interface for events this command can emit.</typeparam>
/// <typeparam name="THandler">The handler type implementing ICommandHandlerWithEventsAndCorrelation.</typeparam>
/// <typeparam name="TValidator">The FluentValidation validator type.</typeparam>
public static IServiceCollection AddCommandWithEventsAndCorrelation<TCommand, TResult, TEvents, THandler, TValidator>(this IServiceCollection services)
where TCommand : class
where TEvents : ICorrelatedEvent
where THandler : class, ICommandHandlerWithEventsAndCorrelation<TCommand, TResult, TEvents>
where TValidator : class, IValidator<TCommand>
{
// Register validator
services.AddTransient<IValidator<TCommand>, TValidator>();
// Register command with events and correlation
return services.AddCommandWithEventsAndCorrelation<TCommand, TResult, TEvents, THandler>();
}
// ============================================================================
// WORKFLOW-BASED COMMAND REGISTRATION (NEW API)
// ============================================================================
/// <summary>
/// Register a command handler that participates in a workflow and returns a result.
/// The workflow manages event emission and correlation automatically.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <typeparam name="TWorkflow">The workflow type that manages events. Must inherit from <see cref="Workflow"/> and have a parameterless constructor.</typeparam>
/// <typeparam name="THandler">The handler type implementing ICommandHandlerWithWorkflow.</typeparam>
/// <remarks>
/// <para>
/// <strong>Workflow Pattern (Recommended):</strong>
/// This is the recommended way to register commands that emit events. Workflows provide:
/// - Clearer business process modeling (workflow = business process)
/// - Automatic correlation ID management (workflow ID = correlation ID)
/// - Type-safe event emission within workflow boundaries
/// - Foundation for multi-step workflows and event sourcing
/// </para>
/// <para>
/// <strong>Example Usage:</strong>
/// <code>
/// services.AddCommandWithWorkflow&lt;InviteUserCommand, string, InvitationWorkflow, InviteUserCommandHandler&gt;();
/// </code>
/// </para>
/// <para>
/// <strong>Phase 1 Behavior:</strong>
/// - Each command execution creates a new workflow instance
/// - Workflow ID is auto-generated (GUID)
/// - All events emitted within the handler receive the workflow ID as correlation ID
/// </para>
/// <para>
/// <strong>Future Phases:</strong>
/// Later phases will add workflow continuation (resume existing workflows by ID) and
/// persistent workflow state for event sourcing scenarios.
/// </para>
/// </remarks>
public static IServiceCollection AddCommandWithWorkflow<TCommand, TResult, TWorkflow, THandler>(this IServiceCollection services)
where TCommand : class
where TWorkflow : Workflow, new()
where THandler : class, ICommandHandlerWithWorkflow<TCommand, TResult, TWorkflow>
{
// Register the actual handler
services.AddTransient<THandler>();
// Register metadata for discovery
var commandMeta = new CommandMeta(typeof(TCommand), typeof(THandler), typeof(TResult));
services.AddSingleton<ICommandMeta>(commandMeta);
// Register the decorator as the ICommandHandler that the framework uses
services.AddTransient<ICommandHandler<TCommand, TResult>>(sp =>
{
var handler = sp.GetRequiredService<THandler>();
var eventEmitter = sp.GetRequiredService<IEventEmitter>();
return new CommandHandlerWithWorkflowDecorator<TCommand, TResult, TWorkflow>(handler, eventEmitter);
});
return services;
}
/// <summary>
/// Register a command handler that participates in a workflow, returns a result, and includes FluentValidation support.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <typeparam name="TResult">The result type.</typeparam>
/// <typeparam name="TWorkflow">The workflow type that manages events. Must inherit from <see cref="Workflow"/> and have a parameterless constructor.</typeparam>
/// <typeparam name="THandler">The handler type implementing ICommandHandlerWithWorkflow.</typeparam>
/// <typeparam name="TValidator">The FluentValidation validator type.</typeparam>
/// <remarks>
/// This is a convenience overload that registers both the validator and the workflow command.
/// Equivalent to calling AddTransient&lt;IValidator&lt;TCommand&gt;, TValidator&gt;() followed by AddCommandWithWorkflow().
/// </remarks>
public static IServiceCollection AddCommandWithWorkflow<TCommand, TResult, TWorkflow, THandler, TValidator>(this IServiceCollection services)
where TCommand : class
where TWorkflow : Workflow, new()
where THandler : class, ICommandHandlerWithWorkflow<TCommand, TResult, TWorkflow>
where TValidator : class, IValidator<TCommand>
{
// Register validator
services.AddTransient<IValidator<TCommand>, TValidator>();
// Register command with workflow
return services.AddCommandWithWorkflow<TCommand, TResult, TWorkflow, THandler>();
}
/// <summary>
/// Register a command handler that participates in a workflow but does not return a result.
/// The workflow manages event emission and correlation automatically.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <typeparam name="TWorkflow">The workflow type that manages events. Must inherit from <see cref="Workflow"/> and have a parameterless constructor.</typeparam>
/// <typeparam name="THandler">The handler type implementing ICommandHandlerWithWorkflow.</typeparam>
/// <remarks>
/// This is the "no result" variant of <see cref="AddCommandWithWorkflow{TCommand, TResult, TWorkflow, THandler}(IServiceCollection)"/>.
/// Use this when your command performs an action but doesn't need to return a value.
/// </remarks>
public static IServiceCollection AddCommandWithWorkflow<TCommand, TWorkflow, THandler>(this IServiceCollection services)
where TCommand : class
where TWorkflow : Workflow, new()
where THandler : class, ICommandHandlerWithWorkflow<TCommand, TWorkflow>
{
// Register the actual handler
services.AddTransient<THandler>();
// Register metadata for discovery (no result type)
var commandMeta = new CommandMeta(typeof(TCommand), typeof(THandler));
services.AddSingleton<ICommandMeta>(commandMeta);
// Register the decorator as the ICommandHandler that the framework uses
services.AddTransient<ICommandHandler<TCommand>>(sp =>
{
var handler = sp.GetRequiredService<THandler>();
var eventEmitter = sp.GetRequiredService<IEventEmitter>();
return new CommandHandlerWithWorkflowDecoratorNoResult<TCommand, TWorkflow>(handler, eventEmitter);
});
return services;
}
// ============================================================================
// EVENT STREAMING CONFIGURATION API (FLUENT BUILDER)
// ============================================================================
/// <summary>
/// Configures event streaming services using a fluent API.
/// </summary>
/// <param name="services">The service collection to configure.</param>
/// <param name="configure">Action to configure streaming options using the fluent builder.</param>
/// <returns>The service collection for method chaining.</returns>
/// <remarks>
/// <para>
/// <strong>Purpose:</strong>
/// This method provides a centralized way to configure all event streaming aspects including
/// streams, subscriptions, delivery options, and storage implementations.
/// </para>
/// <para>
/// <strong>Phase 1 Usage:</strong>
/// <code>
/// services.AddEventStreaming(streaming =>
/// {
/// // Configure a stream
/// streaming.AddStream&lt;UserWorkflow&gt;(stream =>
/// {
/// stream.Type = StreamType.Ephemeral;
/// stream.DeliverySemantics = DeliverySemantics.AtLeastOnce;
/// });
///
/// // Configure a subscription
/// streaming.AddSubscription&lt;UserWorkflow&gt;("analytics", sub =>
/// {
/// sub.Mode = SubscriptionMode.Broadcast;
/// });
/// });
/// </code>
/// </para>
/// <para>
/// <strong>Progressive Complexity:</strong>
/// Additional configuration options (subscriptions, external delivery, schema evolution)
/// will be added to the builder in later phases, maintaining backward compatibility.
/// </para>
/// </remarks>
public static IServiceCollection AddEventStreaming(
this IServiceCollection services,
Action<EventStreamingBuilder>? configure = null)
{
var builder = new EventStreamingBuilder(services);
// Invoke user configuration
configure?.Invoke(builder);
// Subscriptions are now automatically registered via EventSubscriptionClient constructor
// which receives IEnumerable<Subscription> from DI
return services;
}
// ========================================================================
// Phase 3: Exactly-Once Delivery & Read Receipt Configuration
// ========================================================================
/// <summary>
/// Configure exactly-once delivery options.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="configure">Configuration action for exactly-once delivery options.</param>
/// <returns>The service collection for method chaining.</returns>
/// <example>
/// <code>
/// services.ConfigureExactlyOnceDelivery(options =>
/// {
/// options.LockDuration = TimeSpan.FromSeconds(60);
/// options.MaxRetries = 5;
/// options.RetryDelay = TimeSpan.FromMilliseconds(200);
/// options.UseExponentialBackoff = true;
/// });
/// </code>
/// </example>
public static IServiceCollection ConfigureExactlyOnceDelivery(
this IServiceCollection services,
Action<ExactlyOnceDeliveryOptions> configure)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
if (configure == null)
throw new ArgumentNullException(nameof(configure));
services.Configure(configure);
return services;
}
/// <summary>
/// Configure read receipt tracking and cleanup options.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="configure">Configuration action for read receipt options.</param>
/// <returns>The service collection for method chaining.</returns>
/// <example>
/// <code>
/// services.ConfigureReadReceipts(options =>
/// {
/// options.EnableAutoCleanup = true;
/// options.CleanupInterval = TimeSpan.FromHours(6);
/// options.RetentionPeriod = TimeSpan.FromDays(60);
/// });
/// </code>
/// </example>
public static IServiceCollection ConfigureReadReceipts(
this IServiceCollection services,
Action<ReadReceiptOptions> configure)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
if (configure == null)
throw new ArgumentNullException(nameof(configure));
services.Configure(configure);
return services;
}
/// <summary>
/// Add read receipt cleanup background service.
/// </summary>
/// <param name="services">The service collection.</param>
/// <returns>The service collection for method chaining.</returns>
/// <remarks>
/// The cleanup service will run based on the configured ReadReceiptOptions.
/// If EnableAutoCleanup is false, the service will not perform any cleanup.
/// </remarks>
public static IServiceCollection AddReadReceiptCleanup(this IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
services.AddHostedService<ReadReceiptCleanupService>();
return services;
}
/// <summary>
/// Add idempotency cleanup background service.
/// </summary>
/// <param name="services">The service collection.</param>
/// <returns>The service collection for method chaining.</returns>
/// <remarks>
/// The cleanup service will periodically remove old processed event records
/// and expired idempotency locks.
/// </remarks>
public static IServiceCollection AddIdempotencyCleanup(this IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
services.AddHostedService<IdempotencyCleanupService>();
return services;
}
// ========================================================================
// Phase 5: Schema Evolution & Versioning
// ========================================================================
/// <summary>
/// Adds schema evolution support with in-memory schema storage.
/// </summary>
/// <remarks>
/// <para>
/// <strong>Registered Services:</strong>
/// - <see cref="ISchemaRegistry"/> - Schema registration and upcasting
/// - <see cref="ISchemaStore"/> - In-memory schema storage
/// </para>
/// <para>
/// For production use with persistent storage, use <see cref="AddPostgresSchemaStore"/>
/// or implement a custom <see cref="ISchemaStore"/>.
/// </para>
/// </remarks>
public static IServiceCollection AddSchemaEvolution(this IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
// Register schema store (in-memory by default)
services.TryAddSingleton<ISchemaStore, InMemorySchemaStore>();
// Register schema registry
services.TryAddSingleton<ISchemaRegistry, SchemaRegistry>();
return services;
}
/// <summary>
/// Adds JSON Schema generation support for event versioning.
/// </summary>
/// <remarks>
/// <para>
/// This is optional. If registered, the schema registry will automatically
/// generate JSON schemas for registered event types.
/// </para>
/// <para>
/// JSON schemas enable:
/// - External consumers (non-.NET) to understand event structure
/// - Schema validation
/// - Documentation generation
/// - Code generation for other languages
/// </para>
/// </remarks>
public static IServiceCollection AddJsonSchemaGeneration(this IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
services.TryAddSingleton<IJsonSchemaGenerator, SystemTextJsonSchemaGenerator>();
return services;
}
// ========================================================================
// Phase 6: Management, Monitoring & Observability
// ========================================================================
/// <summary>
/// Adds stream health check services (Phase 6).
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="configure">Optional configuration action for health check options.</param>
/// <returns>The service collection for chaining.</returns>
/// <remarks>
/// <para>
/// <strong>Registered Services:</strong>
/// - <see cref="IStreamHealthCheck"/> - Stream and subscription health checking
/// - <see cref="StreamHealthCheckOptions"/> - Configurable thresholds for lag and staleness
/// </para>
/// <para>
/// <strong>Health Check Capabilities:</strong>
/// - Stream availability and writability
/// - Consumer lag detection (events behind stream head)
/// - Stalled consumer detection (no progress for N minutes)
/// - Configurable degraded/unhealthy thresholds
/// </para>
/// </remarks>
public static IServiceCollection AddStreamHealthChecks(
this IServiceCollection services,
Action<StreamHealthCheckOptions>? configure = null)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
if (configure != null)
{
services.Configure(configure);
}
else
{
services.Configure<StreamHealthCheckOptions>(_ => { });
}
services.TryAddSingleton<IStreamHealthCheck, StreamHealthCheck>();
return services;
}
/// <summary>
/// Adds event stream metrics and telemetry (Phase 6).
/// </summary>
/// <param name="services">The service collection.</param>
/// <returns>The service collection for chaining.</returns>
/// <remarks>
/// <para>
/// <strong>Registered Services:</strong>
/// - <see cref="IEventStreamMetrics"/> - Metrics collection for event streaming
/// </para>
/// <para>
/// <strong>Collected Metrics:</strong>
/// - Events published per stream (counter with stream/event_type tags)
/// - Events consumed per subscription (counter with stream/subscription/event_type tags)
/// - Processing latency (histogram with stream/subscription tags)
/// - Consumer lag (gauge with stream/subscription tags)
/// - Error rate (counter with stream/subscription/error_type tags)
/// - Retry count (counter with stream/subscription/attempt tags)
/// - Stream length (gauge with stream tag)
/// - Active consumers (gauge with stream/subscription tags)
/// </para>
/// <para>
/// <strong>OpenTelemetry Integration:</strong>
/// This implementation uses .NET's System.Diagnostics.Metrics API which is
/// automatically discovered by OpenTelemetry instrumentation. To export metrics:
/// <code>
/// services.AddOpenTelemetry()
/// .WithMetrics(builder => builder
/// .AddMeter("Svrnty.CQRS.Events")
/// .AddPrometheusExporter());
/// </code>
/// </para>
/// <para>
/// <strong>Prometheus Integration:</strong>
/// Use OpenTelemetry's Prometheus exporter to expose metrics at /metrics endpoint.
/// All metrics use the prefix "svrnty_cqrs_events_" in Prometheus format.
/// </para>
/// </remarks>
public static IServiceCollection AddEventStreamMetrics(this IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
services.TryAddSingleton<IEventStreamMetrics, EventStreamMetrics>();
return services;
}
}