saritasa-nest / saritasa-dotnet-tools

Development Tools For Company'S .NET Projects.
BSD 2-Clause "Simplified" License
27 stars 13 forks source link

Is it possible to create transient error handling middleware? #48

Closed dermeister0 closed 6 years ago

dermeister0 commented 6 years ago

For example:

Task Handle()
{
   return FlowUtils.RetryAsync(executeCommandAction, retryStrategy, SomeException, AnotherException);
}
krasninja commented 6 years ago

It is possible, I have prepared example. Will show soon.

krasninja commented 6 years ago

There is new version: https://www.nuget.org/packages/Saritasa.Tools.Messages/0.5.3 . Here is a sample middleware:

/// <summary>
/// Command handler executor with transient errors handling.
/// </summary>
public class TransientErrorHandlingCommandHandlerExecutorMiddleware :
    Saritasa.Tools.Messages.Commands.PipelineMiddlewares.CommandHandlerExecutorMiddleware
{
    private static readonly Type[] transientExceptions = new Type[] { typeof(Exception) };

    private static readonly Saritasa.Tools.Common.Utils.FlowUtils.RetryStrategy retryStrategy =
        Saritasa.Tools.Common.Utils.FlowUtils.CreateIncrementDelayRetryStrategy(
            delay: TimeSpan.FromSeconds(3),
            increment: TimeSpan.FromSeconds(3)
        );

    /// <inheritdoc />
    public override void Handle(IMessageContext messageContext)
    {
        Saritasa.Tools.Common.Utils.FlowUtils.Retry(
            () =>
            {
                base.Handle(messageContext);
                PostHandle(messageContext);
            },
            retryStrategy,
            transientExceptions);
    }

    /// <inheritdoc />
    public override Task HandleAsync(IMessageContext messageContext, CancellationToken cancellationToken)
    {
        return Saritasa.Tools.Common.Utils.FlowUtils.RetryAsync(
            async () =>
            {
                await base.HandleAsync(messageContext, cancellationToken);
                PostHandle(messageContext);
            },
            retryStrategy,
            cancellationToken,
            transientExceptions);
    }
}

Initialization:

var pipelineContainer = new Messages.Common.DefaultMessagePipelineContainer();
var commandPipeline = pipelineContainer
    .AddCommandPipeline()
    .AddStandardMiddlewares(options =>
    {
        options.SetAssemblies(System.Reflection.Assembly.GetAssembly(typeof(Program)));
    })
    .Get();
commandPipeline.ReplaceMiddlewareById("CommandHandlerExecutorMiddleware", new Internal.TransientErrorHandlingCommandHandlerExecutorMiddleware()
{
    CaptureExceptionDispatchInfo = true
});

var pipelineService = new Messages.Common.DefaultMessagePipelineService(null, pipelineContainer);

Usage:

public class CommandWithException
{
    public int CallCount { get; set; }

    public void Handle(CommandWithException command)
    {
        CallCount++;
        if (CallCount < 2)
        {
            throw new InvalidOperationException();
        }
    }
}
var obj = new Internal.CommandWithException();
try
{
    pipelineService.HandleCommand(obj);
}
catch (Exception ex)
{

}
Console.WriteLine(obj.CallCount); // 3