Polly is a useful dotnet library to make a web service call resilient by introducing retry, circuit breaker and fallback policy with ease.
It is common to add retry logic when a web service call is failed. However, integrating retry, circuit breaker and fallback logic can be tricky. Polly makes it easy.
The retry policy can be configured like below.
var retryPolicy = Policy.HandleResult<HttpResponseMessage>(result => result.StatusCode == HttpStatusCode.NotFound) .WaitAndRetryAsync(2, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
This policy defines retrying up to 2 times and with the interval that increased by the power of 2.
The circuit breaker policy can be configured as below.
var circuitBreakerPolicy = Policy.HandleResult<HttpResponseMessage>(result => result.StatusCode == HttpStatusCode.NotFound) .CircuitBreakerAsync(2, TimeSpan.FromSeconds(10), OnBreak, OnReset, OnHalfOpen);
The above policy defines circuit breaks at 3rd failure, which is NotFound error in this case, and stayed open for 10 seconds. The circuit breaker logic helps to prevent hammering the unresponsive services with retry calls from the client codes. Without a circuit breaker, it is possible for client code to DDos attack the target web service unintentionally.
The fallback policy can be set up as below.
var fallbackPolicy = Policy.HandleResult<HttpResponseMessage>(result => result.StatusCode == HttpStatusCode.NotFound) .FallbackAsync(context => { Console.WriteLine("Fallback policy executed"); return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); });
The above fallback code kicks in when the service call fails. The fallback logic can return a default value rather than a failure message. Returning a default value or cached value can be better than returning an Internal Server error.
To make the 3 policies together, WrapAsync method is provided.
var policyWrap = Policy.WrapAsync(fallbackPolicy, circuitBreakerPolicy, retryPolicy);
In dotnet web API, HttpClinet with Polly policy can be added in a dependency container.
builder.Services.AddHttpClient("ResilientClient") .AddPolicyHandler(policyWrap);
With the 3 policies combined, a request can be retried 2 times. If it still fails, a circuit breaker can be opened. When the request fails in the end, a fallback policy kicks in.
The client code can call a web service like the one below.
app.MapGet("/", async (IApiService apiService) => { if (circuitBreakerPolicy.CircuitState == CircuitState.Open) { return "Circuit breaker is open"; } var httpResponseMessage = await apiService.CallApi(); return httpResponseMessage.StatusCode switch { HttpStatusCode.OK => "OK", HttpStatusCode.NotFound => "Not Found", _ => "Unknown" }; });
By checking CircuitState is open, the Get request can fail fast rather than calling the backend API unnecessarily.
A complete code example is at rocker8942/WebApiPollyExample: Polly example in dotnet to use Retry, CircuitBreak and Fallback all together. (github.com)