Circuit Breaker Pattern in Spring Boot

Truong Bui
9 min readApr 18, 2023

--

Part of the Resilience4J Article Series: If you haven’t read my other articles yet, please refer to the following links:
1. MicroService Patterns: Rate Limiting with Spring Boot
2. MicroService Patterns: Retry with Spring Boot

Many of us are likely aware of the numerous advantages and benefits that a microservices architecture offers. In comparison to monolithic architecture, it significantly simplifies our lives as applications expand and become more complex.

Microservices architecture has grown in popularity in software development recently. However, it is essential to acknowledge that it does come with its own set of challenges. One such issue is cascading failures, wherein the failure of a single microservice can have an impact on others that depend on it, and a domino effect occurs causing a system-wide failure.

So, how can we prevent cascading failures? Circuit Breaker pattern is the answer.

What is Circuit Breaker Pattern?

The idea of this pattern is to prevent calling a microservice in case of abnormal behavior in that service (service down or timeout…). This idea can help the client’s side ignore handling failed requests and give the service some time to recover.

Consider the following scenario: Service A invokes Service B, but unfortunately, Service B is either unavailable or unable to respond. Consequently, Service A might wait for responses from Service B or handle the exceptions encountered. Subsequent requests directed at Service B will encounter similar challenges, leading to a bad user experience. In such cases, circuit breaker can help by stopping request sending for a specific time, waiting for the timeout ends, enable a limited number of requests to check whether Serice B is working. If those requests succeed, it allows microservices to continue normal operations. If not, it will again start the timeout.

Circuit Breaker has three states: Closed, Open, and Half_Open.

1. Closed

Closed is the initial state of circuit breaker. When microservices run and interact smoothly, circuit breaker is Closed. It keeps continuously monitoring the number of failures occurring within the configured time period. If the failure rate exceeds the specified threshold, Its state will change to Open state. If not, it will reset the failure count and timeout period.

2. Open

During Open state, circuit breaker will block the interacting flow between microservices. Request callings will fail, and exceptions will be thrown. Open state remains until the timeout ends, then change to Half_Open state.

3. Half_Open

In Half_Open state, circuit breaker will allow a limited of number requests to pass through. If the failure rate is greater than the specified threshold, it switches again to Open state. Otherwise, it is Closed state.

That sums up the concept of the circuit breaker pattern. Pretty simple, isn’t it? 😃 Moving forward, let’s dive into practical implementation of the circuit breaker pattern in Spring Boot.

Micro Services Demonstration

Our demonstration has 2 services named address-service and order-service.

Scenario

  • Before making a purchase, shoppers desire to review the details of their order. As a result, they send a request to the order-service.
  • Order-service utilizes a postal code to call address-service for shipping address details.
  • Upon receiving the shipping address details, order-service updates the order information and subsequently sends it back to the shopper.

Address service

Let’s build address-service first since it is a dependent service.

Model

@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "addresses")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String postalCode;
private String state;
private String city;
}

Repository

@Repository
public interface AddressRepository extends JpaRepository<Address, Integer> {
Optional<Address> findByPostalCode(String postalCode);
}

Service

public interface AddressService {
Address getAddressByPostalCode(String postalCode);
}
@Service
public class AddressServiceImpl implements AddressService {
@Autowired
private AddressRepository addressRepository;
public Address getAddressByPostalCode(String postalCode) {
return addressRepository.findByPostalCode(postalCode)
.orElseThrow(() -> new RuntimeException("Address Not Found: " + postalCode));
}
}

Controller

@RestController
@RequestMapping("addresses")
public class AddressController {
@Autowired
private AddressService addressService;
@GetMapping("/{postalCode}")
public Address getAddressByPostalCode(@PathVariable("postalCode") String postalCode) {
return addressService.getAddressByPostalCode(postalCode);
}
}

Data Setup

We might need some default data records for the database table.

A method annotated with @PostConstruct is what we need. Spring will call that method just after the initialization of bean properties and then populating data.

@Configuration
public class DataSetup {
@Autowired
private AddressRepository addressRepository;
@PostConstruct
public void setupData() {
addressRepository.saveAll(Arrays.asList(
Address.builder().id(1).postalCode("1000001").state("Tokyo").city("Chiyoda")
.build(),
Address.builder().id(2).postalCode("1100000").state("Tokyo").city("Taito").build(),
Address.builder().id(3).postalCode("2100001").state("Kanagawa").city("Kawasaki")
.build()));
}
}

Properties

server:
port: 9090
spring:
application:
name: address-service
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
datasource:
url: jdbc:h2:mem:address-db
username: admin
password: 1111
driverClassName: org.h2.Driver
h2:
console:
enabled: true

We finished building address-service. Run and access the link http://localhost:9090/addresses/1000001, the expected response should be as below

{
"id": 1,
"postalCode": "1000001",
"state": "Tokyo",
"city": "Chiyoda"
}

Order Service

When it comes to order-service, the most interesting part might be around configuring Circuit Breaker, and monitoring its status through Actuator. I will strive to provide a straightforward explanation, so let’s get started!

