In modern software development, particularly when dealing with microservices or distributed systems, managing failures gracefully is crucial. One robust approach to handle such failures is the Circuit Breaker pattern. This pattern helps prevent cascading failures and enhances the resilience of applications. This blog post delves into the Circuit Breaker pattern, explains its workings, and provides practical examples using .NET 8 and C#.
What is the Circuit Breaker Pattern?
The Circuit Breaker pattern is a design pattern used in software development to detect and handle failures gracefully. It acts as a protective barrier, cutting off calls to a failing service to prevent the application from repeatedly trying to execute the same failing operation. By doing this, it helps maintain the stability and responsiveness of the system.
How Does It Work?
The Circuit Breaker pattern operates in three states:
- Closed: The circuit is closed, and requests pass through to the service. If the service call fails a certain number of times (defined by a threshold), the circuit transitions to the Open state.
- Open: The circuit is open, and requests are blocked for a specific period. During this period, any call to the service fails immediately, preventing the system from being overwhelmed by failure attempts.
- Half-Open: After the open period ends, the circuit transitions to a Half-Open state. A limited number of requests are allowed through to test if the service has recovered. If they succeed, the circuit transitions back to Closed; if they fail, it returns to Open.
Implementing Circuit Breaker in .NET 8 with C
Let’s explore how to implement the Circuit Breaker pattern in .NET 8 using C#.
Setting Up the Environment
First, ensure you have the .NET 8 SDK installed. You can create a new project using the following command:
dotnet new webapi -n CircuitBreakerDemo
cd CircuitBreakerDemo
Adding Necessary Packages
We will use the Polly library, which is a .NET library that provides resilience and transient-fault-handling capabilities. Install it using:
dotnet add package Polly
dotnet add package Microsoft.Extensions.Http.Polly
Configuring the Circuit Breaker
Here’s how you can configure a basic Circuit Breaker using Polly in your Program.cs
:
using Microsoft.Extensions.DependencyInjection;
using Polly;
using Polly.Extensions.Http;
using System;
using System.Net.Http;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient("RemoteService", client =>
{
client.BaseAddress = new Uri("https://api.example.com");
})
.AddPolicyHandler(GetCircuitBreakerPolicy());
var app = builder.Build();
app.MapGet("/", async (IHttpClientFactory httpClientFactory) =>
{
var client = httpClientFactory.CreateClient("RemoteService");
var response = await client.GetAsync("/endpoint");
return response.IsSuccessStatusCode ? "Success" : "Failure";
});
app.Run();
static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(2, TimeSpan.FromMinutes(1));
}
Explanation
- HttpClient Configuration: We configure an
HttpClient
named „RemoteService“ that points to a hypothetical remote API. - Policy Handler: We add a policy handler that uses Polly to apply a Circuit Breaker policy.
- CircuitBreakerAsync: This method defines the Circuit Breaker policy, where the circuit opens after 2 consecutive failures and stays open for 1 minute before transitioning to Half-Open.
Detailed Example with Custom Settings
To further illustrate, let’s enhance the example with custom settings and logging:
using Microsoft.Extensions.DependencyInjection;
using Polly;
using Polly.Extensions.Http;
using System;
using System.Net.Http;
using Microsoft.Extensions.Logging;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddLogging();
builder.Services.AddHttpClient("RemoteService", client =>
{
client.BaseAddress = new Uri("https://api.example.com");
})
.AddPolicyHandler(GetCircuitBreakerPolicy(builder.Logging));
var app = builder.Build();
app.MapGet("/", async (IHttpClientFactory httpClientFactory, ILogger<Program> logger) =>
{
var client = httpClientFactory.CreateClient("RemoteService");
try
{
var response = await client.GetAsync("/endpoint");
return response.IsSuccessStatusCode ? "Success" : "Failure";
}
catch (Exception ex)
{
logger.LogError(ex, "Request failed");
return "Failure";
}
});
app.Run();
static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy(ILoggerFactory loggerFactory)
{
var logger = loggerFactory.CreateLogger<Program>();
return HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 3,
durationOfBreak: TimeSpan.FromSeconds(30),
onBreak: (outcome, breakDelay) =>
{
logger.LogWarning($"Circuit broken! Delay: {breakDelay.TotalSeconds}s, Exception: {outcome.Exception?.Message}");
},
onReset: () => logger.LogInformation("Circuit reset."),
onHalfOpen: () => logger.LogInformation("Circuit is half-open.")
);
}
Summary
The Circuit Breaker pattern is an essential tool for building resilient microservices and distributed systems. It helps prevent cascading failures, allows systems to recover gracefully, and maintains overall system stability. By leveraging libraries like Polly in .NET 8, developers can easily implement this pattern, enhancing the robustness of their applications.
Pros:
- Prevents cascading failures: Isolates failing services to protect the overall system.
- Improves stability: Allows systems to maintain functionality under failure conditions.
- Enhances user experience: Prevents prolonged waits due to repeated failures.
Cons:
- Complexity: Adds another layer of complexity to the system design.
- Potential delays: Can introduce delays during recovery periods.
In conclusion, the Circuit Breaker pattern is a vital component in the toolkit of modern software development, ensuring that applications remain robust and reliable in the face of inevitable failures. As systems grow in complexity, such patterns will continue to play a crucial role in maintaining their health and performance.