Combining policies
rezilience policies can be composed into one to apply several resilience strategies as one.
A composed policy has a wider range of possible errors than an individual policy. This is made explicit by having to convert each policy to an instance of Policy by calling .toPolicy. Such a Policy has a slightly different signature for the apply method in the error type:
def apply[R, E1 <: E, A](f: ZIO[R, E1, A]): ZIO[R, PolicyError[E1], A]
A policy can be composed with another one using its compose method, which wraps another policy around it. Below is an example of wrapping a Retry around a RateLimiter around a Bulkhead. The for-comprehension is needed because policies are created as ZManageds.
val policy: ZIO[Scope, Nothing, Policy[Any]] = for {
rateLimiter <- RateLimiter.make(1, 2.seconds)
bulkhead <- Bulkhead.make(2)
retry <- Retry.make(Schedule.recurs(3))
} yield bulkhead.toPolicy compose rateLimiter.toPolicy compose retry.toPolicy
Composing policies requires some special care in handling policy errors, behavior-wise and type-wise. Take for example a retry around a circuit breaker.
-
Behavior: what is the desired retry behavior when a circuit breaker error is encountered? Should the call be retried or the error passed through to the caller?
-
Types: because a
Retryis created with aSchedulethat expects a certain typeEof errors as input, aRetry[E]cannot be applied onZIO[R, CircuitBreakerError[E], A]effects.
For these cases, the Retry and CircuitBreaker policies have a widen method that can adapt them to a diferent type of error. For example to adapt a Retry[Throwable] to a Retry[PolicyError[Throwable]]:
val retry: Retry[Throwable] = ???
val retryComposable = retry.widen[PolicyError[Throwable]] { case Policy.WrappedError(e) => e }
The partial function above is made available as Policy#unwrap[E] for convenience, so that the above can be written as
val retryComposable: Retry[PolicyError[Throwable]] = retry.widen(Policy.unwrap[Throwable])
Many variations of policy combinations are possible. The polly project has some good advice for the order in which to compose policies: https://github.com/App-vNext/Polly/wiki/PolicyWrap#usage-recommendations.