PolicyWrap - 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.
To provide a simple way to combine resilience strategies.
PolicyWrap
provides a flexible way to encapsulate applying multiple resilience policies to delegates in a nested fashion (sometimes known as the 'Russian-Doll' or 'onion-skin-layers' model).
Rather than the longhand:
fallback.Execute(() => waitAndRetry.Execute(() => breaker.Execute(action)));
A PolicyWrap
can express this:
fallback.Wrap(waitAndRetry).Wrap(breaker).Execute(action);
or equivalently:
fallback.Wrap(waitAndRetry.Wrap(breaker)).Execute(action);
or equivalently:
Policy.Wrap(fallback, waitAndRetry, breaker).Execute(action);
All PolicyWrap
instances must contain two or more policies.
PolicyWrap
executes the supplied delegate through the layers or wrap:
- the outermost (leftmost in reading order) policy executes the next inner, which executes the next inner, etc; until the innermost policy executes the user delegate;
- exceptions bubble back outwards (until handled) through the layers.
For example, for Policy.Wrap(fallback, waitAndRetry, breaker)
:
-
fallback
is the outermost policy; it places its call throughwaitAndRetry
;-
waitAndRetry
places its call throughbreaker
;-
breaker
is the innermost policy, and (unless the circuit is open) executes the passed delegate.- the passed delegate succeeds; or returns a handled result; or throws.
-
breaker
will update circuit stats for that outcome, then pass it back to ...
-
-
waitAndRetry
, which will (for a fault) wait-and-retry, or fail when retries are exhausted; (for a success) return it; and pass back to ...
-
-
fallback
, which will (for a fault) substitute the fallback result; (for a success) return it.
Syntax examples given are sync; comparable async overloads exist for asynchronous operation. See readme and wiki for more details.
var policyWrap = fallback.Wrap(cache).Wrap(retry).Wrap(breaker).Wrap(bulkhead).Wrap(timeout);
// or (functionally equivalent)
var policyWrap = fallback.Wrap(cache.Wrap(retry.Wrap(breaker.Wrap(bulkhead.Wrap(timeout)))));
The .Wrap()
instance-method syntax is very flexible: the syntax can mix generic and non-generic policies. The compiler can infer generic type parameters for a PolicyWrap<TResult>
where necessary.
PolicyWrap policyWrap = Policy.Wrap(fallback, cache, retry, breaker, bulkhead, timeout);
This syntax only wraps non-generic policies together.
PolicyWrap<TResult> genericPolicyWrap = Policy.Wrap<TResult>(fallback, cache, retry, breaker, bulkhead, timeout);
This syntax only wraps generic policies together.
A PolicyWrap
is just another Policy
, and has the same qualities:
- it is thread-safe
- it can be reused across multiple call sites
- a non-generic
PolicyWrap
can be used with the generic.Execute/Async<TResult>(...)
methods, across multipleTResult
types.
From a functional-programming or mathematical perspective, a PolicyWrap
is functional composition of a higher-order function, as in Linq or Rx.
If applying a policy to a delegate is f(x)
(where f
is the Polly policy and x
the delegate), a PolicyWrap
allows you to express a(b(c(d(e(f(x))))))
(or: a(b(c(d(e(f)))))(x)
), (where a
to f
are policies, and x
the delegate).
Some interesting characteristics flow from the functional-composition and thread-safe aspects of PolicyWrap
.
- The same
Policy
instance can safely be used in more than onePolicyWrap
in different ways. (EachPolicyWrap
can weave a different path through the policy instances configured in your app. Policy instances need not have only one possible antecedent or subsequent, in the wraps in your app.) - A
PolicyWrap
can be onward-wrapped into further wraps, to build more powerful combinations.
The instance syntax allows you to build variations on a theme, mixing common and site-specific resilience needs:
PolicyWrap commonResilience = Policy.Wrap(retry, breaker, timeout);
// ... then wrap in extra policies specific to a call site:
Avatar avatar = Policy
.Handle<Whatever>()
.Fallback<Avatar>(Avatar.Blank)
.Wrap(commonResilience)
.Execute(() => { /* get avatar */ });
// Share the same commonResilience, but wrap with a different fallback at another call site:
Reputation reps = Policy
.Handle<Whatever>()
.Fallback<Reputation>(Reputation.NotAvailable)
.Wrap(commonResilience)
.Execute(() => { /* get reputation */ });
When you wrap non-generic Policy
s together, the PolicyWrap
remains non-generic, no matter how many policies in the wrap. Any .Execute<TResult>
can be executed through the non-generic wrap using the .Execute<TResult>
generic method.
When you include a generic-typed SomePolicy<TResult>
in a wrap, the PolicyWrap
as a whole becomes generic-typed PolicyWrap<TResult>
. This provides type-safety. It would be non-sensical to have (paraphrasing syntax)
fallback<int>(...).Execute(() => breaker<string>(...).Execute(() => retry<Foo>(...).Execute<Bar>(func)));
just as LINQ and Rx do not let you do this.
You can however mix non-generic and generic policies in the same PolicyWrap<TResult>
, provided all the generic policies are for the same TResult
type and provided the instance configuration syntax is used.
Policies can be combined flexibly in any order. It is worth however considering the following points:
Policy type | Common positions in a PolicyWrap
|
Explanation |
---|---|---|
FallbackPolicy |
Usually outermost | Provides a substitute value after all other resilience strategies have failed. |
FallbackPolicy |
Can also be used mid-wrap ... | ... eg as a failover strategy calling multiple possible endpoints (try first; if not, try next). |
CachePolicy |
As outer as possible but not outside stub fallbacks | As outer as possible: if you hold a cached value, you don't want to bother trying the bulkhead or circuit-breaker etc. But cache should not wrap any FallbackPolicy providing a placeholder-on-failure (you likely don't want to cache and serve the placeholder to all subsequent callers) |
TimeoutPolicy |
Outside any RetryPolicy , CircuitBreaker or BulkheadPolicy
|
... to apply an overall timeout to executions, including any delays-between-tries or waits-for-bulkhead-slot |
RetryPolicy and CircuitBreaker
|
Either retry wraps breaker, or vice versa. | Judgment call. With longer delays between retries, we suggest WaitAndRetry wraps CircuitBreaker (the circuit-state might reasonably change in the delay between tries). With no/short delays between retries, we suggest CircuitBreaker wraps Retry (don't take hammering the underlying system with three closely-spaced tries as cause to break the circuit). |
BulkheadPolicy |
Usually innermost unless wraps a final TimeoutPolicy ; certainly inside any WaitAndRetry
|
Bulkhead intentionally limits parallelization. You want that parallelization devoted to running the delegate, not eg occupied by waits for a retry. |
TimeoutPolicy |
Inside any RetryPolicy , CircuitBreaker or BulkheadPolicy , closest to the delegate. |
... to apply a timeout to an individual try. You might alternatively put a TimeoutPolicy outside the bulkhead, if you are allowing calls to queue for the bulkhead, and want to time-out how long they queue for. |
The typical ordering of policies outlined above gives rise to an execution flow similar to the below:
While the above presents one typical ordering and a fully-fledged strategy, PolicyWrap
is intentionally fully flexible, allowing you to include only the resilience components you need, or to devise more complex strategies, as required.
You may use the same policy-type multiple times (eg two RetryPolicy
s; two FallbackPolicy
s) in the same wrap. This is particularly useful for setting different handling strategies for different faults, as part of the overall strategy for a given call site.
- You might retry more times or with shorter delay for one kind of exception, than another.
- You might have one retry policy for typical transient faults; and a separate retry policy for a special case such as reauthentication; or honouring 429 RetryAfter headers.
- You may want one kind of exception to immediately break the circuit; others to break more cautiously.
- You can provide different fallback values/messages for different handled faults.
Other policy types can also be used more than once in a wrap:
- You can apply an overall timeout to the whole attempt (including waits-between-tries); as well as a separate timeout for each individual try.
- You can nest multiple cache policies with different kinds of cache: eg memory-cache backed up by a cloud cache (if not using a cache provider which provides this double-cache approach intrinsically).
.ExecuteAndCapture(...)
on a PolicyWrap
captures whether the final outcome of the execution is one considered a fault by the outermost policy in the wrap.
A PolicyKey
can be attached to a PolicyWrap
, as with any other policy:
PolicyWrap commonResilience = Policy
.Wrap(retry, breaker, timeout)
.WithPolicyKey("CommonServiceResilience");
The wrap's PolicyKey
is exposed on the execution Context
as the property:
context.PolicyWrapKey
In a multiply-nested wrap, the PolicyKey
attached to the outermost wrap carries all through the execution as the context.PolicyWrapKey
.
The future Polly roadmap envisages adding metrics to Polly, with metrics for the overall execution time (latency) across a PolicyWrap
or elements of a wrap.
Returns the outer policy of the pair represented by this PolicyWrap
instance.
Returns the inner policy of the pair represented by this PolicyWrap
instance.
All the below GetPolicies()
methods are available from Polly v5.6+.
IEnumerable<IsPolicy> GetPolicies()
returns all the policies configured in this PolicyWrap
instance, in outer->inner order.
IEnumerable<TPolicy> GetPolicies<TPolicy>()
returns all policies of type TPolicy
configured in this PolicyWrap
instance, in outer->inner order.
IEnumerable<TPolicy> GetPolicies<TPolicy>(Func<TPolicy, bool>)
returns all policies of type TPolicy
in this PolicyWrap
instance matching the filter
, in outer->inner order.
TPolicy GetPolicy<TPolicy>()
returns a single policy of type TPolicy
configured in this PolicyWrap
instance; or null
if none match.
TPolicy GetPolicy<TPolicy>(Func<TPolicy, bool>)
returns a single of type TPolicy
in this PolicyWrap
instance matching the filter
; or null
if none match.
PolicyWrap
is thread-safe: multiple calls may safely be placed concurrently through a policy instance.
PolicyWrap
instances may be re-used across multiple call sites.
When reusing policies, use an OperationKey
to distinguish different call-site usages within logging and metrics.