-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Non generic and generic policies
TL;DR Policy<TResult>
policies generic-typed to TResult
allow compile-time type-binding and intellisense when:
- configuring policies to
.Handle<TResult>()
return values; - using
Fallback<TResult>
; - using
PolicyWrap
to combine policies already strongly-typed to executions returningTResult
.
Polly offers non-generic policies: RetryPolicy
, CircuitBreakerPolicy
(etc), each extending the base non-generic type Policy
.
These offer void
-returning .Execute()
, and generic method overloads .Execute<TResult>(...)
:
public abstract class Policy // (much elided!)
{
void Execute(Action action); // (and many similar overloads)
TResult Execute<TResult>(Func<TResult> func); // generic _method_ overload (and many similar)
Task ExecuteAsync(Func<Task> action); // (and many similar overloads)
Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> func); // generic _method_ overload (and many similar)
}
This offers maximum flexibility of what can be executed through the policy, for more straightforward use cases.
For fault-handling policies such as retry or circuit-breaker, these non-generic policies can be used flexibly across return types, provided you are only handling exceptions with the policy, not results.
Non-reactive policies such as Bulkhead
and Timeout
also configure to this form by default.
The generic method overloads on non-generic Policy
offer flexibility, but can't offer compile-time type-binding to anything beyond that .Execute<TResult>()
method. This is limiting if we want to do anything more with TResult
.
Once features are used where compile-time binding makes sense, Polly's configuration syntax instead returns you generic policies RetryPolicy<TResult>
, CircuitBreakerPolicy<TResult>
(etc), each extending the base Policy<TResult>
.
These execute Func
s strongly-typed to return TResult
:
public abstract class Policy<TResult> // (much elided!)
{
TResult Execute(Func<TResult> func); // (and many similar overloads)
Task<TResult> ExecuteAsync(Func<Task<TResult>> func); // (and many similar overloads)
}
Features that drive the transition to Policy<TResult>
are:
When a .HandleResult<TResult>(...)
clause is used, the generic Policy<TResult>
enforces compile-time type binding between the .HandleResult<TResult>(...)
clause and .Execute<TResult>(...)
calls made on the policy.
RetryPolicy<HttpResponseMessage> httpRetryPolicy = Policy
.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
.Or<HttpResponseException>()
.WaitAndRetryAsync(new[] {
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(4)
});
HttpResponseMesage response =
httpRetryPolicy.ExecuteAsync(ct => httpClient.GetAsync(url, ct), cancellationToken); // executions compile-time type-bound
If this strong-typing were not used, it would be possible to write (and compile) non-sensical code such as:
Policy
.HandleResult<foo>(Func<foo, bool>)
.Retry(2)
.Execute<bar>(Func<bar>);
This was deemed unacceptable. If executing a Func<bar>
on a foo
-handling Policy was permitted, what to do?
- If the
foo/bar
mismatch were to throw an exception, then why not enforce the type matching at compile time instead of leave it to a (harder to discover/debug) run-time failure? - If the
foo/bar
mismatch were to not throw an exception, it would have to be silently ignored. But this would carry the grave risk of leading users into a pit of failure. Unwittingly mismatching the.HandleResult<T>()
type and the.Execute<T>()
type would lead to silent loss of operation of the Policy. This could be particularly pernicious when refactoring - a slight wrong change and your Polly protection is (silently) gone.
Typed policies Policy<TResult>
allow type-binding for TResult
-values passed to delegate hooks such as onRetry
, onBreak
, onFallback
etc. Without strongly-typed Policy<TResult>
, these would have to be passed as object
and endure ugly casting back to TResult
within the delegate hooks.
HttpStatusCode[] httpStatusCodesWorthRetrying = {
HttpStatusCode.RequestTimeout, // 408
HttpStatusCode.InternalServerError, // 500
HttpStatusCode.BadGateway, // 502
HttpStatusCode.ServiceUnavailable, // 503
HttpStatusCode.GatewayTimeout // 504
};
Retry<HttpResponseMessage> httpRetryPolicy = Policy
.HandleResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
.WaitAndRetryAsync(retryDelays,
onRetryAsync: async (outcome, duration) => {
await logger.LogError($"HttpCall failed with status code {outcome.Result.StatusCode}"); // outcome.Result is passed as HttpResponseMessage
});
Generic policies Policy<TResult>
also allow compile-time type-binding between different Policy<TResult>
instances combined into a PolicyWrap<TResult>
.
FallbackPolicy<HttpResponseMessage> fallback = // ...
RetryPolicy<HttpResponseMessage> retry = // ...
CircuitBreakerPolicy<HttpResponseMessage> breaker = // ...
PolicyWrap<HttpResponseMessage> combinedResilience = Policy.WrapAsync(fallback, retry, breaker); // compile-time type-bound
The generic policies give you the compile-time intellisense to only combine these correctly, just as when coding other generic functional monads such as Linq or Rx expressions.
The alternative - permitting policy1<Foo>.Wrap(policy2<Bar>)
- would imply the same issues around swapping compile-time failure for runtime-failure, or silently dropping type mismatches, as discussed above.
Policy<TResult>
does not (and could not sensibly) extend non-generic Policy
.
Instead, Polly's interfaces group what is common to non-generic and generic policies of the same policy type. For example:
-
ICircuitBreakerPolicy
groups what is common to non-generic and generic circuit breakers. -
ICircuitBreakerPolicy<TResult>
adds what is specific to the genericTResult
form -
ICircuitBreakerPolicy<TResult> : ICircuitBreakerPolicy
.
At the base class level, IsPolicy
is a marker interface, signifying 'this is some kind of policy'.
It contains what is common to the base classes Policy
and Policy<TResult>
.
You can combine non-generic Policy
instances with generic Policy<TResult>
into a resulting PolicyWrap<TResult>
.
Take the preceding PolicyWrap<HttpResponseMessage>
example. You could combine in a TimeoutPolicy
(non-generic by default as it doesn't handle results).
TimeoutPolicy timeout = ... // (non-generic)
RetryPolicy<HttpResponseMessage> retry = ... // (generic)
CircuitBreakerPolicy<HttpResponseMessage> breaker = ... // (generic)
FallbackPolicy<HttpResponseMessage> fallback = ... // (generic)
// Polly permits this, mixing non-generic and generic policies into a generic PolicyWrap
PolicyWrap<HttpResponseMessage> combinedResilience =
fallback
.WrapAsync(breaker)
.WrapAsync(retry)
.WrapAsync(timeout);
For further information on combining policies, see the PolicyWrap wiki.
- Home
- Polly RoadMap
- Contributing
- Transient fault handling and proactive resilience engineering
- Supported targets
- Retry
- Circuit Breaker
- Advanced Circuit Breaker
- Timeout
- Bulkhead
- Cache
- Rate-Limit
- Fallback
- PolicyWrap
- NoOp
- PolicyRegistry
- Polly and HttpClientFactory
- Asynchronous action execution
- Handling InnerExceptions and AggregateExceptions
- Statefulness of policies
- Keys and Context Data
- Non generic and generic policies
- Polly and interfaces
- Some policy patterns
- Debugging with Polly in Visual Studio
- Unit-testing with Polly
- Polly concept and architecture
- Polly v6 breaking changes
- Polly v7 breaking changes
- DISCUSSION PROPOSAL- Polly eventing and metrics architecture