254 lines
9.7 KiB
C#
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
|
|
};
|
|
}
|
|
}
|