# Circuit Breaker

In 
Published 2023-07-01

This tutorial explains how to use Spring Cloud Circuit Breaker. This tutorial contains an example as well.

The main idea behind the Circuit Breaker concept is that in a case where "Service A" calls "Service B", "Service A" must stop calling "Service B" for a period of time (some rules apply) when the "Service B" is not responding (could be down or not).

This pattern has a lot of advantages:

  • that call uses some resources (computing, network, disk sometimes) and this resource consumption must be avoided
  • it helps to prevent cascading failures
  • the state of the circuit breaker can be used for error monitoring

This pattern is difficult to test and to maintain. For this reason, the circuit breaker pattern is not used for every microservice implementation. Before implementing, you need to evaluate the improvements it brings to your system.

For implementing and testing the Circuit Breaker, first of all we need to create a simple Web Spring Boot application using spring initializr. This application will not implement the Circuit Breaker, but it will be called by another application which uses the Circuit Breaker pattern.

So we will have the following situation: CircuitBreakerApplication (uses Circuit Breaker patter) will call ExternalApiApplication (doesn't use Circuit Breaker patter).

The ExternalApiApplication has the following classes :

ExternalApiApplication.java
package com.example.externalapi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ExternalApiApplication {

	public static void main(String[] args) {
		SpringApplication.run(ExternalApiApplication.class, args);
	}

}
MyController.java
package com.example.externalapi;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyController {

    @GetMapping("/call")
    public String call() {
        var var1 =  "{\"code\":\"ADES\", \"description\":\"This is the description of the code\"}";
        return var1;
    }
}

The pom.xml is the following:

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.1.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>external-api</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>external-api</name>
	<description>External API for Spring Boot Circuit Breaker</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

... and the application.properties is :

MyInterface.java
server.port=8010

So, this is a simple service which expose a simple API.

Now, let create the CircuitBreakerApplication.

From spring initializr we generate a web application with the following dependencies.

Now I will choose "MyCircuitBreaker1" as name for my Circuit Breaker and I will define it in application.properties:

application.properties
server.port=9090

# MyCircuitBreaker1 settings
resilience4j.circuitbreaker.instances.MyCircuitBreaker1.registerHealthIndicator=true
resilience4j.circuitbreaker.instances.MyCircuitBreaker1.slidingWindowSize=10
resilience4j.circuitbreaker.instances.MyCircuitBreaker1.slidingWindowType=COUNT_BASED
resilience4j.circuitbreaker.instances.MyCircuitBreaker1.permittedNumberOfCallsInHalfOpenState=4
resilience4j.circuitbreaker.instances.MyCircuitBreaker1.minimumNumberOfCalls=2
resilience4j.circuitbreaker.instances.MyCircuitBreaker1.waitDurationInOpenState=5s
resilience4j.circuitbreaker.instances.MyCircuitBreaker1.slowCallRateThreshold=50
resilience4j.circuitbreaker.instances.MyCircuitBreaker1.slowCallDurationThreshold=10
resilience4j.circuitbreaker.instances.MyCircuitBreaker1.failureRateThreshold=10

management.health.circuitbreakers.enabled=true

# Actuator will show all the information (including circuit breakers) which is not done by default
management.endpoint.health.show-details="ALWAYS"
endpoints.web.exposure.include="*"

registerHealthIndicator: tells Circuit Breaker to register its health indicator in Actuator. (Default value is "false")

slidingWindowSize: configures the size of the sliding window which is used to record the outcome of calls when the CircuitBreaker is closed. Its value is in counts (when slidingWindowType=COUNT_BASED) or in seconds (when slidingWindowType=TIME_BASED). (Default value is 100)

permittedNumberOfCallsInHalfOpenState: defines the number of permitted calls when the CircuitBreaker is half open. (Default value is 10)

minimumNumberOfCalls: defines the minimum number of calls which are required (per sliding window period) before the CircuitBreaker can calculate the error rate. (Default value is 100)

waitDurationInOpenState: defines the wait duration (in seconds) which specifies how long the CircuitBreaker should stay open, before it switches to half open. (Default value is 60 seconds)

slowCallRateThreshold: defines a threshold in percentage. The CircuitBreaker considers a call as slow when the call duration is greater than slowCallDurationThreshold. (Default value is 100)

slowCallDurationThreshold: defines the duration threshold above which calls are considered as slow and increase the rate of slow calls. (Default value is 60)

failureRateThreshold: defines the failure rate threshold in percentage. If the failure rate is equal or greater than the threshold the CircuitBreaker transitions to open and starts short-circuiting calls. (Default value is 50)

More information on defining the Circuit Breakers we can find here.

Once defined, a circuit breaker could be used. I will use it in the following class:

MyController.java
package com.example.circuitbreaker;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.Arrays;

@RestController
public class MyController {

    Logger logger = LoggerFactory.getLogger(MyController.class);
    private static final String REMOTE_API_URL = "http://localhost:8010/call";

    @GetMapping("/cb-call")
    @CircuitBreaker(name = "MyCircuitBreaker1", fallbackMethod = "getAPIFallBack")
    public String getFromRemoteAPI() throws Exception{

        UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(REMOTE_API_URL);
        URI uri = builder.build().toUri();

        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        headers.set("my-token-1", "NO");
        headers.set("my-token-2", "some-value");

        HttpEntity<String> entity = new HttpEntity<String>(headers);

        RestTemplate restTemplate = new RestTemplate();

        HttpEntity<String> httpEntity = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);

        String response = httpEntity.getBody();

        return response;

    }

    public String  getAPIFallBack(Exception e){
        logger.error("getAPIFallBack::{}", e.getMessage());
        return "";
    }
}

When the external API call has issues, fallbackMethod = "getAPIFallBack" will run instead.

Here are the dependencies in pom.xml :

pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
    </dependency>
    <dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-openfeign</artifactId>
	</dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

The main class remains unchanged:

CircuitBreakerApplication.java
package com.example.circuitbreaker;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CircuitBreakerApplication {

	public static void main(String[] args) {
		SpringApplication.run(CircuitBreakerApplication.class, args);
	}
}

That is all !

Now, let's test the Circuit Breaker.

Test Case #1

  • ExternalApiApplication is running
  • CircuitBreakerApplication is running

In this case we can see that http://localhost:9090/cb-call call shows the same thing as http://localhost:8010/call (this is the normal behaviour of my implementation):

When we check http://localhost:9090/actuator/health we can see:

So the circuit is CLOSED and all looks good. The request are forwarded to ExternalApiApplication by the Circuit Breaker.

Test Case #2

  • ExternalApiApplication is NOT running
  • CircuitBreakerApplication is running

In this case, if there are no http://localhost:9090/cb-call calls, the Circuit Breaker will not change its state. However, at the 3rd call (we have minimumNumberOfCalls set to 2), we will see in the logs the following message:

getAPIFallBack::CircuitBreaker 'MyCircuitBreaker1' is OPEN and does not permit further calls

Now, the Actuator will give us the following information:

In this case, for a period of time, the Circuit Breaker will not send request to ExternalApiApplication.

Test Case #3

  • ExternalApiApplication is just restarted
  • CircuitBreakerApplication is running

In this case, if there are no http://localhost:9090/cb-call calls, the Circuit Breaker will not change its state.

After the first http://localhost:9090/cb-call call the status of the Circuit Breaker will be HALF_OPEN. In this status, the Circuit Breaker will permit only to some calls to reach the external API.

After a while, if the external calls are working well, the Circuit Breaker status becomes OPEN again.

Enjoy Circuit Breaker pattern !