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