Saturday, March 7, 2020

Spring Retry Example


In this Spring boot tutorial, learn how we can build applications using spring retry module facility where we have to call some methods where exception is sometimes expected and we have to retry the request.
In such cases, if we have to implement any retry functionality on any backend service call, generally we implement using loop and a break condition and we keep on retrying to certain retry limits. But this solution is error prone.
Spring has provided us one simple framework around this called spring-retry which can be configured using annotations. We can define the retry limits, fallback method etc.
Table of Contents

1. Why Why we need to retry?
2. Spring retry
3. Spring retry example
4. Test the application
5. Summary

1. Why we need to retry?

In most our projects we usually have some scenario to retry few operation if it falls first time. For example let’s say the during invoking any backend external service, that service might be down for few reasons like network outage, server down, network glitch, deadlock etc. In this case usually we try to retry the operation for few times before we send any specific error to the client programs to make processing more robust and less prone to failure.
Sometimes it helps to automatically retry a failed operation in case it might succeed on a subsequent attempt. I guess all of you have faced this and as workaround, you most probably solved this by having a loop and break that loop once you reached the retry limit, but now with the help of spring-retry module, we don’t have to write such code to handle the retry scenario.

2. Spring retry

Spring-retry from spring family is another utility module which can help us in handle the retry of any specific operation with standard fashion. In spring-retry all configurations are simple annotation based.

2.1. Spring retry annotations

  • @EnableRetry – to enable spring retry in spring boot project
  • @Retryable – to indicate any method to be a candidate of retry
  • @Recover – to specify fallback method!

3. Spring retry example

3.1. Development environment

We will use the following technology stack to try out spring-retry in pur sample application below.
  • Java, Eclipse, Maven as Development Environment
  • Spring-boot as application framework
  • spring-retry module as retry module
  • AspectJ as dependency of spring-retry

3.2. Demo overview

  1. Create one Spring boot project to expose once sample Rest API which will call one backend operation which is prone to failure, We will simulate this failure conditions to initiate the retry.
  2. One service class which will actually invoke the remote api and this will send exception in case of failure, we will design the retry based on this custom exception, like once we receive this exception, we will retry for 3 times and finally return to client.
    In those 3 attempts, if we get success response from backend service then that success response will be returned else a standard fallback method will be called.

3.3. Create Spring-boot project

As a first step we will create a spring boot project from spring initializer site where we will test the spring-retry functionality.
To do this we need to go to https://start.spring.io/ and select dependencies web and retry. Download the zip file containing the skeleton project and import to maven.


Spring Boot Initializer

3.4. Maven dependencies

Spring initializer automatically add spring-boot-starter-data-rest and spring-boot-starter-security dependencies in the project. For the shake of our testing, we will not require those, so we will delete those two dependencies from the pom.xml.
Additionally, spring-retry depends on Aspectj which is not included in the skeleton project, so we will add below dependency in the pom.xml file.
pom.xml
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>${version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>${version}</version>
</dependency>

3.5. Create rest api endpoint

Create one sample Rest controller which will call the backend service class where we will simulate the exception and spring-retry module will automatically retry.
MyRestController.java
package com.example.springretry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyRestController {
    @Autowired
    BackendAdapter backendAdapter;
    @GetMapping("/retry")
    @ExceptionHandler({ Exception.class })
    public String validateSPringRetryCapability(@RequestParam(required = false) boolean simulateretry,
                                @RequestParam(required = false) boolean simulateretryfallback)
    {
        System.out.println("===============================");
        System.out.println("Inside RestController mathod..");
        return backendAdapter.getBackendResponse(simulateretry, simulateretryfallback);
    }
}
In the Rest Api we will add two optional request parameters.
  • simulateretry – parameter to simulate the exception scenario, so that spring can retry.
  • simulateretryfallback – as we are simulating the exception, after retry certain times we can either expect a successful backend call or all retry falls. In this case we will go to the fall back method to get hard-coded/error response. Now this parameter will ensure all the retry will fail and we will go to fall back path only.

3.6. @EnableRetry annotation

To enable spring-retry we need to put one annotation in the Spring Boot Application class. So open SpringRetryApplication class and add @EnableRetry in class level.
SpringRetryApplication.java
package com.example.springretry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;
@EnableRetry
@SpringBootApplication
public class SpringRetryApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringRetryApplication.class, args);
    }
}

