using System; using Svrnty.CQRS.Events.Core; using Svrnty.CQRS.Events.Abstractions.Models; using Svrnty.CQRS.Events.Abstractions.Correlation; using Svrnty.CQRS.Events.Abstractions.EventHandlers; using Svrnty.CQRS.Events.Abstractions.EventStore; using System.Threading; using System.Threading.Tasks; using Svrnty.CQRS.Abstractions; using Svrnty.CQRS.Events.Abstractions; namespace Svrnty.CQRS.Events.Decorators; /// /// Decorator that wraps a command handler with events and automatically manages correlation IDs and event emission. /// /// The command type. /// The result type. /// The base type or marker interface for events this command can emit. internal sealed class CommandHandlerWithEventsDecorator : ICommandHandler where TCommand : class where TEvents : ICorrelatedEvent { private readonly ICommandHandlerWithEvents _inner; private readonly IEventEmitter _eventEmitter; private readonly ICorrelationStore? _correlationStore; public CommandHandlerWithEventsDecorator( ICommandHandlerWithEvents inner, IEventEmitter eventEmitter, ICorrelationStore? correlationStore = null) { _inner = inner; _eventEmitter = eventEmitter; _correlationStore = correlationStore; } public async Task HandleAsync(TCommand command, CancellationToken cancellationToken = default) { // Create event context for collecting events var eventContext = new EventContext(_correlationStore); // Execute the handler with the event context var result = await _inner.HandleAsync(command, eventContext, cancellationToken); // Determine correlation ID based on context string correlationId; if (eventContext.IsCorrelationLoaded) { // Correlation was loaded from business data if (string.IsNullOrEmpty(eventContext.CorrelationId)) { // No existing correlation found, generate new one correlationId = Guid.NewGuid().ToString(); // Store the new correlation ID with the key if (_correlationStore != null && eventContext.LoadedKeyHash != null) { await _correlationStore.SetCorrelationIdAsync(eventContext.LoadedKeyHash, correlationId, cancellationToken); } } else { // Use existing correlation ID from store correlationId = eventContext.CorrelationId; } } else if (command is ICorrelatedCommand correlatedCommand && !string.IsNullOrWhiteSpace(correlatedCommand.CorrelationId)) { // Use correlation ID from command (legacy approach for backward compatibility) correlationId = correlatedCommand.CorrelationId; } else { // Generate new correlation ID correlationId = Guid.NewGuid().ToString(); } // Assign correlation IDs to all events eventContext.AssignCorrelationIds(correlationId); // Emit all events if (eventContext.Events.Count > 0) { await _eventEmitter.EmitBatchAsync(eventContext.Events, cancellationToken); } // Return the result return result; } } /// /// Decorator for commands that don't return a result but emit events. /// /// The command type. /// The base type or marker interface for events this command can emit. internal sealed class CommandHandlerWithEventsDecoratorNoResult : ICommandHandler where TCommand : class where TEvents : ICorrelatedEvent { private readonly ICommandHandlerWithEvents _inner; private readonly IEventEmitter _eventEmitter; private readonly ICorrelationStore? _correlationStore; public CommandHandlerWithEventsDecoratorNoResult( ICommandHandlerWithEvents inner, IEventEmitter eventEmitter, ICorrelationStore? correlationStore = null) { _inner = inner; _eventEmitter = eventEmitter; _correlationStore = correlationStore; } public async Task HandleAsync(TCommand command, CancellationToken cancellationToken = default) { // Create event context for collecting events var eventContext = new EventContext(_correlationStore); // Execute the handler with the event context await _inner.HandleAsync(command, eventContext, cancellationToken); // Determine correlation ID based on context string correlationId; if (eventContext.IsCorrelationLoaded) { // Correlation was loaded from business data if (string.IsNullOrEmpty(eventContext.CorrelationId)) { // No existing correlation found, generate new one correlationId = Guid.NewGuid().ToString(); // Store the new correlation ID with the key if (_correlationStore != null && eventContext.LoadedKeyHash != null) { await _correlationStore.SetCorrelationIdAsync(eventContext.LoadedKeyHash, correlationId, cancellationToken); } } else { // Use existing correlation ID from store correlationId = eventContext.CorrelationId; } } else if (command is ICorrelatedCommand correlatedCommand && !string.IsNullOrWhiteSpace(correlatedCommand.CorrelationId)) { // Use correlation ID from command (legacy approach for backward compatibility) correlationId = correlatedCommand.CorrelationId; } else { // Generate new correlation ID correlationId = Guid.NewGuid().ToString(); } // Assign correlation IDs to all events eventContext.AssignCorrelationIds(correlationId); // Emit all events if (eventContext.Events.Count > 0) { await _eventEmitter.EmitBatchAsync(eventContext.Events, cancellationToken); } } } /// /// Decorator for commands that return both result and correlation ID. /// Useful for multi-step workflows where correlation ID needs to be passed to follow-up commands. /// /// The command type. /// The result type. /// The base type or marker interface for events this command can emit. internal sealed class CommandHandlerWithEventsAndCorrelationDecorator : ICommandHandler> where TCommand : class where TEvents : ICorrelatedEvent { private readonly ICommandHandlerWithEventsAndCorrelation _inner; private readonly IEventEmitter _eventEmitter; private readonly ICorrelationStore? _correlationStore; public CommandHandlerWithEventsAndCorrelationDecorator( ICommandHandlerWithEventsAndCorrelation inner, IEventEmitter eventEmitter, ICorrelationStore? correlationStore = null) { _inner = inner; _eventEmitter = eventEmitter; _correlationStore = correlationStore; } public async Task> HandleAsync(TCommand command, CancellationToken cancellationToken = default) { // Create event context for collecting events var eventContext = new EventContext(_correlationStore); // Execute the handler with the event context var result = await _inner.HandleAsync(command, eventContext, cancellationToken); // Determine correlation ID based on context string correlationId; if (eventContext.IsCorrelationLoaded) { // Correlation was loaded from business data if (string.IsNullOrEmpty(eventContext.CorrelationId)) { // No existing correlation found, generate new one correlationId = Guid.NewGuid().ToString(); // Store the new correlation ID with the key if (_correlationStore != null && eventContext.LoadedKeyHash != null) { await _correlationStore.SetCorrelationIdAsync(eventContext.LoadedKeyHash, correlationId, cancellationToken); } } else { // Use existing correlation ID from store correlationId = eventContext.CorrelationId; } } else if (command is ICorrelatedCommand correlatedCommand && !string.IsNullOrWhiteSpace(correlatedCommand.CorrelationId)) { // Use correlation ID from command (legacy approach for backward compatibility) correlationId = correlatedCommand.CorrelationId; } else { // Generate new correlation ID correlationId = Guid.NewGuid().ToString(); } // Assign correlation IDs to all events eventContext.AssignCorrelationIds(correlationId); // Emit all events if (eventContext.Events.Count > 0) { await _eventEmitter.EmitBatchAsync(eventContext.Events, cancellationToken); } // Return result with correlation ID return new CommandResultWithCorrelation { Result = result, CorrelationId = correlationId }; } }