dotnet-cqrs/Svrnty.CQRS.Events/Decorators/CommandHandlerWithEventsDecorator.cs

254 lines
9.7 KiB
C#

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;
/// <summary>
/// Decorator that wraps a command handler with events and automatically manages correlation IDs and 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>
internal sealed class CommandHandlerWithEventsDecorator<TCommand, TResult, TEvents> : ICommandHandler<TCommand, TResult>
where TCommand : class
where TEvents : ICorrelatedEvent
{
private readonly ICommandHandlerWithEvents<TCommand, TResult, TEvents> _inner;
private readonly IEventEmitter _eventEmitter;
private readonly ICorrelationStore? _correlationStore;
public CommandHandlerWithEventsDecorator(
ICommandHandlerWithEvents<TCommand, TResult, TEvents> inner,
IEventEmitter eventEmitter,
ICorrelationStore? correlationStore = null)
{
_inner = inner;
_eventEmitter = eventEmitter;
_correlationStore = correlationStore;
}
public async Task<TResult> HandleAsync(TCommand command, CancellationToken cancellationToken = default)
{
// Create event context for collecting events
var eventContext = new EventContext<TEvents>(_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;
}
}
/// <summary>
/// Decorator for commands that don't return a result but emit events.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <typeparam name="TEvents">The base type or marker interface for events this command can emit.</typeparam>
internal sealed class CommandHandlerWithEventsDecoratorNoResult<TCommand, TEvents> : ICommandHandler<TCommand>
where TCommand : class
where TEvents : ICorrelatedEvent
{
private readonly ICommandHandlerWithEvents<TCommand, TEvents> _inner;
private readonly IEventEmitter _eventEmitter;
private readonly ICorrelationStore? _correlationStore;
public CommandHandlerWithEventsDecoratorNoResult(
ICommandHandlerWithEvents<TCommand, TEvents> 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<TEvents>(_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);
}
}
}
/// <summary>
/// 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.
/// </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>
internal sealed class CommandHandlerWithEventsAndCorrelationDecorator<TCommand, TResult, TEvents> : ICommandHandler<TCommand, CommandResultWithCorrelation<TResult>>
where TCommand : class
where TEvents : ICorrelatedEvent
{
private readonly ICommandHandlerWithEventsAndCorrelation<TCommand, TResult, TEvents> _inner;
private readonly IEventEmitter _eventEmitter;
private readonly ICorrelationStore? _correlationStore;
public CommandHandlerWithEventsAndCorrelationDecorator(
ICommandHandlerWithEventsAndCorrelation<TCommand, TResult, TEvents> inner,
IEventEmitter eventEmitter,
ICorrelationStore? correlationStore = null)
{
_inner = inner;
_eventEmitter = eventEmitter;
_correlationStore = correlationStore;
}
public async Task<CommandResultWithCorrelation<TResult>> HandleAsync(TCommand command, CancellationToken cancellationToken = default)
{
// Create event context for collecting events
var eventContext = new EventContext<TEvents>(_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<TResult>
{
Result = result,
CorrelationId = correlationId
};
}
}