Circuit Breaker with Ocelot & Polly – Part 2

In the previous blog post on Circuit Breaker with Ocelot And Polly, we saw how we could use the Ocelot library’s inbuilt support for Polly to build Circuit breakers for handling timeout errors from the downstream services. In this blog post, we will address, how we could trigger a circuit breaker when the downstream service returns a response status code of 500 (Internal Server Error) a fixed number of times.

This doesn’t come quite out of the box from Ocelot. But that doesn’t make it exactly difficult as well. Let us see how we could achieve this. As the first step, let us create a Sample endpoint in our downstream service which would return a 500 Status Code (Internal Server Error).

app.MapGet("services/raiseexception", () =>
{
    throw new Exception("Mock Exception");
})
.WithName("raiseexception");

This test endpoint would help us test our circuit breaker.

Handling 500 Internal Server Error

The next step is to build a DelegatingHandler for handling our requests.

public class PollyWithInternalServerErrorCircuitBreakingDelegatingHandler:DelegatingHandler
{
    private readonly IOcelotLogger _logger;
    private Polly.Wrap.AsyncPolicyWrap<HttpResponseMessage> _circuitBreakerPolicies;
    public PollyWithInternalServerErrorCircuitBreakingDelegatingHandler(DownstreamRoute route, IOcelotLoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<PollyWithInternalServerErrorCircuitBreakingDelegatingHandler>();

        var pollyQosProvider = new PollyQoSProvider(route,loggerFactory);
        
        var responsePolicy = Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.InternalServerError)
                                    .CircuitBreakerAsync(route.QosOptions.ExceptionsAllowedBeforeBreaking, 
                                                        TimeSpan.FromMilliseconds(route.QosOptions.DurationOfBreak));
        _circuitBreakerPolicies  = Policy.WrapAsync(pollyQosProvider.CircuitBreaker.Policies)
                                            .WrapAsync(responsePolicy);
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        try
        {
            return await _circuitBreakerPolicies.ExecuteAsync(() => base.SendAsync(request, cancellationToken));
        }
        catch (BrokenCircuitException ex)
        {
            _logger.LogError($"Reached to allowed number of exceptions. Circuit is open", ex);
            throw;
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError($"Error in CircuitBreakingDelegatingHandler.SendAync", ex);
            throw;
        }
        catch (Exception e)
        {
            throw;
        }
    }
}

Inheriting from DelegatingHandler, the PollyWithInternalServerErrorCircuitBreakingDelegatingHandler is responsible for a couple of things.

  • Create Policies for each of our requirement (existing policies supported by Polly and our new policy to handle 500 Response Status Codes)

We are retrieving the existing policies from the PollyQoSProvider class. We then create our custom policy, which is then wrapped along with the existing ones. Also note that we are using the same configuration which currently exists in the QosOptions configuration of Ocelot for our policy, but if required, we could create custom configurations as well for specify it separately.

  • Execute and send our request.

The second step we need to do is to ensure our custom Handler(PollyWithInternalServerErrorCircuitBreakingDelegatingHandler) is invoked instead of the existing PollyCircuitBreakingDelegatingHandler. This could be done by registering it with to OcelotBuilder services.

For ease of usage, let us create an extension method to serve our purposes.

public static IOcelotBuilder AddPollyWithInternalServerErrorHandling(this IOcelotBuilder builder)
{
    var errorMapping = new Dictionary<Type, Func<Exception, Error>>
    {
        {typeof(TaskCanceledException), e => new RequestTimedOutError(e)},
        {typeof(TimeoutRejectedException), e => new RequestTimedOutError(e)},
        {typeof(BrokenCircuitException), e => new RequestTimedOutError(e)}
    };

    builder.Services.AddSingleton(errorMapping);

    DelegatingHandler QosDelegatingHandlerDelegate(DownstreamRoute route, IOcelotLoggerFactory logger)
    {
        return new PollyWithInternalServerErrorCircuitBreakingDelegatingHandler(route, logger);
    }

    builder.Services.AddSingleton((QosDelegatingHandlerDelegate)QosDelegatingHandlerDelegate);

    return builder;
}

Now the only step required to do is to ensure we replace the existing call  AddPolly() with our new method.

builder.Services.AddOcelot()
                .AddPollyWithInternalServerErrorHandling();

That’s all we need to add to our custom Circuit Breaker policy to handle Response Status codes of 500 from the downstream services. Now each time the downstream services return 500 Error (for a configurable number of times), the circuit breaker would be tripped. Complete Source code for the example discussed in this blog post is available in my Github.

Happy Coding.

One thought on “Circuit Breaker with Ocelot & Polly – Part 2

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s