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;
///
/// Service collection extensions for registering event services.
///
public static class ServiceCollectionExtensions
{
///
/// Add core event services (emitter, subscription service, delivery service).
/// Note: Storage implementations (IEventStore, ISubscriptionStore) must be registered separately.
///
public static IServiceCollection AddSvrntyEvents(this IServiceCollection services)
{
services.TryAddTransient();
services.TryAddTransient();
services.TryAddTransient();
// Phase 1.4: Subscription client for consuming events
services.TryAddSingleton();
// Phase 3.2: Exactly-once delivery decorator
services.TryAddSingleton();
return services;
}
///
/// Add default event discovery service.
///
public static IServiceCollection AddDefaultEventDiscovery(this IServiceCollection services)
{
services.TryAddTransient();
return services;
}
///
/// Register an event type for discovery.
/// This allows the event to be discovered by IEventDiscovery and code generators.
///
public static IServiceCollection AddEvent(
this IServiceCollection services,
string? description = null)
where TEvent : class, ICorrelatedEvent
{
var eventType = typeof(TEvent);
var meta = new EventMeta(eventType, description);
services.AddSingleton(meta);
return services;
}
///
/// Add in-memory storage implementations for events and subscriptions.
/// Suitable for development and testing. Data is lost on application restart.
///
///
///
/// Registered Services:
/// - - Legacy persistent event storage
/// - - New stream-based storage (Phase 1.3)
/// - - Subscription configuration storage
/// - - Correlation ID tracking
/// - - Active consumer tracking (Phase 1.3)
///
///
public static IServiceCollection AddInMemoryEventStorage(this IServiceCollection services)
{
// Legacy event storage (persistent)
services.TryAddSingleton();
// Phase 1.3: Stream-based storage (ephemeral)
services.TryAddSingleton();
// Subscription and consumer management
services.TryAddSingleton();
services.TryAddSingleton();
// Correlation tracking
services.TryAddSingleton();
// Phase 3.1: Idempotency store for exactly-once delivery
services.TryAddSingleton();
// Phase 3.3: Read receipt store for consumer progress tracking
services.TryAddSingleton();
return services;
}
///
/// Register a command handler that automatically manages event emission with correlation IDs.
/// Events are strongly-typed via the TEvents base type or marker interface.
///
/// The command type.
/// The result type.
/// The base type or marker interface for events this command can emit.
/// The handler type implementing ICommandHandlerWithEvents.
public static IServiceCollection AddCommandWithEvents(this IServiceCollection services)
where TCommand : class
where TEvents : ICorrelatedEvent
where THandler : class, ICommandHandlerWithEvents
{
// Register the actual handler
services.AddTransient();
// Register metadata for discovery
var commandMeta = new CommandMeta(typeof(TCommand), typeof(THandler), typeof(TResult));
services.AddSingleton(commandMeta);
// Register the decorator as the ICommandHandler that the framework uses
services.AddTransient>(sp =>
{
var handler = sp.GetRequiredService();
var eventEmitter = sp.GetRequiredService();
var correlationStore = sp.GetService(); // Optional
return new CommandHandlerWithEventsDecorator(handler, eventEmitter, correlationStore);
});
return services;
}
///
/// Register a command handler with FluentValidation that automatically manages event emission.
///
/// The command type.
/// The result type.
/// The base type or marker interface for events this command can emit.
/// The handler type implementing ICommandHandlerWithEvents.
/// The FluentValidation validator type.
public static IServiceCollection AddCommandWithEvents(this IServiceCollection services)
where TCommand : class
where TEvents : ICorrelatedEvent
where THandler : class, ICommandHandlerWithEvents
where TValidator : class, IValidator
{
// Register validator
services.AddTransient, TValidator>();
// Register command with events
return services.AddCommandWithEvents();
}
///
/// Register a command handler (no result) that automatically manages event emission.
/// Events are strongly-typed via the TEvents base type or marker interface.
///
/// The command type.
/// The base type or marker interface for events this command can emit.
/// The handler type implementing ICommandHandlerWithEvents.
public static IServiceCollection AddCommandWithEvents(this IServiceCollection services)
where TCommand : class
where TEvents : ICorrelatedEvent
where THandler : class, ICommandHandlerWithEvents
{
// Register the actual handler
services.AddTransient();
// Register metadata for discovery
var commandMeta = new CommandMeta(typeof(TCommand), typeof(THandler));
services.AddSingleton(commandMeta);
// Register the decorator as the ICommandHandler that the framework uses
services.AddTransient>(sp =>
{
var handler = sp.GetRequiredService();
var eventEmitter = sp.GetRequiredService();
var correlationStore = sp.GetService(); // Optional
return new CommandHandlerWithEventsDecoratorNoResult(handler, eventEmitter, correlationStore);
});
return services;
}
///
/// 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.
///
/// The command type.
/// The result type.
/// The base type or marker interface for events this command can emit.
/// The handler type implementing ICommandHandlerWithEventsAndCorrelation.
public static IServiceCollection AddCommandWithEventsAndCorrelation(this IServiceCollection services)
where TCommand : class
where TEvents : ICorrelatedEvent
where THandler : class, ICommandHandlerWithEventsAndCorrelation
{
// Register the actual handler
services.AddTransient();
// Register metadata for discovery (with the wrapped result type)
var commandMeta = new CommandMeta(typeof(TCommand), typeof(THandler), typeof(CommandResultWithCorrelation));
services.AddSingleton(commandMeta);
// Register the decorator as the ICommandHandler that the framework uses
services.AddTransient>>(sp =>
{
var handler = sp.GetRequiredService();
var eventEmitter = sp.GetRequiredService();
var correlationStore = sp.GetService(); // Optional
return new CommandHandlerWithEventsAndCorrelationDecorator(handler, eventEmitter, correlationStore);
});
return services;
}
///
/// 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.
///
/// The command type.
/// The result type.
/// The base type or marker interface for events this command can emit.
/// The handler type implementing ICommandHandlerWithEventsAndCorrelation.
/// The FluentValidation validator type.
public static IServiceCollection AddCommandWithEventsAndCorrelation(this IServiceCollection services)
where TCommand : class
where TEvents : ICorrelatedEvent
where THandler : class, ICommandHandlerWithEventsAndCorrelation
where TValidator : class, IValidator
{
// Register validator
services.AddTransient, TValidator>();
// Register command with events and correlation
return services.AddCommandWithEventsAndCorrelation();
}
// ============================================================================
// WORKFLOW-BASED COMMAND REGISTRATION (NEW API)
// ============================================================================
///
/// Register a command handler that participates in a workflow and returns a result.
/// The workflow manages event emission and correlation automatically.
///
/// The command type.
/// The result type.
/// The workflow type that manages events. Must inherit from and have a parameterless constructor.
/// The handler type implementing ICommandHandlerWithWorkflow.
///
///
/// Workflow Pattern (Recommended):
/// 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
///
///
/// Example Usage:
///
/// services.AddCommandWithWorkflow<InviteUserCommand, string, InvitationWorkflow, InviteUserCommandHandler>();
///
///
///
/// Phase 1 Behavior:
/// - 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
///
///
/// Future Phases:
/// Later phases will add workflow continuation (resume existing workflows by ID) and
/// persistent workflow state for event sourcing scenarios.
///
///
public static IServiceCollection AddCommandWithWorkflow(this IServiceCollection services)
where TCommand : class
where TWorkflow : Workflow, new()
where THandler : class, ICommandHandlerWithWorkflow
{
// Register the actual handler
services.AddTransient();
// Register metadata for discovery
var commandMeta = new CommandMeta(typeof(TCommand), typeof(THandler), typeof(TResult));
services.AddSingleton(commandMeta);
// Register the decorator as the ICommandHandler that the framework uses
services.AddTransient>(sp =>
{
var handler = sp.GetRequiredService();
var eventEmitter = sp.GetRequiredService();
return new CommandHandlerWithWorkflowDecorator(handler, eventEmitter);
});
return services;
}
///
/// Register a command handler that participates in a workflow, returns a result, and includes FluentValidation support.
///
/// The command type.
/// The result type.
/// The workflow type that manages events. Must inherit from and have a parameterless constructor.
/// The handler type implementing ICommandHandlerWithWorkflow.
/// The FluentValidation validator type.
///
/// This is a convenience overload that registers both the validator and the workflow command.
/// Equivalent to calling AddTransient<IValidator<TCommand>, TValidator>() followed by AddCommandWithWorkflow().
///
public static IServiceCollection AddCommandWithWorkflow(this IServiceCollection services)
where TCommand : class
where TWorkflow : Workflow, new()
where THandler : class, ICommandHandlerWithWorkflow
where TValidator : class, IValidator
{
// Register validator
services.AddTransient, TValidator>();
// Register command with workflow
return services.AddCommandWithWorkflow();
}
///
/// Register a command handler that participates in a workflow but does not return a result.
/// The workflow manages event emission and correlation automatically.
///
/// The command type.
/// The workflow type that manages events. Must inherit from and have a parameterless constructor.
/// The handler type implementing ICommandHandlerWithWorkflow.
///
/// This is the "no result" variant of .
/// Use this when your command performs an action but doesn't need to return a value.
///
public static IServiceCollection AddCommandWithWorkflow(this IServiceCollection services)
where TCommand : class
where TWorkflow : Workflow, new()
where THandler : class, ICommandHandlerWithWorkflow
{
// Register the actual handler
services.AddTransient();
// Register metadata for discovery (no result type)
var commandMeta = new CommandMeta(typeof(TCommand), typeof(THandler));
services.AddSingleton(commandMeta);
// Register the decorator as the ICommandHandler that the framework uses
services.AddTransient>(sp =>
{
var handler = sp.GetRequiredService();
var eventEmitter = sp.GetRequiredService();
return new CommandHandlerWithWorkflowDecoratorNoResult(handler, eventEmitter);
});
return services;
}
// ============================================================================
// EVENT STREAMING CONFIGURATION API (FLUENT BUILDER)
// ============================================================================
///
/// Configures event streaming services using a fluent API.
///
/// The service collection to configure.
/// Action to configure streaming options using the fluent builder.
/// The service collection for method chaining.
///
///
/// Purpose:
/// This method provides a centralized way to configure all event streaming aspects including
/// streams, subscriptions, delivery options, and storage implementations.
///
///
/// Phase 1 Usage:
///
/// services.AddEventStreaming(streaming =>
/// {
/// // Configure a stream
/// streaming.AddStream<UserWorkflow>(stream =>
/// {
/// stream.Type = StreamType.Ephemeral;
/// stream.DeliverySemantics = DeliverySemantics.AtLeastOnce;
/// });
///
/// // Configure a subscription
/// streaming.AddSubscription<UserWorkflow>("analytics", sub =>
/// {
/// sub.Mode = SubscriptionMode.Broadcast;
/// });
/// });
///
///
///
/// Progressive Complexity:
/// Additional configuration options (subscriptions, external delivery, schema evolution)
/// will be added to the builder in later phases, maintaining backward compatibility.
///
///
public static IServiceCollection AddEventStreaming(
this IServiceCollection services,
Action? configure = null)
{
var builder = new EventStreamingBuilder(services);
// Invoke user configuration
configure?.Invoke(builder);
// Subscriptions are now automatically registered via EventSubscriptionClient constructor
// which receives IEnumerable from DI
return services;
}
// ========================================================================
// Phase 3: Exactly-Once Delivery & Read Receipt Configuration
// ========================================================================
///
/// Configure exactly-once delivery options.
///
/// The service collection.
/// Configuration action for exactly-once delivery options.
/// The service collection for method chaining.
///
///
/// services.ConfigureExactlyOnceDelivery(options =>
/// {
/// options.LockDuration = TimeSpan.FromSeconds(60);
/// options.MaxRetries = 5;
/// options.RetryDelay = TimeSpan.FromMilliseconds(200);
/// options.UseExponentialBackoff = true;
/// });
///
///
public static IServiceCollection ConfigureExactlyOnceDelivery(
this IServiceCollection services,
Action configure)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
if (configure == null)
throw new ArgumentNullException(nameof(configure));
services.Configure(configure);
return services;
}
///
/// Configure read receipt tracking and cleanup options.
///
/// The service collection.
/// Configuration action for read receipt options.
/// The service collection for method chaining.
///
///
/// services.ConfigureReadReceipts(options =>
/// {
/// options.EnableAutoCleanup = true;
/// options.CleanupInterval = TimeSpan.FromHours(6);
/// options.RetentionPeriod = TimeSpan.FromDays(60);
/// });
///
///
public static IServiceCollection ConfigureReadReceipts(
this IServiceCollection services,
Action configure)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
if (configure == null)
throw new ArgumentNullException(nameof(configure));
services.Configure(configure);
return services;
}
///
/// Add read receipt cleanup background service.
///
/// The service collection.
/// The service collection for method chaining.
///
/// The cleanup service will run based on the configured ReadReceiptOptions.
/// If EnableAutoCleanup is false, the service will not perform any cleanup.
///
public static IServiceCollection AddReadReceiptCleanup(this IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
services.AddHostedService();
return services;
}
///
/// Add idempotency cleanup background service.
///
/// The service collection.
/// The service collection for method chaining.
///
/// The cleanup service will periodically remove old processed event records
/// and expired idempotency locks.
///
public static IServiceCollection AddIdempotencyCleanup(this IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
services.AddHostedService();
return services;
}
// ========================================================================
// Phase 5: Schema Evolution & Versioning
// ========================================================================
///
/// Adds schema evolution support with in-memory schema storage.
///
///
///
/// Registered Services:
/// - - Schema registration and upcasting
/// - - In-memory schema storage
///
///
/// For production use with persistent storage, use
/// or implement a custom .
///
///
public static IServiceCollection AddSchemaEvolution(this IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
// Register schema store (in-memory by default)
services.TryAddSingleton();
// Register schema registry
services.TryAddSingleton();
return services;
}
///
/// Adds JSON Schema generation support for event versioning.
///
///
///
/// This is optional. If registered, the schema registry will automatically
/// generate JSON schemas for registered event types.
///
///
/// JSON schemas enable:
/// - External consumers (non-.NET) to understand event structure
/// - Schema validation
/// - Documentation generation
/// - Code generation for other languages
///
///
public static IServiceCollection AddJsonSchemaGeneration(this IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
services.TryAddSingleton();
return services;
}
// ========================================================================
// Phase 6: Management, Monitoring & Observability
// ========================================================================
///
/// Adds stream health check services (Phase 6).
///
/// The service collection.
/// Optional configuration action for health check options.
/// The service collection for chaining.
///
///
/// Registered Services:
/// - - Stream and subscription health checking
/// - - Configurable thresholds for lag and staleness
///
///
/// Health Check Capabilities:
/// - Stream availability and writability
/// - Consumer lag detection (events behind stream head)
/// - Stalled consumer detection (no progress for N minutes)
/// - Configurable degraded/unhealthy thresholds
///
///
public static IServiceCollection AddStreamHealthChecks(
this IServiceCollection services,
Action? configure = null)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
if (configure != null)
{
services.Configure(configure);
}
else
{
services.Configure(_ => { });
}
services.TryAddSingleton();
return services;
}
///
/// Adds event stream metrics and telemetry (Phase 6).
///
/// The service collection.
/// The service collection for chaining.
///
///
/// Registered Services:
/// - - Metrics collection for event streaming
///
///
/// Collected Metrics:
/// - 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)
///
///
/// OpenTelemetry Integration:
/// This implementation uses .NET's System.Diagnostics.Metrics API which is
/// automatically discovered by OpenTelemetry instrumentation. To export metrics:
///
/// services.AddOpenTelemetry()
/// .WithMetrics(builder => builder
/// .AddMeter("Svrnty.CQRS.Events")
/// .AddPrometheusExporter());
///
///
///
/// Prometheus Integration:
/// Use OpenTelemetry's Prometheus exporter to expose metrics at /metrics endpoint.
/// All metrics use the prefix "svrnty_cqrs_events_" in Prometheus format.
///
///
public static IServiceCollection AddEventStreamMetrics(this IServiceCollection services)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
services.TryAddSingleton();
return services;
}
}