Skip to content

Non generic and generic policies

reisenberger edited this page Sep 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 using certain features: when configuring policies to handle TResult types; when using Fallback<TResult>; and 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. They can be used:

  • with .Execute(Action), to execute delegates returning void
  • with a generic method overload .Execute<TResult>(Func<..., TResult>), to execute delegates returning any TResult.

They offer 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, non-generic policies can be used flexibly across return types, if you are only handling exceptions with the policy, not results.

Other policy types such as TimeoutPolicy or BulkheadPolicy are also usually found in the non-generic form, as they do not interpret the return values of executions in any way.

Generic policies, Policy<TResult>

While the above generic method overloads on non-generic Policy offer flexibility, they can't offer compile-time type-binding to anything outside that execute method.

Once features are used where compile-time type-binding makes sense, Polly's configuration syntax instead returns you generic policies of the form RetryPolicy<TResult>, CircuitBreakerPolicy<TResult> (etc), each extending the base type Policy<TResult>.

The features falling into this category 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 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, poof, 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 generic-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

The generic type Policy<TResult> does not (and could not sensibly) extend the non-generic type Policy. Polly's interfaces instead group what is common to non-generic and generic policies of the same policy type:

Mixing non-generic and generic polices into a PolicyWrap<TResult>

You can combine non-generic policies in with generic-TResult policies in a PolicyWrap<TResult>.

Returning to the preceding PolicyWrap<HttpResponseMessage> example, you could combine in a TimeoutPolicy (always non-generic as it doesn't handle results). For example:

TimeoutPolicy timeout = // ... 
RetryPolicy<HttpResponseMessage> retry = // ...
CircuitBreakerPolicy<HttpResponseMessage> breaker = // ... 
FallbackPolicy<HttpResponseMessage> fallback = // ...
  
// Polly permits 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