Skip to content

Non generic and generic policies

reisenberger edited this page Jun 7, 2017 · 25 revisions

Why does Polly offer both non-generic and generic policies?

TL;DR Offering Policy<TResult> policies generic-typed to TResult allows compile-time type-binding and intellisense when configuring policies to handle TResult types, and when using PolicyWrap to combine policies for executions returning TResult.

Non-generic policies, Policy

Polly offers non-generic policies, of type Policy. These can be used:

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

Generic policies, Policy<TResult>

Given the non-generic policies offer a generic method overload, why also generic policies Policy<TResult>?

While the generic method overloads on non-generic Policy offer flexibility, they can't offer compile-time type-binding to anything outside that execute method - to any other aspect of Policy or PolicyWrap configuration.

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

Generic policies Policy<TResult> on the other hand allow compile-time type binding between .HandleResult<TResult>(...) and the .Execute<TResult>(...) calls made on the policy.

Without this, it would be possible to write (and compile) non-sensical code such as:

Policy
    .Handle<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, rather than 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. (There's no other meaningful option.) But this carries the grave risk of leading users into a pit of failure. Unwittingly mismatching the .Handle<>() type and the .Execute<>() type would lead to silent failure of the Policy. This could be particularly pernicious when refactoring - a slight wrong change and, poof, your Polly protection is (silently) gone.

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 Http 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>) - implies the same issues around silently dropping type mismatches as above.

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

You can however combine non-generic and generic policies in a PolicyWrap. Returning to the preceding PolicyWrap<HttpResponseMessage>, you could additionally combine in a TimeoutPolicy. TimeoutPolicy is always non-generic (it doesn't handle results), and Polly permits the following:

TimeoutPolicy timeout = // ... 
RetryPolicy<HttpResponseMessage> retry = // ...
CircuitBreakerPolicy<HttpResponseMessage> breaker = // ... 
FallbackPolicy<HttpResponseMessage> fallback = // ...
  
// Polly permits this, mixing non-generic and generic policies.
PolicyWrap<HttpResponseMessage> combinedResilience = 
    fallback
    .Wrap(breaker) 
    .Wrap(retry)
    .Wrap(timeout); 
    

For further information on combining policies, see the PolicyWrap wiki.

Clone this wiki locally