Last active
August 7, 2023 19:04
-
-
Save henkmollema/ba21bb90c35580a7189e77624d9ed8d1 to your computer and use it in GitHub Desktop.
Transient failure handling for MediatR using Polly
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Reflection; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using MediatR; | |
using Microsoft.Extensions.Logging; | |
using Polly; | |
namespace Foo.Bar | |
{ | |
/// <summary> | |
/// Applies a retry policy on the MediatR request. | |
/// Apply this attribute to the MediatR <see cref="IRequest"/> class (not on the handler). | |
/// </summary> | |
[AttributeUsage(AttributeTargets.Class)] | |
public class RetryPolicyAttribute : Attribute | |
{ | |
private int _retryCount = 3; | |
private int _sleepDuration = 200; | |
/// <summary> | |
/// Gets or sets the amount of times to retry the execution. | |
/// Each retry the sleep duration is incremented by <see cref="SleepDuration"/>. | |
/// Defaults to 3 times. | |
/// </summary> | |
public int RetryCount | |
{ | |
get => _retryCount; | |
set | |
{ | |
if (value < 1) | |
{ | |
throw new ArgumentException("Retry count must be higher than 1.", nameof(value)); | |
} | |
_retryCount = value; | |
} | |
} | |
/// <summary> | |
/// Gets or sets the sleep duration in milliseconds. | |
/// Each retry the sleep duration gets incremented by this value. | |
/// Defaults to 200 milliseconds. | |
/// </summary> | |
public int SleepDuration | |
{ | |
get => _sleepDuration; | |
set | |
{ | |
if (value < 1) | |
{ | |
throw new ArgumentException("Sleep duration must be higher than 1ms.", nameof(value)); | |
} | |
_sleepDuration = value; | |
} | |
} | |
} | |
/// <summary> | |
/// Wraps request handler execution of requests decorated with the <see cref="RetryPolicyAttribute"/> | |
/// inside a policy to handle transient failures and retry the execution. | |
/// </summary> | |
public class RetryPolicyBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> | |
{ | |
private readonly ILogger<RetryPolicyBehavior<TRequest, TResponse>> _logger; | |
public RetryPolicyBehavior(ILogger<RetryPolicyBehavior<TRequest, TResponse>> logger) | |
{ | |
_logger = logger; | |
} | |
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) | |
{ | |
var retryAttr = typeof(TRequest).GetCustomAttribute<RetryPolicyAttribute>(); | |
if (retryAttr == null) | |
{ | |
return await next(); | |
} | |
return await Policy.Handle<Exception>() | |
.WaitAndRetryAsync( | |
retryAttr.RetryCount, | |
i => TimeSpan.FromMilliseconds(i * retryAttr.SleepDuration), | |
(ex, ts, _) => _logger.LogWarning(ex, "Failed to execute handler for request {Request}, retrying after {RetryTimeSpan}s: {ExceptionMessage}", typeof(TRequest).Name, ts.TotalSeconds, ex.Message)) | |
.ExecuteAsync(async () => await next()); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment