Important
.NET 8
now brings better support for adding resilience to HttpClient
. See Add resilience to an HTTP client and Building resilient cloud services with .NET 8 | .NET Conf 2023.
You should consider adopting the new .NET 8
API instead of using the one presented here.
Every time I use an HttpClient
I end up repeating the same Polly usage pattern in my projects to add a circuit breaker policy.
Plus, at times I want to have the values for configuring the circuit breaker policy read from the appsettings.json
which further increases the code I keep repeating.
You will have to add the dotnet-sdk-extensions nuget to your project.
The AddCircuitBreakerPolicy
method is an extension method to the IHttpClientBuilder
which is what you use when configuring an HttpClient.
This extension will add a circuit breaker policy wrapped with a circuit breaker checker policy to the HttpClient
.
Note
the variable services
in the examples below is of type IServiceCollection
. On the default template
for a Web API you can access it via builder.services
. Example:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
You can add a circuit breaker policy by doing the following:
services
.AddHttpClient("my-http-client")
.AddCircuitBreakerPolicy(options =>
{
options.FailureThreshold = 0.5; // break on >=50% actions result in failures
options.SamplingDurationInSecs = 10; // over any 10 second period
options.MinimumThroughput = 8; // provided at least 8 actions in the 10 second period
options.DurationOfBreakInSecs = 30; // break for 30 seconds
});
The above example is the simplest way to use the extension method. Note that:
-
even though the example shows adding a circuit breaker policy to a named
HttpClient
you can also add it to typedHttpClient
because the extension method works on theIHttpClientBuilder
. -
the configuration of the policy's options is done inline but the extension method is also integrated with the all the ecosystem around the Options pattern in dotnet core, such as the possibility of binding the options values from the
appsettings
. See Binding appsettings values to the circuit breaker policy options. -
you can provide a class to handle the events produced by the circuit breaker policy. See Handling events from the circuit breaker policy.
The CircuitBreakerOptions
provides the following configuration options for the circuit breaker policy:
FailureThreshold
: failure threshold at which the circuit will break, eg 0.5 represents breaking if 50% or more of actions result in a handled failure. Must be a value betweendouble.Epsilon
and 1.SamplingDurationInSecs
: duration of the timeslice over which failure ratios are assessed. Must be a value betweendouble.Epsilon
anddouble.MaxValue
. You can represent values smaller than 1 second by using a decimal number such as 0.1 which would mean 100 milliseconds.MinimumThroughput
: this many actions or more must pass through the circuit in the timeslice for statistics to be considered significant and the circuit-breaker to come into action. Must be a value between 2 andint.MaxValue
.DurationOfBreakInSecs
: duration the circuit will stay open before resetting. Must be a value betweendouble.Epsilon
anddouble.MaxValue
. You can represent values smaller than 1 second by using a decimal number such as 0.1 which would mean 100 milliseconds.
If you want to bind the configuration from the appsettings remember that the name of the key in the appsettings must match the property names of the CircuitBreakerOptions
for the bind to work.
Imagine that you have an appsettings file with the following:
"MyHttpClient": {
"FailureThreshold": 0.5,
"SamplingDurationInSecs": 10,
"MinimumThroughput": 8,
"DurationOfBreakInSecs": 30
}
You can add a circuit breaker policy that is configured from the values on the appsettings file by doing the following:
services
.AddHttpClientCircuitBreakerOptions("my-circuit-breaker-options")
.Bind(Configuration.GetSection("MyHttpClient"));
services
.AddHttpClient("my-http-client")
.AddCircuitBreakerPolicy("my-circuit-breaker-options");
The services.AddHttpClientCircuitBreakerOptions
adds a named options of type CircuitBreakerOptions
and returns an instance of OptionsBuilder<CircuitBreakerOptions>
, which means you can now use any of the methods provided by dotnet to configure it such as for example:
This extension method also enables you to access the events provided by Polly's circuit breaker policy.
You can specify a class to handle the circuit breaker events by doing the following:
services
.AddHttpClient("my-http-client")
.AddCircuitBreakerPolicy<MyCircuitBreakerEventHandler>(options =>
{
options.FailureThreshold = 0.5;
options.SamplingDurationInSecs = 10;
options.MinimumThroughput = 8;
options.DurationOfBreakInSecs = 30;
});
The MyCircuitBreakerEventHandler
must implement the ICircuitBreakerPolicyEventHandler
interface.
public class MyCircuitBreakerEventHandler : ICircuitBreakerPolicyEventHandler
{
private readonly ILogger<MyCircuitBreakerEventHandler> _logger;
public MyCircuitBreakerEventHandler(ILogger<MyCircuitBreakerEventHandler> logger)
{
_logger = logger;
}
public Task OnBreakAsync(BreakEvent breakEvent)
{
//do something like logging
_logger.LogInformation($"Circuit state transitioned from {breakEvent.PreviousState} to open/isolated for the HttpClient {breakEvent.HttpClientName}. Break will last for {breakEvent.DurationOfBreak}");
return Task.CompletedTask;
}
public Task OnHalfOpenAsync(HalfOpenEvent halfOpenEvent)
{
//do something like logging
_logger.LogInformation($"Circuit state transitioned to half open for the HttpClient {halfOpenEvent.HttpClientName}");
return Task.CompletedTask;
}
public Task OnResetAsync(ResetEvent resetEvent)
{
//do something like logging
_logger.LogInformation($"Circuit state transitioned to closed for the HttpClient {resetEvent.HttpClientName}");
return Task.CompletedTask;
}
}
With the above whenever a circuit breaker event occurs on the my-http-client
HttpClient
there will be a log message for it.
There are overloads that enable you to have more control on how the instance that will handle the events is created. For instance:
services
.AddHttpClient("my-http-client")
.AddCircuitBreakerPolicy(
configureOptions: options =>
{
options.FailureThreshold = 0.5;
options.SamplingDurationInSecs = 10;
options.MinimumThroughput = 8;
options.DurationOfBreakInSecs = 30;
},
eventHandlerFactory: provider =>
{
// This would be the same as using the `AddCircuitBreakerPolicy<MyCircuitBreakerEventHandler>`.
// It's just an example of how you can control the creaton of the object handling the
// policy events.
var loggerFactory = provider.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<MyCircuitBreakerEventHandler>();
return new MyCircuitBreakerEventHandler(logger);
});
For the majority of the cases the overload that accepts a genericy type AddCircuitBreakerPolicy<T>
is what is more likely to be used since whatever dependencies you need to provide to the type T
can be passed through the constructor as long as they are added to the IServiceCollection
.
The AddCircuitBreakerPolicy
extension method adds a circuit breaker checker policy that is evaluated before the circuit breaker policy.
When the circuit's state is open/isolated the circuit breaker checker policy avoids an exception being thrown and returns a instance of CircuitBrokenHttpResponseMessage
which derives from HttpResponseMessage
and has a status code 500.
If you want to handle the CircuitBrokenHttpResponseMessage
type returned, here's an example you can consider:
//httpClient is an HttpClient with a circuit breaker and a circuit breaker check policies applied
var response = await httpClient.GetAsync("/some-path");
if(response is CircuitBrokenHttpResponseMessage circuitBrokenHttpResponseMessage)
{
// do something because the request failed due to the circuit being broken
}
if (response.IsSuccessStatusCode)
{
// do something because the request was successful
}
else
{
// do something because the request failed
}