-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
In Polly V8, resilience strategies are configured using options and delegates.
Here are some patterns you can use to accomplish this:
// Switch-based pattern (recommended)
builder.AddRetry(new RetryStrategyOptions
{
ShouldHandle = args => args.Exception switch
{
InvalidOperationException => PredicateResult.True,
_ => PredicateResult.False
}
});
// Simplified pattern
builder.AddRetry(new RetryStrategyOptions
{
ShouldHandle = args =>
{
if (args.Exception is InvalidOperationException)
{
return PredicateResult.True;
}
return PredicateResult.False;
}
});
// Inline pattern
builder.AddRetry(new RetryStrategyOptions
{
ShouldHandle = args => new ValueTask<bool>(args.Exception is InvalidOperationException)
});For handling results, the pattern is slightly more complex:
builder.AddRetry(new RetryStrategyOptions<HttpResponseMessage>
{
ShouldHandle = args => args switch
{
{ Exception: InvalidOperationException } => PredicateResult.True,
{ Result: HttpResponseMessage response } when !response.IsSuccessStatusCode => PredicateResult.True,
_ => PredicateResult.False
}
});I'm currently exploring ways to streamline the configuration of predicates. Here are some options I've considered:
Option 1: Introduction of new Constructors
This would involve adding new sets of constructors for every generic option:
public class RetryStrategyOptions<TResult> : ResilienceStrategyOptions
{
public RetryStrategyOptions()
{
// ...
}
public RetryStrategyOptions(PredicateBuilder<TResult> shouldHandle)
{
// ...
}
public RetryStrategyOptions(Func<OutcomeArguments<TResult, RetryPredicateArguments>> shouldHandle)
{
// ...
}With these, you can now configure strategies like so:
// Simple
builder.AddRetry(new RetryStrategyOptions<HttpResponseMessage>(args => args.Exception is InvalidOperationException));
// With results
builder.AddRetry(new RetryStrategyOptions<HttpResponseMessage>(args =>
{
if (args.Exception is InvalidOperationException)
{
return true;
}
if (args.Result != null && !args.Result.IsSuccessStatusCode)
{
return false;
}
return false;
}));
// Using predicate builder
var predicate = new PredicateBuilder<HttpResponseMessage>()
.Handle<InvalidOperationException>()
.HandleResult(r => !r.IsSuccessStatusCode);
builder.AddRetry(new RetryStrategyOptions<HttpResponseMessage>(predicate));This option complements the pattern well but limits extensibility to constructors only.
Option 2: Use extension methods for options
In this scenario, the previous example would look like this:
// Simple
builder.AddRetry(new RetryStrategyOptions<HttpResponseMessage>().WithShouldHandle(args => args.Exception is InvalidOperationException));
// With results
builder.AddRetry(new RetryStrategyOptions<HttpResponseMessage>().WithShouldHandle(args =>
{
if (args.Exception is InvalidOperationException)
{
return true;
}
if (args.Result != null && !args.Result.IsSuccessStatusCode)
{
return false;
}
return false;
}));
// Using predicate builder
var predicate = new PredicateBuilder<HttpResponseMessage>()
.Handle<InvalidOperationException>()
.HandleResult(r => !r.IsSuccessStatusCode);
builder.AddRetry(new RetryStrategyOptions<HttpResponseMessage>(predicate).WithShouldHandle(builder));While this method allows for greater future extensibility, the syntax is a bit more complex.
Option 3: Extensions for builders
Here, we'd offer extensions for the ResilienceStrategyBuilder to facilitate easier predicate configuration. For instance, these convenience extensions could take the handling configuration as the first parameter, followed by the options.
// Simple
builder.AddRetry(args => args.Exception is InvalidOperationException, new RetryStrategyOptions<HttpResponseMessage>());
// With results
builder.AddRetry(
args =>
{
if (args.Exception is InvalidOperationException)
{
return true;
}
if (args.Result != null && !args.Result.IsSuccessStatusCode)
{
return false;
}
return false;
},
new RetryStrategyOptions<HttpResponseMessage>());
// Using predicate builder
var predicate = new PredicateBuilder<HttpResponseMessage>()
.Handle<InvalidOperationException>()
.HandleResult(r => !r.IsSuccessStatusCode);
builder.AddRetry(predicate , new RetryStrategyOptions<HttpResponseMessage>(predicate));This approach could lead to the development of more extensions and even the complete omission of options. However, the number of extension types could potentially increase exponentially.
Option 4: Implicit conversion from PredicateBuilder to delegate
This allows the following usage:
new ResilienceStrategyBuilder<HttpResponseMessage>()
.AddRetry(new()
{
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.Handle<HttpRequestException>()
.HandleResult(result => result.IsSuccessStatusCode)
});Or for non-generic options:
new ResilienceStrategyBuilder()
.AddRetry(new()
{
ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>()
});The idea si to define built-in implicit conversion between PredicateBuilder and the delegate:
public static implicit operator Func<OutcomeArguments<TResult, RetryPredicateArguments>, ValueTask<bool>>(PredicateBuilder<TResult> builder)
{
return builder.CreatePredicate<RetryPredicateArguments>();
}We wouldn't expose any additional extensions for individual strategies. Simple use-cases can be done with PredicateBuilder while the more complex (args access, async predicates) can be done by defining the full delegate.
Option 5: Embracing switch Expressions, no new API
This option involves accepting switch expressions for all predicate configurations. It keeps the API minimal, but the configuration could seem foreign to those unfamiliar with the latest C# features.
I welcome your thoughts on these options. @martincostello, @joelhulen, @geeknoid, what are your preferences? Are there any other alternatives you'd suggest?