-
-
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; - 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 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 above 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 as soon as 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.
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>
also 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.
Generic policies Policy<TResult>
also allow compile-time type-binding between different Policy<TResult>
instances combined into a PolicyWrap<TResult>
.
In a policy protecting an HttpClient
call, for example, you might create a PolicyWrap<HttpResponseMessage>
combining:
- a
RetryPolicy<HttpResponseMessage>
handling particularHttpResponseMessage.StatusCode
values - a
CircuitBreakerPolicy<HttpResponseMessage>
handling particularHttpResponseMessage.StatusCode
values - a
FallbackPolicy<HttpResponseMessage>
.
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
.Wrap(breaker)
.Wrap(retry)
.Wrap(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