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; } }