Spring Cloud - Intro to Circuit Breaker with Spring Boot 3 & Resilience4j

Spring Cloud - Intro to Circuit Breaker with Spring Boot 3 & Resilience4j

Intro to Spring Cloud Circuit Breaker with Spring Boot 3.x, Java 17 & Resilience4j

Hola fellow devs!

Today, we'll talk a little bit about Circuit Breaker Pattern and how we can implement it in Spring Boot with Resilience4j as our choice of Circuit Breaker library. So to start, well first talk about the problem we're trying to solve here.

Important note: All the prerequisite code setup is mentioned in my article over Service Discovery with Netflix Eureka. Please refer to this article to set up services as, in this article, we're going to build on top of them.


What is Circuit Breaker Pattern?

At this point, I'm assuming you already have the code setup as per the above note. So let's take the case of this application here.

Restaurant Service needs Location Service to create an order. But what if Location Service is down? Or Location Service API is not working for some reason. Maybe a failed auth or timeout.

In that case, we're going to see a failure in our create order API. Since this is an important piece to create our order, we surely can't let the user proceed with the order without any address information. With the current code, it'll start throwing some exceptions depending on the issue and most probably result in either of the 2 cases.

  1. You'll keep retrying the Location Service API in hopes of getting a proper response and keep handling the Exceptions.

  2. You'll keep retrying the Location Service API & user will end up getting Internal Server Error

Both these scenarios have some flaws as listed:

  1. We're unnecessarily calling a dead API hoping it'll work. This only increases our network calls

  2. Poor UX as users get Internal Server Error

  3. Exception handling is an extra overhead that takes a toll on performance if happens very frequently at scale or is done poorly.

This is where Circuit Breaker Pattern comes in. In this pattern, we choose to stop calling a failing external system and return a response from some kind of fallback mechanism that doesn't include calling a failing system. In other words, we end up tripping the circuit of our application to handle the failure gracefully.

In this article, we're going to learn to implement this Circuit Breaker in our application.


Hands-on with Circuit Breaker

Here is the stack we're going to use to implement Circuit Breaker

  • Spring Boot 3

  • Java 17

  • Gradle 7.6

  • Spring Cloud - 2022.0.2


Creating the need for Circuit Breaker

To achieve this, we're going to add some more functionality to our application. Let's say, to enhance UX, we decide to add a good foodie quote to be displayed along with the order details. To fetch these quotes, we hit another API which eventually ends up being a shitty one. Goes down every now and then without any notification.

This requires a couple of changes in our code.

First, we introduce an OrderResponse DTO that'll act as our response class for order creation. We're going to use Lombok for our DTO class. This is how it'll look like

@Data
@Builder
public class OrderResponse {
    private String address;
    private boolean orderConfirmed;
    private String foodLoverQuote;
}

Next are some changes we'll do to our create order API to add quotes to the response.

@PostMapping("/{restaurantId}/order")
    public OrderResponse createOrderForRestaurant(@PathVariable int restaurantId) {
        String baseUrl = discoveryClient.getInstances("Location Service")
                .stream()
                .findFirst()
                .map(ServiceInstance::getUri)
                .orElse(null).toString();
        log.info("Base Url fetched is : {}", baseUrl);
        RestTemplate rt = new RestTemplate();
        String location = rt.getForObject(String.format("%s/location/restaurant/%d", baseUrl, restaurantId), String.class);
        log.info("Restaurant Address : {}", location);

        // Fetching quote from a random url which is going to fail
        String quote = rt.getForObject("http://foodquote.blog", String.class);
        return OrderResponse.builder()
                .address(location)
                .orderConfirmed(true)
                .foodLoverQuote(quote)
                .build();
    }

Now, let's start all our services (Eureka, Location, and Restaurant Service) and try to create an order. We see that order creation failed here. The response that the user gets looks something like this.

{
    "timestamp": "2023-04-08T06:48:27.437+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/restaurant/1/order"
}

And in logs, we see an error :

java.net.UnknownHostException: foodquote.blog

This error could be anything ranging from Unknown Host to Timeout or Unauthorised. But we don't want users to miss out on the good things that we're planning for them (like really important "food lover quotes") or even worse, compromise the entire order creation flow for a quote.

This is where Circuit Breaker comes in.


Implementing Circuit Breaker with Resilience4j

To use Resilience4j, we first need to add the dependency. For this project, we're using Gradle. So here we go with the dependency.

implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'

Since it is all autoconfigured, we simply need to add this dependency and the default Circuit Breaker configuration will be done by Spring Boot automagically.

Next, we need to wrap our failing task in a Circuit Breaker executor provided by Spring Boot. That means that this piece of code :

 String quote = rt.getForObject("http://foodquote.blog", String.class);

changes to :

String quote = circuitBreakerFactory.create("Quote-Circuit-Breaker")
                .run(() -> rt.getForObject("http://foodquote.blog", String.class),
                        throwable -> "It’s not true that money can’t buy happiness. I mean, have you tried buying ice cream? It’s the same thing!");

Let's break this code and see what's going on here.

We first create a 'circuitBreakerFactory' instance from the default Spring implementation.

@Autowired
private CircuitBreakerFactory circuitBreakerFactory;

Next, we use this factory to get an instance of CircuitBreaker with '.create(String id)' method and implement our code inside its run method. This run method has 2 signatures :

  1. T run(Supplier<T> toRun)

  2. T run(Supplier<T> toRun, Function<Throwable, T> fallback)

The job of the supplier in run method is to wrap the code that might be leading to a failing external resource and handle the failure.

The second argument is a Function that decides how you want to handle the failure. In our case, we have set a default quote to be sent in case the API to fetch quotes fails unexpectedly.

If you don't provide a fallback function, it throws a NoFallbackAvailableException.

So now, if you try to create the order with the same URL, not only your order will be created successfully, but also you'll see a super awesome food quote that'll remind you to order a good Belgian Chocolate Icecream.

{
    "address": "221B Undertaker Street",
    "orderConfirmed": true,
    "foodLoverQuote": "It’s not true that money can’t buy happiness. I mean, have you tried buying ice cream? It’s the same thing!"
}


Conclusion

So this is how we can very easily implement a circuit breaker with almost 0 configuration. But we have a couple of options to customize our circuit breaker which demands a separate article altogether.

With Circuit Breaker, we can have default data and in case of network failure, we can use that default data. This pattern can be employed to mitigate a lot of performance issues stemming from timeouts and dead endpoints to make sure that we provide a seamless experience to users and that our application is fault tolerant & robust.

That'll be all for today. See you in the next article. Thanks for reading :)

Ciao.