Improving HTTP resilience in Blazor webassembly
Hi All! Today I decided to take a quick break from my Blazor gamedev series and talk about resilience. We’ll see how to call a REST API from a Blazor webassembly app using an HTTP Client and how to handle errors using Polly.
As usual, I’ve pushed a sample repository on GitHub, feel free to check it out.
So, resilience. I have always liked this word.
re·sil·ience | \ ri-ˈzil-yən(t)s \
from https://www.merriam-webster.com/
– the capability of a strained body to recover its size and shape after deformation caused especially by compressive stress
– an ability to recover from or adjust easily to misfortune or change
Why should we care about it when writing applications? Well, like many other things in our field, the answer lies in Murphy’s law:
“Anything that can go wrong will go wrong”
This applies to most everything in software, regardless it’s between application boundaries or when communicating with external systems.
Talking about Blazor webassembly, oftentimes we have to use an Http Client to perform requests to some APIs. Or maybe we’re talking with a gRPC service, that’s more or less the same concept.
What should we do if a call fails for some reason? Maybe there was a network hiccup. Or maybe the service went down for maintenance. Or again, maybe we just were unlucky and stumbled upon some weird transient issue.
There are several cases to handle and to be fair, we can’t just surround all our calls with try/catch, if/else and so on and so forth.
So, assuming we’re using a Typed Http Client, we can leverage the super-famous NuGet package Polly and add few policies here and there.
I wrote about Polly in the past already, but this time let’s go a little bit more in detail.
Adding the Microsoft.Extensions.Http.Polly NuGet will allow us to write something like this:
builder.Services.AddHttpClient<WeatherClient>((sp, client) => { client.BaseAddress = new Uri(builder.Configuration["weatherApi"]); }).AddPolicyHandler(/* let's get fancy here */);
Now it comes the juicy part: we have to identify what’s the right Policy (or combination of) for us.
Usually the first stop is always WaitAndRetryAsync. In a nutshell, it would allow us to retry the call a specified number of times, potentially even using exponential backoff. Something like this:
Random _jitterer = new Random(); HttpPolicyExtensions .HandleTransientHttpError() .WaitAndRetryAsync(retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) + TimeSpan.FromMilliseconds(_jitterer.Next(0, 100) );
Notice the call to HandleTransientHttpError(). It’s a handy method that will handle transient errors for us, like timeouts and server errors.
This is nice and it’s a good starting point. But what if the server is not responding even after all the retries ? Well, depends. We might decide to throw an exception and live with it.
But since we’re always trying to be polite, we can try to go for graceful degradation. A very simple approach is to use the NullObject pattern, or, as Fowler uses to call it, Special Case.
We can create a compound Policy that wraps our Retry with a Fallback: this way if everything fails, we can return some kind of “acceptable” result to the user. For example, if we’re loading an archive of product, we can show a message saying “there are no products matching your query”. Whatever.
If you check the sample repository, you’ll see that this is exactly the strategy I’ve taken when things start going south:
The system is trying the call for 3 times before reverting to the fallback value. Everything is logged so we know what’s happening, but we can also instruct Polly to execute some custom operation, in this case logging some additional warning messages.
As you’ll see from the code, I have simply expanded the default Weather service example. Our fallback is an empty list of WeatherForecast items, which is treated like this by the UI:
Way better than blowing up isn’t it?