Model

public interface Type {
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "orders")
public class Order implements Type {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Integer id;
private String orderNumber;
private String postalCode;
private String shippingState;
private String shippingCity;
}
@Data
public class Failure implements Type {
private final String msg;
}

Repository

@Repository
public interface OrderRepository extends JpaRepository<Order, Integer> {
Optional<Order> findByOrderNumber(String orderNumber);
}

Service

Here is a place where every logic is written.

Logical operation order:

  • Retrieve order info from “orders” table.
  • Make a call to an external service (address-service) to get shipping address info.
  • Update order info with shipping address details then return.

The tricky part is how to call an external API. Fortunately, Spring has RestTemplate can help us do that.

RestTemplate is a central spring class used to consume the web services for all HTTP methods. ( Remember we need to create a RestTemplate Bean, see in Setup part below)

public interface OrderService {
Type getOrderByPostCode(String orderNumber);
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private RestTemplate restTemplate;
private static final String SERVICE_NAME = "order-service";
private static final String ADDRESS_SERVICE_URL = "http://localhost:9090/addresses/";
@CircuitBreaker(name = SERVICE_NAME, fallbackMethod = "fallbackMethod")
public Type getOrderByPostCode(String orderNumber) {
Order order = orderRepository.findByOrderNumber(orderNumber)
.orElseThrow(() -> new RuntimeException("Order Not Found: " + orderNumber));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<AddressDTO> entity = new HttpEntity<>(null, headers);
ResponseEntity<AddressDTO> response = restTemplate.exchange(
(ADDRESS_SERVICE_URL + order.getPostalCode()), HttpMethod.GET, entity,
AddressDTO.class);
AddressDTO addressDTO = response.getBody();
if (addressDTO != null) {
order.setShippingState(addressDTO.getState());
order.setShippingCity(addressDTO.getCity());
}
return order;
}

private Type fallbackMethod(Exception e) {
return new Failure("Address service is not responding properly");
}
}

As you noticed that we’re annotating the method with “@CircuitBreaker”. The attribute “name” is assigned as “order-service” which means every configuration of “order-service” instance is applied for this method. (you can see details configurations in Properties part below). Then we’re using “fallbackMethod” attribute as well with the purpose to call a backup method in case the dependent service (address-service) is not responding properly. We need to be careful that both methods should return the same data type. Now you might understand why I’m using “Type interface” for both model classes to implement.

Setup

@Configuration
public class RestConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Configuration
public class DataSetup {
@Autowired
private OrderRepository orderRepository;
@PostConstruct
public void setupData() {
orderRepository.saveAll(Arrays.asList(
Order.builder().id(1).orderNumber("0c70c0c2").postalCode("1000001").build(),
Order.builder().id(2).orderNumber("7f8f9f15").postalCode("1100000").build(),
Order.builder().id(3).orderNumber("394627b2").postalCode("2100001").build()));
}
}

Properties

server:
port: 1010
spring:
application:
name: order-service
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
datasource:
url: jdbc:h2:mem:order-db
username: root
password: 123
driverClassName: org.h2.Driver
h2:
console:
enabled: true
management:
endpoint:
health:
show-details: always
endpoints:
web:
exposure:
include: health
health:
circuitbreakers:
enabled: true
resilience4j:
circuitbreaker:
instances:
order-service:
sliding-window-type: COUNT_BASED
failure-rate-threshold: 50
minimum-number-of-calls: 5
automatic-transition-from-open-to-half-open-enabled: true
wait-duration-in-open-state: 5s
permitted-number-of-calls-in-half-open-state: 3
sliding-window-size: 10
register-health-indicator: true

Allow me to provide a brief introduction to resilience4j-circuitbreaker configurations.

  • sliding-window-type: The number of requests is recorded and aggregated in the last “sliding-window-size” seconds.
  • failure-rate-threshold: Trigger Circuit Breaker if at least “failure-rate-threshold” of requests have failed.
  • minimum-number-of-calls: Records at least “minimum-number-of-calls” requests in the last “sliding-window-size” seconds before calculating the failure rate.
  • automatic-transition-from-open-to-half-open-enabled: After ”wait-duration-in-open-state” seconds, Circuit Breaker will automatically transition from open to half-open state.
  • wait-duration-in-open-state: If triggered, wait at least “wait-duration-in-open-state” seconds before allowing more calls.
  • permitted-number-of-calls-in-half-open-state: After “wait-duration-in-open-state” time has passed, allow another “permitted-number-of-calls-in-half-open-state” requests and wait for them to calculate the failure rate again.
  • sliding-window-size: Record the result of the last “sliding-window-size” seconds.

We finished building order-service. Run sequentially address-service and then order-service, access to the link http://localhost:1010/orders?orderNumber=0c70c0c2, the response should be as below

{
"id": 1,
"orderNumber": "0c70c0c2",
"postalCode": "1000001",
"shippingState": "Tokyo",
"shippingCity": "Chiyoda"
}

