Using Spring Retry to consume REST Services

User Rating: 5 / 5

Star ActiveStar ActiveStar ActiveStar ActiveStar Active
 

Spring-retry is one of the many side-projects of Spring: the famous dependency injection framework. This library let us automatically re-invoke a method, moreover this operation it’s trasparent to the rest of our application. In this post I will try to illustrate the main features of this API, using a simple example.

Setup

To add Spring-retry to your project, you just need to add the following dependency to pom.xml file:

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.1.2.RELEASE</version>
</dependency>

Spring Retry Use case

Our purpose is to obtain the current Euro/Dollar exchange rate consuming a REST service. If the server responds with a HTTP code 503, we will relaunch the method unitil the server responds with a 200 code. Here is the service implementation:

@Path("/fetchRate")
public class ChangeService {

	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public Response getChange(@QueryParam("from") String from,
			@QueryParam("to") String to) {
		Random randomGenerator = new Random();
		int randomInt = randomGenerator.nextInt(2);

		if (from.equals("EUR") && to.equals("USD")) {
			if (randomInt == 1)
				return Response.status(200).entity("1.10").build();
			else
				return Response.status(503)
						.entity("Service temporarily not available").build();
		} 
		return Response.status(500).entity("Currency not available").build();

	}

}

 As you can see, in order to simulate a service which is temporarily unavailable we are using a Random generator which returns a 503 error on 50% of the invocations.

@Retryable

The “heart” of spring-retry is the @Retryable annotation. With the maxAttempts attribute we will set how many times the method should be invoked. With the @Backoff annotation we will configure an initial delay in milliseconds; this delay will increase of a 50% factor for every iteration. Let’s have a look at the full code of the REST client to understand how this annotation works.

public class RealExchangeRateCalculator implements ExchangeRateCalculator {
	
	private static final double BASE_EXCHANGE_RATE = 1.09;
	private int attempts = 0;
	private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
	
	@Retryable(maxAttempts=10,value=RuntimeException.class,backoff = @Backoff(delay = 10000,multiplier=2))
	public Double getCurrentRate() {
		
		System.out.println("Calculating - Attempt " + attempts + " at " + sdf.format(new Date()));
		attempts++;
		
		try {
			HttpResponse<JsonNode> response = Unirest.get("http://rate-exchange.herokuapp.com/fetchRate")
				.queryString("from", "EUR")
				.queryString("to","USD")
				.asJson();
			
			switch (response.getStatus()) {
			case 200:
				return response.getBody().getObject().getDouble("Rate");
			case 503:
				throw new RuntimeException("Server Response: " + response.getStatus());
			default:
				throw new IllegalStateException("Server not ready");
			}
		} catch (UnirestException e) {
			throw new RuntimeException(e);
		}
	}
	
	@Recover
	public Double recover(RuntimeException e){
		System.out.println("Recovering - returning safe value");
		return BASE_EXCHANGE_RATE;
	}

}

In case of a RuntimeException, the getCurrentRate method will be automatically reinvoked 10 times. If during the last execution the method fails again, the method annotated with @Recover will be launched. If there’s no recover method, the exception it’s simply throwed up.

XML Configuration

All of our configuration are made via annotations. Spring-retry, just like its greater brother Spring Framework can use the XML configuration. In this case we use the aspect-oriented programming, adding an Interceptor to our beans with this configuration:

<aop:config>
    <aop:pointcut id="retryable" expression="execution(* it..*RateCalculator.getCurrentRate(..))" />
    <aop:advisor pointcut-ref="retryable" advice-ref="retryAdvice" order="-1"/>
</aop:config>

<bean id="retryAdvice" class="org.springframework.batch.retry.interceptor.RetryOperationsInterceptor"/>

Conclusions

I think that spring-retry should be a must-have in your application if you have to interact with external resources. I think that the most important feature it’s that this API leaves the rest of your application intact. If you’re interested you can explore the code of project on Github. See you soon!

Author: Francesco Strazzullo works as front-end developer / JSF expert at e-xtrategy. if you are interested in Primefaces, he has has developed his own PrimeFaces library called MaterialPrime.

 

Follow us on Twitter