Implementing Reslience in Microservices using Java

Introduction

Welcome back! In this part, we will talk about part 3 of Microservices in Java, make sure to check part 1 and part 2, let’s dive into it.

In typical microservice architectures, where services are interconnected and continuously communicating, ensuring resilience in Microservices architectures is crucial. However, it’s challenging to guarantee consistent responses from all services. What if a service is down or times out? This could significantly impact business operations and lead to customer complaints. In this article, we will delve into implementing resilience in microservices using Java, focusing on enhancing the robustness and efficiency of your microservice calls, which is essential for maintaining smooth business operations.

Resilience

Resilience refers to the ability of a system to handle and recover from failures gracefully, without affecting the user experience or causing cascading failures throughout the system.

Why is resilience so important?
  • Financial Loss: Application downtime or erratic behavior can lead to direct financial loss for businesses.
  • Customer Trust: Unreliable services can erode customer trust and satisfaction.
  • Brand Reputation: Repeated failures or prolonged downtime can damage a brand’s reputation.
  • User Experience: Resilience directly impacts the overall user experience and satisfaction.
  • Competitive Advantage: Resilient services can differentiate businesses in competitive markets.
Implementing resilience patterns is crucial for several reasons:
  • Enhanced User Experience
  • Increased System Availability
  • Improved Scalability and Performance
  • Simplified Error Handling

 

In this article, we will explore Java examples to improve the resilience and efficiency of your microservice calls.

 

Implementing the Circuit Breaker Pattern in Java and Spring

Resilience patterns (Circuit Breaker) are design techniques that help build fault-tolerant and resilient microservices. They provide strategies to handle failures, prevent cascading failures, and ensure the system remains operational and responsive, even when individual services encounter issues.

 

The Problem
Consider a service A that makes requests to service B. If service B starts failing, service A keeps retrying until requests start piling up and resources are overwhelmed. This can lead to cascading failures that take down service A as well.
We need a way to fail fast and avoid overwhelming a failing service, let’s figure out the solution.

 

The Solution
The Circuit Breaker pattern acts like an electrical circuit breaker for systems. It detects issues, monitors failure rates, and switches to the Open state after a threshold, denying further requests to allow the downstream system’s recovery. It periodically allows test requests to determine issue resolution before closing again.

Circket Breaker

 

As shown in the above diagram, the Circuit Breaker Pattern is a technique used to prevent system failures. Here’s how it works:

  1. Closed state: normal system operation.
  2. Open state: The system stops accepting requests to prevent more issues.
  3. Half-open state: The system partially reopens to test if the issue is resolved.
  4. Return to Closed: The system resumes normal operation if the test succeeds.
  5. Remain open: if the test fails, the system continues to cool down before testing again.

This pattern prevents the system from overloading by managing the flow of requests during errors or high traffic.

Pros
  • fails fast to prevent cascading failures
  • Lets failing dependencies recover
Cons
  • Adds overhead and complexity
  • Requires tuning failure thresholds

 

When to Use It
  1. When a service depends on a remote service it may experience failures or high latency.
  2. When the remote service is not critical of the application’s core functionality, a fallback response is acceptable.
  3. When the system needs to maintain stability and prevent cascading failures.

 

So far we have a good understanding of the issue and how to fix it, now let’s see this in action using spring.

Implementing with Spring 3.2

In the following example, we will demonstrate how the order service processes orders by verifying the availability of the product and quantity in the inventory service. Both services are accessible in the GitHub repository. (inventory-service, order-service).

Note: If you want to see how it works, stop the inventory service and call the order service. After that, the fallback method will be executed.

1- Add the required dependency

<dependency>
    <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
   </dependency>

 

2- Spring Framework provides an easy way to add Circuit Breakers using the `@CircuitBreaker` annotation:
    @CircuitBreaker(name = "inventoryService", fallbackMethod = "fallbackCheckInventory")
    public void processOrder(Long productId, long quantity) {
        
        System.out.println("processOrder -- calling the inventoryService with productId: "+productId + ", and quantity: "+quantity);
        Boolean isProductAvailable = inventoryClient.checkProductAvailability(productId);

        if (!isProductAvailable) {
            throw new ProductAvailabilityException("Product:"+ productId+" unavailable");
        }

    }

 

In the above example, the @CircuitBreaker annotation wraps the call checkProductAvailability inside a Circuit Breaker named fallbackCheckInventory handles exceptions by returning a fallback response.

public void fallbackCheckInventory(Long productId, long quantity, Throwable throwable) {

     System.out.println("Fallback logic if Inventory Service fails for productId "
                + productId + " and quantity : "+quantity+ ". Error: " + throwable.getMessage());
    }

The output
Happy scenario:
When bother services are running (Order service and Inventory service) we will get the response back normally from the Inventory service while executing this API in the postman

curl --location 'http://localhost:8080/api/orders' \
--header 'Content-Type: application/json' \
--data '{
    "productId" : 2922,
    "quantity": 2
}'

Order Service logs:

processOrder -- calling the inventoryService with productId: 2922, and quantity: 2
Order processed successfully

Inventory Service Logs

inventory service --- product id: 2922

Now, let’s assume that the Inventory service is down (we will shut it down). The Order service depends on the inventory service for processing the order. What will happen now? Will the order crash? Let’s review the logs from both services

Order Service logs.

processOrder -- calling the inventoryService with productId: 2922, and quantity: 2
Fallback logic if Inventory Service fails for productId 2922 and quantity : 2. Error: Connection refused executing GET http://localhost:8081/api/inventory/check/2922
Order processed successfully

The logs above indicate that the order did not fail even though the inventory service was down. Instead, it intelligently executed the fallback method and proceeded with the order. This is just an example, and the appropriate response to failure depends on your specific requirements.

Inventory Service logs

There are no logs because it is already down 🙂

Conclusion

Resilience is a critical aspect of building robust and reliable microservice architectures. By implementing resilience patterns like the Circuit Breaker, you can improve the stability, fault tolerance, and overall user experience of your system.

The Spring Framework provides excellent support for implementing the Circuit Breaker pattern through the@CircuitBreaker annotation and integration with the Resilience4j library. By following the steps outlined in this article, you can easily incorporate Circuit Breakers into your Spring-based microservices and enhance their resilience.

Remember, while the Circuit Breaker pattern is a powerful tool, it is just one of many resilience patterns available. It’s important to consider other patterns like Retry, Fallback, Bulkhead, and Rate Limiting based on your specific requirements and the characteristics of your system.

By embracing resilience and implementing appropriate patterns, you can build microservices that are capable of handling failures gracefully, maintaining system availability, and providing a seamless user experience even in the face of adversity.

Share This Article

Reddit
LinkedIn
Twitter
Facebook
Telegram
Mezo Code

Mezo Code

Welcome to my technical blog, where I strive to simplify the complexities of technology and provide practical insights. Join me on this knowledge-sharing adventure as we unravel the mysteries of the digital realm together.

All Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

Latest Post
Kubernetes Developer Cheat Sheet

Kubernetes Developer Cheat Sheet

This cheat sheet covers the most frequently used kubectl commands that every developer working with Kubernetes should know. 1. Cluster Information kubectl version Displays the

Read More »