"After all, the engineers only needed to refuse to fix anything, and modern industry would grind to a halt." -Michael Lewis

Enable Massive Growth

Configuring, Testing, and Using Circuit Breakers on Rest API calls with Resilience4j

May 2021

The source code for what follows can be found on Github.

The circuit breaker pattern, popularized by Netflix [using Hystrix], exists for a couple of reasons, the most prominent being that it reduces load on a downstream service when it is not responding properly [presumably because it's under duress]. By wrapping operations that might fail and get overloaded in a circuit breaker, we can prematurely prevent cascading failure and stop overloading those services.

Testing the Circuit Breaker

To start with, we will want to build off of a previous article that demonstrates how to setup a Mock Server instance for testing. If you're using JUnit5, we can start like so:

@ExtendWith(MockServerExtension.class)
public class CircuitBreakerTest {

    private ClientAndServer clientAndServer;

    private RestTemplate restTemplate;

    public CircuitBreakerTest(ClientAndServer clientAndServer) {
        this.clientAndServer = clientAndServer;
        this.restTemplate = new RestTemplateBuilder()
                .rootUri("http://localhost:" + clientAndServer.getPort())
                .build();
    }

    @AfterEach
    public void reset() {
        this.clientAndServer.reset();
    }
}

We can now get some more boilerplate out of the way: let's just respond with a 500 status code on every request that we're about to make:

    @Test
    public void basicConfig_nothingHappensIfSlidingWindowNotFilled() {
        HttpRequest expectedFirstRequest = HttpRequest.request()
                .withMethod(HttpMethod.GET.name())
                .withPath("/some/endpoint/10");

        this.clientAndServer
                .when(expectedFirstRequest)
                .respond(HttpResponse.response().withStatusCode(500));

        ...code to come...
    }

And with that, we can actually start using the circuit breaker to make our applications better at handling failure.

Configuring and Using the Circuit Breaker

The circuit breaker configuration options are pretty varied and deserve their own set of articles. In most situations, the defaults are going to be reasonable. One that would make testing this very hard is the slidingWindowSize. Because this is set to 100 by default, we would have to call this 100 times before the circuit is eligible to be tripped [OPEN]. Therefore I'm going to make it 10 instead:

        CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig
                .custom()
                .slidingWindowSize(10)
                .build();

        CircuitBreakerRegistry circuitBreakerRegistry =
                CircuitBreakerRegistry.of(circuitBreakerConfig);

        CircuitBreaker callingEndpointCircuitBreaker = circuitBreakerRegistry.circuitBreaker("call-endpoint");

As is a common theme among Resilience4j's tooling, you need a registry, which takes in configuration. Then, to actually get an object you can work with you simply pull from the registry using a string to identify the name.

We can use a circuit breaker by passing in a lambda function containing the code we actually want to run, so that the circuit breaker is decorating it:

                callingEndpointCircuitBreaker.decorateSupplier(() ->
                        restTemplate.getForEntity("/some/endpoint/10", JsonNode.class)
                ).get();

But this doesn't tell us anything about the circuit breaker itself, because as we have it configured this call is just going to fail and we're going to get an exception. We can verify that a circuit trips once the slidingWindowSize has been reached [configured to 10] and then there is a greater than 50% error rate for the underlying operation, which in this case is a network call:

        // force the circuit to trip
        for (int i = 1; i < 11; i++) {
            try {
                callingEndpointCircuitBreaker.decorateSupplier(() ->
                        restTemplate.getForEntity("/some/endpoint/10", JsonNode.class)
                ).get();
                fail("we should never get here!");
            } catch (HttpServerErrorException e) {
                // expected
            }
        }

        // circuit is now tripped
        try {
            callingEndpointCircuitBreaker.decorateSupplier(() ->
                    restTemplate.getForEntity("/some/endpoint/10", JsonNode.class)
            ).get();
            fail("we should never get here!");
        } catch (CallNotPermittedException callNotPermittedException)  {
            assertEquals("call-endpoint", callNotPermittedException.getCausingCircuitBreakerName());
            assertSame(CircuitBreaker.State.OPEN, callingEndpointCircuitBreaker.getState());
        }

Here we use the circuit breaker decorator to call our downstream service [using mock server to simulate the service]. After ten attempts, because the error percentage was over 50%, we should see a new kind of error--one generated by the CircuitBreaker itself and not by the underlying code that it is decorating.

To sum this up, the code for the entire test can be seen here:

    @Test
    public void basicConfig_nothingHappensIfSlidingWindowNotFilled() {
        HttpRequest expectedFirstRequest = HttpRequest.request()
                .withMethod(HttpMethod.GET.name())
                .withPath("/some/endpoint/10");

        this.clientAndServer
                .when(expectedFirstRequest)
                .respond(HttpResponse.response().withStatusCode(500));

        CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig
                .custom()
                .slidingWindowSize(10)
                .build();

        CircuitBreakerRegistry circuitBreakerRegistry =
                CircuitBreakerRegistry.of(circuitBreakerConfig);

        CircuitBreaker callingEndpointCircuitBreaker = circuitBreakerRegistry.circuitBreaker("call-endpoint");

        // force the circuit to trip
        for (int i = 1; i < 11; i++) {
            try {
                callingEndpointCircuitBreaker.decorateSupplier(() ->
                        restTemplate.getForEntity("/some/endpoint/10", JsonNode.class)
                ).get();
                fail("we should never get here!");
            } catch (HttpServerErrorException e) {
                // expected
            }
        }

        // circuit is now tripped
        try {
            callingEndpointCircuitBreaker.decorateSupplier(() ->
                    restTemplate.getForEntity("/some/endpoint/10", JsonNode.class)
            ).get();
            fail("we should never get here!");
        } catch (CallNotPermittedException callNotPermittedException)  {
            assertEquals("call-endpoint", callNotPermittedException.getCausingCircuitBreakerName());
            assertSame(CircuitBreaker.State.OPEN, callingEndpointCircuitBreaker.getState());
        }
    }

And with that, you should be good to go.

Nick Fisher is a software engineer in the Pacific Northwest. He focuses on building highly scalable and maintainable backend systems.