Non generic and generic policies - App-vNext/Polly GitHub Wiki
âšī¸ This documentation describes the previous Polly v7 API. If you are using the new v8 API, please refer to pollydocs.org.
TL;DR Policy<TResult>
policies generic-typed to TResult
enable compile-time type-binding and intellisense:
- when configuring policies to
.Handle<TResult>()
return values; - when passing results to delegate hooks;
- when using
Fallback<TResult>
; - when 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 simpler 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
(above) offer flexibility, but can't offer compile-time type-binding to anything beyond that .Execute<TResult>()
generic method. This is limiting if we want to do anything more with TResult
.
Once multiple features are being used referencing TResult
, 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> // executions will now be bound to HttpResponseMessage
(r => r.StatusCode == HttpStatusCode.InternalServerError) // and you can handle return results directly with the policy
.Or<HttpRequestException>() // as well as exceptions
.WaitAndRetryAsync(new[] {
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(4)
});
HttpResponseMesage response = await httpRetryPolicy.ExecuteAsync(
ct => httpClient.GetAsync(url, ct), // compile-time type-bound to HttpResponseMessage
cancellationToken);
If 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 would be (silently) gone.
Typed policies Policy<TResult>
permit type-binding of TResult
-values passed to delegate hooks such as onRetry
, onBreak
, onFallback
etc.
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,
async (outcome, duration) => {
await logger.LogError($"HttpCall failed with status code {outcome.Result.StatusCode}"); // outcome.Result is passed as HttpResponseMessage
});
Without strongly-typed Policy<TResult>
, outcomes would have to be passed as object
and endure ugly casting back to TResult
within the delegate hooks.
Generic policies Policy<TResult>
also enable 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 in Linq or Rx. This is just as Linq does not allow you to write .Select<int>().Where<foo>().OrderBy<bar>()
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).
FallbackPolicy<HttpResponseMessage> fallback = ... // (generic)
CircuitBreakerPolicy<HttpResponseMessage> breaker = ... // (generic)
RetryPolicy<HttpResponseMessage> retry = ... // (generic)
TimeoutPolicy timeout = ... // (non-generic)
// Polly permits this, mixing non-generic and generic policies into a generic PolicyWrap
// provided the instance syntax (as below) is used to create the PolicyWrap.
PolicyWrap<HttpResponseMessage> combinedResilience =
fallback
.WrapAsync(breaker)
.WrapAsync(retry)
.WrapAsync(timeout);
For further information on combining policies, see the PolicyWrap wiki.
Policy<TResult>
necessarily does not extend non-generic Policy
. The whole raison-d'etre of Policy<TResult>
is to restrict executions to delegates returning a single TResult
type. Policy
allows executions returning void
, and multiple TResult
types for a simpler feature set.
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'. IsPolicy
contains what is common to the base classes Policy
and Policy<TResult>
.
Some policies naturally define in the non-generic form, as they are not reactive to results:
IAsyncPolicy timeout = Policy.TimeoutAsync(10);
Policy consumers may however sometimes require a generic policy. HttpClientFactory
in ASP.NET Core 2.1, for example, configures policies for use with HttpClient
calls. All these policies govern executions returning HttpResponseMessage
, so the configuration overloads only accept generic policies IAsyncPolicy<HttpResponseMessage>
.
From Polly v5.9.0, Polly offers helper extension methods to facilitate the conversion:
// non-generic ISyncPolicy -> ISyncPolicy<TResult>
ISyncPolicy timeout = Policy.Timeout(10);
ISyncPolicy<TResult> genericVersion = timeout.AsPolicy<TResult>();
// non-generic IAsyncPolicy -> IAsyncPolicy<TResult>
IAsyncPolicy timeout = Policy.TimeoutAsync(10);
IAsyncPolicy<HttpResponseMessage> httpTimeout = timeout.AsAsyncPolicy<HttpResponseMessage>();
Note that all policies can be defined natively in the generic form. For instance, the TimeoutPolicy
above can be defined:
IAsyncPolicy<HttpResponseMessage> httpTimeout = Policy.TimeoutAsync<HttpResponseMessage>(10);
The helper method .AsAsyncPolicy<HttpResponseMessage>()
exists simply for those times when you already have an IAsyncPolicy
in hand.