Skip to content

Non generic and generic policies

reisenberger edited this page Oct 24, 2017 · 25 revisions

Why does Polly offer both 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 returning TResult.

Non-generic policies, Policy

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.

Generic policies, Policy<TResult>

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 Funcs 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>

Features that drive the transition to Policy<TResult> are:

(1) Binding .HandleResult<TResult>(...) and .Execute<TResult>(...)

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.

Why?

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.

(2) TResult execution results passed to policy hooks

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.

(3) Binding multiple Policy<TResult> instances into a PolicyWrap<TResult>

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 particular HttpResponseMessage.StatusCode values
  • a CircuitBreakerPolicy<HttpResponseMessage> handling particular HttpResponseMessage.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 extend Policy

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:

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>.

Mixing non-generic and generic in a PolicyWrap<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.

Clone this wiki locally