Building Resilient and Fault-Tolerant .NET Applications with Polly and .NET 6

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.