Introduction
Building resilient and fault-tolerant applications is more critical than ever in today's fast-paced and distributed software landscape. A well-designed application should gracefully handle failures and maintain a high level of availability and responsiveness.
This article will discuss patterns and practices for building resilient .NET applications, including the Circuit Breaker, Retry, and Bulkhead patterns. We will also demonstrate how to use the popular resilience library Polly in conjunction with .NET 6 to implement these patterns effectively.
Resilience Patterns
1. Retry Pattern
The Retry pattern involves reattempting a failed operation due to transient errors, such as network failures or timeouts. The application can recover from temporary issues without impacting the user experience by retrying the operation.
2. Circuit Breaker Pattern
The Circuit Breaker pattern helps to prevent a cascade of failures in a distributed system. When a service fails repeatedly, the circuit breaker "trips" and stops calling the failing service for a pre-defined period. The application can return a default response or fail fast during this time. Once the time elapses, the circuit breaker allows requests to the service again, giving it time to recover.
3. Bulkhead Pattern
The Bulkhead pattern isolates critical resources, such as threads or network connections, to prevent failures in one part of the system from affecting the entire application. Creating separate "compartments" for each resource type allows the application to continue functioning even if one resource becomes unavailable.
Using Polly for Resilience in .NET 6
Polly is a powerful and extensible library for building fault-tolerant and resilient .NET applications. It provides a set of policies that implement the resilience patterns mentioned above. Let's explore how to use Polly in a .NET 6 application.
a. Install the Polly NuGet package
dotnet add package Polly
b. Implement the Retry pattern
using Polly;
// Create a retry policy with a maximum of 3 retries and an exponential backoff
var retryPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
// Use the retry policy to execute an HTTP request
await retryPolicy.ExecuteAsync(async () =>
{
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync("https://api.example.com/data");
response.EnsureSuccessStatusCode();
});
c. Implement the Circuit Breaker pattern
using Polly;
// Create a circuit breaker policy with a maximum of 2 consecutive failures and a 30-second break duration
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(2, TimeSpan.FromSeconds(30));
// Use the circuit breaker policy to execute an HTTP request
await circuitBreakerPolicy.ExecuteAsync(async () =>
{
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync("https://api.example.com/data");
response.EnsureSuccessStatusCode();
});
d. Implement the Bulkhead pattern
using Polly;
// Create a bulkhead policy that allows a maximum of 2 concurrent requests
var bulkheadPolicy = Policy.BulkheadAsync(2);
// Use the bulkhead policy to execute an HTTP request
await bulkheadPolicy.ExecuteAsync(async () =>
{
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync("https://api.example.com/data");
response.EnsureSuccessStatusCode();
});
e. Combine policies for enhanced resilience
using Polly;
// Combine a retry policywith a circuit breaker and bulkhead policy
var combinedPolicy = Policy.WrapAsync(retryPolicy, circuitBreakerPolicy, bulkheadPolicy);
// Use the combined policy to execute an HTTP request
await combinedPolicy.ExecuteAsync(async () =>
{
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync("https://api.example.com/data");
response.EnsureSuccessStatusCode();
});
f. Integrate Polly with HttpClientFactory in ASP.NET Core
// In your Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("exampleApi", client =>
{
client.BaseAddress = new Uri("https://api.example.com/");
})
.AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))))
.AddTransientHttpErrorPolicy(builder => builder.CircuitBreakerAsync(2, TimeSpan.FromSeconds(30)))
.AddPolicyHandler(Policy.BulkheadAsync(2));
// Other services and configurations
}
g. Use the configured HttpClient in your application
public class ExampleService
{
private readonly HttpClient _httpClient;
public ExampleService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task GetDataAsync()
{
var response = await _httpClient.GetAsync("data");
response.EnsureSuccessStatusCode();
}
}
Conclusion
Building resilient and fault-tolerant .NET applications is crucial for maintaining high availability and delivering a reliable user experience. Applications can gracefully handle failures and recover from transient issues by implementing patterns such as Retry, Circuit Breaker, and Bulkhead.
Polly is an excellent library for building resilience in .NET applications, offering a wide range of policies and seamless integration with ASP.NET Core and HttpClientFactory. With the power of Polly and .NET 6, developers can create robust applications that can withstand the challenges of modern distributed systems.