3.7. Backend Service Adapter interface and implementation

Now we will create one interface/implementation for calling the external service. Here we will not actually call any external service call, rather will simulate the success/failure scenarios by adding some random logic, as below.
BackendAdapter.java
package com.example.springretry;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
public interface BackendAdapter {
    @Retryable(value = { RemoteServiceNotAvailableException.class }, maxAttempts = 3, backoff = @Backoff(delay = 1000))
    public String getBackendResponse(boolean simulateretry, boolean simulateretryfallback);
    @Recover
    public String getBackendResponseFallback(RuntimeException e);
}
BackendAdapterImpl.java
package com.example.springretry;
import java.util.Random;
import org.springframework.stereotype.Service;
@Service
public class BackendAdapterImpl implements BackendAdapter {
    @Override
    public String getBackendResponse(boolean simulateretry, boolean simulateretryfallback) {
        if (simulateretry) {
            System.out.println("Simulateretry is true, so try to simulate exception scenario.");
            if (simulateretryfallback) {
                throw new RemoteServiceNotAvailableException(
                        "Don't worry!! Just Simulated for Spring-retry..Must fallback as all retry will get exception!!!");
            }
            int random = new Random().nextInt(4);
            System.out.println("Random Number : " + random);
            if (random % 2 == 0) {
                throw new RemoteServiceNotAvailableException("Don't worry!! Just Simulated for Spring-retry..");
            }
        }
        return "Hello from Remote Backend!!!";
    }
    @Override
    public String getBackendResponseFallback(RuntimeException e) {
        System.out.println("All retries completed, so Fallback method called!!!");
        return "All retries completed, so Fallback method called!!!";
    }
}
  • @Retryable – This is the main annotation after @EnableRetry. This annotation tells that if we get RemoteServiceNotAvailableException from the method then retry maximum 3 times before sending the response. Also we are introducing delay of 1 second in each retry.
  • @Recover – in the fallback method indicates that if we don’t get any success response after 3 retry, response will come from this fallback method. Make sure you pass expected exception as parameter, else spring will have hard time finding the exact method.
  • In the actual method from where the Remote service will be invoked, we have added some custom logic to control the Exception based on simulateretry and simulateretryfallback parameters. The code is simple, just returning the expected exception for retry if the conditions are met, else we will return the success response. Also we have added some random logic based on the Random number to mimic the randomness of the failure.
  • The fallback method implementation sends simple fallback response.

4. Test the application

The testing section is pretty straightforward. We will pass proper parameter in the REST request to simulate the retry requests.

4.1. Test retry – success or fallback

Let’s start with 'http://localhost:8080/retry?simulateretry=true&simulateretryfallback=false' in browser. Based on the parameter, we are expecting exception in the backend service call and at the same time as simulateretryfallback=false, we are depending on the random logic (random % 2 == 0 –> even random number) we can expect a success response while retry.
So once we hit the request in browser, we might get exception in backend and spring will retry the same method multiple times. The outcome could be the Success response from backend. Here are the few lines of log from one of my request where spring is trying retry.
Console
===============================
Inside RestController method..
Simulateretry is true, so try to simulate exception scenario.
Random Number : 1
===============================
Inside RestController mathod..
Simulateretry is true, so try to simulate exception scenario.
Random Number : 2
Simulateretry is true, so try to simulate exception scenario.
Random Number : 2
Simulateretry is true, so try to simulate exception scenario.
Random Number : 0
All retries completed, so Fallback method called!!!
First time I found success and second time I have gone to fallback path.

4.2. Test retry – fallback only

Now try with 'http://localhost:8080/retry?simulateretry=true&simulateretryfallback=true', you will get fallback response after retry limit as from code everytime we are throwing RuntimeException. Here are my last few lines of code, it tried 3 times before sending the response.
Console
===============================
Inside RestController method..
Simulateretry is true, so try to simulate exception scenario.
Simulateretry is true, so try to simulate exception scenario.
Simulateretry is true, so try to simulate exception scenario.
All retries completed, so Fallback method called!!!

5. Spring retry summary

So we have seen how easily we can use spring retry module for implementing retry based on Exception. So next time if you need this kind of requirements, you can use this approach. Comment below if you have any problem understanding this.

Reference :

Previous Post
Next Post

0 komentar: