127 lines
4.1 KiB
C#
127 lines
4.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Svrnty.CQRS.Events.Abstractions.Sagas;
|
|
|
|
/// <summary>
|
|
/// Registry for saga definitions.
|
|
/// </summary>
|
|
public interface ISagaRegistry
|
|
{
|
|
/// <summary>
|
|
/// Register a saga definition.
|
|
/// </summary>
|
|
/// <typeparam name="TSaga">The saga type.</typeparam>
|
|
/// <param name="definition">The saga definition.</param>
|
|
void Register<TSaga>(SagaDefinition definition) where TSaga : ISaga;
|
|
|
|
/// <summary>
|
|
/// Get saga definition by type.
|
|
/// </summary>
|
|
/// <typeparam name="TSaga">The saga type.</typeparam>
|
|
/// <returns>The saga definition, or null if not found.</returns>
|
|
SagaDefinition? GetDefinition<TSaga>() where TSaga : ISaga;
|
|
|
|
/// <summary>
|
|
/// Get saga definition by name.
|
|
/// </summary>
|
|
/// <param name="sagaName">The saga name.</param>
|
|
/// <returns>The saga definition, or null if not found.</returns>
|
|
SagaDefinition? GetDefinitionByName(string sagaName);
|
|
|
|
/// <summary>
|
|
/// Get saga type by name.
|
|
/// </summary>
|
|
/// <param name="sagaName">The saga name.</param>
|
|
/// <returns>The saga type, or null if not found.</returns>
|
|
Type? GetSagaType(string sagaName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saga definition with steps.
|
|
/// </summary>
|
|
public sealed class SagaDefinition
|
|
{
|
|
private readonly List<ISagaStep> _steps = new();
|
|
|
|
public SagaDefinition(string sagaName)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(sagaName))
|
|
throw new ArgumentException("Saga name cannot be null or empty", nameof(sagaName));
|
|
|
|
SagaName = sagaName;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saga name.
|
|
/// </summary>
|
|
public string SagaName { get; }
|
|
|
|
/// <summary>
|
|
/// Saga steps in execution order.
|
|
/// </summary>
|
|
public IReadOnlyList<ISagaStep> Steps => _steps.AsReadOnly();
|
|
|
|
/// <summary>
|
|
/// Add a step to the saga.
|
|
/// </summary>
|
|
public SagaDefinition AddStep(ISagaStep step)
|
|
{
|
|
if (step == null)
|
|
throw new ArgumentNullException(nameof(step));
|
|
|
|
_steps.Add(step);
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a step using lambdas.
|
|
/// </summary>
|
|
public SagaDefinition AddStep(
|
|
string stepName,
|
|
Func<ISagaContext, System.Threading.CancellationToken, System.Threading.Tasks.Task> execute,
|
|
Func<ISagaContext, System.Threading.CancellationToken, System.Threading.Tasks.Task> compensate)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(stepName))
|
|
throw new ArgumentException("Step name cannot be null or empty", nameof(stepName));
|
|
if (execute == null)
|
|
throw new ArgumentNullException(nameof(execute));
|
|
if (compensate == null)
|
|
throw new ArgumentNullException(nameof(compensate));
|
|
|
|
_steps.Add(new LambdaSagaStep(stepName, execute, compensate));
|
|
return this;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saga step implemented with lambda functions.
|
|
/// </summary>
|
|
internal sealed class LambdaSagaStep : ISagaStep
|
|
{
|
|
private readonly Func<ISagaContext, System.Threading.CancellationToken, System.Threading.Tasks.Task> _execute;
|
|
private readonly Func<ISagaContext, System.Threading.CancellationToken, System.Threading.Tasks.Task> _compensate;
|
|
|
|
public LambdaSagaStep(
|
|
string stepName,
|
|
Func<ISagaContext, System.Threading.CancellationToken, System.Threading.Tasks.Task> execute,
|
|
Func<ISagaContext, System.Threading.CancellationToken, System.Threading.Tasks.Task> compensate)
|
|
{
|
|
StepName = stepName ?? throw new ArgumentNullException(nameof(stepName));
|
|
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
|
|
_compensate = compensate ?? throw new ArgumentNullException(nameof(compensate));
|
|
}
|
|
|
|
public string StepName { get; }
|
|
|
|
public System.Threading.Tasks.Task ExecuteAsync(ISagaContext context, System.Threading.CancellationToken cancellationToken = default)
|
|
{
|
|
return _execute(context, cancellationToken);
|
|
}
|
|
|
|
public System.Threading.Tasks.Task CompensateAsync(ISagaContext context, System.Threading.CancellationToken cancellationToken = default)
|
|
{
|
|
return _compensate(context, cancellationToken);
|
|
}
|
|
}
|