In case address-service is not responding properly (service is down), we’ll get the response below.

{
"msg": "Address service is not responding properly"
}

Playing with Circuit Breaker

Both services are running already, access the link http://localhost:1010/actuator/health to view Circuit Breaker details. (let’s focus on 4 fields I commented)

{
"circuitBreakers": {
"status": "UP",
"details": {
"order-service": {
"status": "UP",
"details": {
"failureRate": "-1.0%", // Ratio of failedCalls to bufferedCalls (failedCalls/bufferedCalls) * 100
"failureRateThreshold": "50.0%",
"slowCallRate": "-1.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 0, // Total number of API calls from order-service to address-service
"slowCalls": 0,
"slowFailedCalls": 0,
"failedCalls": 0, // Number of failed API calls from order-service to address-service
"notPermittedCalls": 0,
"state": "CLOSED" // CircuitBreaker state
}
}
}
}
}

Call order-service API 2 times http://localhost:1010/orders?orderNumber=0c70c0c2, then refresh the actuator link http://localhost:1010/actuator/health, we will see the change.

{
"circuitBreakers": {
"status": "UP",
"details": {
"order-service": {
"status": "UP",
"details": {
"failureRate": "-1.0%",
"failureRateThreshold": "50.0%",
"slowCallRate": "-1.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 2, // Total number of API calls now is 2
"slowCalls": 0,
"slowFailedCalls": 0,
"failedCalls": 0,
"notPermittedCalls": 0,
"state": "CLOSED"
}
}
}
}
}

Turn down address-service, call order-service API 3 times http://localhost:1010/orders?orderNumber=0c70c0c2, then refresh the actuator link http://localhost:1010/actuator/health, we will notice now Circuit Breaker was triggered. the reason is that “failureRate” is now greater than “failure-rate-threshold”.

{
"circuitBreakers": {
"status": "UNKNOWN",
"details": {
"order-service": {
"status": "CIRCUIT_OPEN", // CircuitBreaker is triggered
"details": {
"failureRate": "60.0%", // this rate now is greater than "failureRateThreshold"
"failureRateThreshold": "50.0%",
"slowCallRate": "0.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 5, // Total number of API calls from order-service to address-service
"slowCalls": 0,
"slowFailedCalls": 0,
"failedCalls": 3, // Number of failed API calls from order-service to address-service
"notPermittedCalls": 0,
"state": "OPEN"
}
}
}
}
}

Wait for 5 seconds, refresh the actuator link http://localhost:1010/actuator/health, and we’ll see Circuit Breaker now is in HALF_OPEN state. Do you remember “wait-duration-in-open-state”? - The time that the Circuit Breaker should wait before transitioning from open to half-open)

{
"circuitBreakers": {
"status": "UNKNOWN",
"details": {
"order-service": {
"status": "CIRCUIT_HALF_OPEN",
"details": {
"failureRate": "-1.0%",
"failureRateThreshold": "50.0%",
"slowCallRate": "-1.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 0,
"slowCalls": 0,
"slowFailedCalls": 0,
"failedCalls": 0,
"notPermittedCalls": 0,
"state": "HALF_OPEN"
}
}
}
}
}

During HALF_OPEN state, it allows “permitted-number-of-calls-in-half-open-state” requests (We configured its value as 3), then calculates the failure rate again, If the failure rate is still greater than “failure-rate-threshold”, Circuit Breaker will be triggered again. Continue calling order-service API 3 times http://localhost:1010/orders?orderNumber=0c70c0c2, then refresh the actuator link http://localhost:1010/actuator/health. we have the below details.

{
"circuitBreakers": {
"status": "UNKNOWN",
"details": {
"order-service": {
"status": "CIRCUIT_OPEN", // // CircuitBreaker is triggered again
"details": {
"failureRate": "100.0%",
"failureRateThreshold": "50.0%",
"slowCallRate": "0.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 3,
"slowCalls": 0,
"slowFailedCalls": 0,
"failedCalls": 3,
"notPermittedCalls": 0,
"state": "OPEN"
}
}
}
}
}

Now run address-service, then continue calling order-service API 3 times http://localhost:1010/orders?orderNumber=0c70c0c2, refresh the actuator link http://localhost:1010/actuator/health. we’ll see Circuit Breaker was closed.

{
"circuitBreakers": {
"status": "UP",
"details": {
"order-service": {
"status": "UP",
"details": {
"failureRate": "-1.0%",
"failureRateThreshold": "50.0%",
"slowCallRate": "-1.0%",
"slowCallRateThreshold": "100.0%",
"bufferedCalls": 0,
"slowCalls": 0,
"slowFailedCalls": 0,
"failedCalls": 0,
"notPermittedCalls": 0,
"state": "CLOSED"
}
}
}
}
}

We have just explored the concept of the circuit breaker and conducted a brief demonstration to observe its behavior.

Hope you can find something useful!

You can find the details source code here: https://github.com/buingoctruong/circuit-breaker-pattern-spring-boot

Thank you for reading!

--

--