Adding Resilience and Transient Fault handling to your .NET Core HttpClient with Polly
Last week while upgrading my podcast site to ASP.NET Core 2.1 and .NET. Core 2.1 I moved my Http Client instances over to be created by the new HttpClientFactory. Now I have a single central place where my HttpClient objects are created and managed, and I can set policies as I like on each named client.
It really can't be overstated how useful a resilience framework for .NET Core like Polly is.
Take some code like this that calls a backend REST API:
public class SimpleCastClient
{
private HttpClient _client;
private ILogger<SimpleCastClient> _logger;
private readonly string _apiKey;
public SimpleCastClient(HttpClient client, ILogger<SimpleCastClient> logger, IConfiguration config)
{
_client = client;
_client.BaseAddress = new Uri($"https://api.simplecast.com");
_logger = logger;
_apiKey = config["SimpleCastAPIKey"];
}
public async Task<List<Show>> GetShows()
{
var episodesUrl = new Uri($"/v1/podcasts/shownum/episodes.json?api_key={_apiKey}", UriKind.Relative);
var res = await _client.GetAsync(episodesUrl);
return await res.Content.ReadAsAsync<List<Show>>();
}
}
Now consider what it takes to add things like
- Retry n times - maybe it's a network blip
- Circuit-breaker - Try a few times but stop so you don't overload the system.
- Timeout - Try, but give up after n seconds/minutes
- Cache - You asked before!
- I'm going to do a separate blog post on this because I wrote a WHOLE caching system and I may be able to "refactor via subtraction."
If I want features like Retry and Timeout, I could end up littering my code. OR, I could put it in a base class and build a series of HttpClient utilities. However, I don't think I should have to do those things because while they are behaviors, they are really cross-cutting policies. I'd like a central way to manage HttpClient policy!
Enter Polly. Polly is an OSS library with a lovely Microsoft.Extensions.Http.Polly package that you can use to combine the goodness of Polly with ASP.NET Core 2.1.
As Dylan from the Polly Project says:
HttpClientFactory in ASPNET Core 2.1 provides a way to pre-configure instances of
HttpClient
which apply Polly policies to every outgoing call.
I just went into my Startup.cs and changed this
services.AddHttpClient<SimpleCastClient>();
to this (after adding "using Polly;" as a namespace)
services.AddHttpClient<SimpleCastClient>().
AddTransientHttpErrorPolicy(policyBuilder => policyBuilder.RetryAsync(2));
and now I've got Retries. Change it to this:
services.AddHttpClient<SimpleCastClient>().
AddTransientHttpErrorPolicy(policyBuilder => policyBuilder.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 2,
durationOfBreak: TimeSpan.FromMinutes(1)
));
And now I've got CircuitBreaker where it backs off for a minute if it's broken (hit a handled fault) twice!
I like AddTransientHttpErrorPolicy because it automatically handles Http5xx's and Http408s as well as the occasional System.Net.Http.HttpRequestException. I can have as many named or typed HttpClients as I like and they can have all kinds of specific policies with VERY sophisticated behaviors. If those behaviors aren't actual Business Logic (tm) then why not get them out of your code?
Go read up on Polly at https://githaub.com/App-vNext/Polly and check out the extensive samples at https://github.com/App-vNext/Polly-Samples/tree/master/PollyTestClient/Samples.
Even though it works great with ASP.NET Core 2.1 (best, IMHO) you can use Polly with .NET 4, .NET 4.5, or anything that's compliant with .NET Standard 1.1.
Gotchas
A few things to remember. If you are POSTing to an endpoint and applying retries, you want that operation to be idempotent.
"From a RESTful service standpoint, for an operation (or service call) to be idempotent, clients can make that same call repeatedly while producing the same result."
But everyone's API is different. What would happen if you applied a Polly Retry Policy to an HttpClient and it POSTed twice? Is that backend behavior compatible with your policies? Know what the behavior you expect is and plan for it. You may want to have a GET policy and a post one and use different HttpClients. Just be conscious.
Next, think about Timeouts. HttpClient's have a Timeout which is "all tries overall timeout" while a TimeoutPolicy inside a Retry is "timeout per try." Again, be aware.
Thanks to Dylan Reisenberger for his help on this post, along with Joel Hulen! Also read more about HttpClientFactory on Steve Gordon's blog and learn more about HttpClientFactory and Polly on the Polly project site.
Sponsor: Check out JetBrains Rider: a cross-platform .NET IDE. Edit, refactor, test and debug ASP.NET, .NET Framework, .NET Core, Xamarin or Unity applications. Learn more and download a 30-day trial!
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Last time in my .net core project I had to implement circuit breaker policy.
I spent two days for implement generic mechanism, which use all policies from Polly.
Now, each time, when I want to connect with third service - everything what i need to do is just use this mechanism ;)
Go read up on Polly at https://githaub.com/App-vNext/Polly and check out the extensive samples at https://github.com/App-vNext/Polly-Samples/tree/master/PollyTestClient/Samples.
Great stuff Scott! I didn't realize there was a German GitHub called GitHaub :)
Not blocking is not a radical concept. It's well documented advice for good reason; Scott himself wrote a good summary in an old post. It may sometimes be simpler to implement and there are situations where it doesn't matter and/or you can get away with it, but you definitely trade it for less robust solutions and harder to debug problems.
I spent two days for implement generic mechanism, which use all policies from Polly.
Now, each time, when I want to connect with third service - everything what i need to do is just use this mechanism ;)
Regards
MJ
Best outdoor projectors
throw new HttpRequestException("Foo");
Comments are closed.
I just added a link to this post to the Polly readme, where people can find this and other great posts about our little library :)