How to handle Fault Tolerance in your Enterprise applications and Microservices

Microprofile fault tolerance specification is a way for your application to handle the unavailability of a service by using a set of different policies. In this tutorial we will see how we can apply these policies in an application that can be run in Thorntail runtime.

how to handle fault tolerance in your enterprise applications and microservices

Thorntail is a Microprofile compliant environment, therefore you can combine the power of Enterprise API with the enhancement provided by Microprofile. Let’s see which policies can be used to improve the resiliency of your applications by using the FaultTolerance specification available in Microprofile:

Timeout

By using the @Timeout annotation, you can specify the maximum time (in ms) allowed to return a response in a method. Here is an example:

@GET
@Path("/json")
@Produces(MediaType.APPLICATION_JSON)
@Timeout(250)
public SimpleProperty getPropertyJSON () 
{
    SimpleProperty p = new SimpleProperty("key","value");
    randomSleep();
	return p;
}

private void randomSleep() {
 	try {  
      Thread.sleep(new Random().nextInt(500));
    }
    catch (Exception exc) {
      exc.printStackTrace();
    }	
}

You can further specify a @Fallback policy which states which fallback method should be specified in case of failure:

@GET
@Path("/json")
@Produces(MediaType.APPLICATION_JSON)
@Timeout(250)
@Fallback(fallbackMethod = "fallbackJSON")
public SimpleProperty getPropertyJSON () 
{
    SimpleProperty p = new SimpleProperty("key","value");
    randomSleep();
	return p;
}
public SimpleProperty fallbackJSON() 
{
    SimpleProperty p = new SimpleProperty("key","fallback");
	return p;
}

Retry

The retry annotation is essential to deal with unstable services. MicroProfile uses @Retry to specify the retry when a certain Exception is raised. You can allow retrying for a certain number of times before cancelling the retry. In this example we allow up to 4 retries when the RuntimeException is thrown:

@GET
@Path("/text")
@Retry(maxRetries = 4, retryOn = RuntimeException.class)
public String getHello () 
{
	 if (new Random().nextFloat() < 0.5f) {
        System.out.println("Error!!!");
        throw new RuntimeException("Resource failure.");
    }
	return "hello world!";
}

Circuit Breaker

Circuit Breaker is a key pattern for creating resilient Microservices. It can be used to prevent repeatable timeout Exceptions by instantly rejecting the requests. MicroProfile Fault Tolerance uses @CircuitBreaker to control the client calls.

The software circuit breaker works much like an electrical circuit breaker therefore it can be in three states:

 

how to handle fault tolerance in your enterprise applications and microservices

 

  • Closed state: A closed circuit represents a fully functional system, which is available to its clients
  • Half open circuit: When some failures are detected the state can change to half-open. In this state, it checks whether the failed component is restored. If so, it closes the circuit. otherwise, it moves to the Open state
  • Open state. An open state means the service is temporarly disabled. You can specify how much time after checks are done to verify if it’s safe to switch to half-open state.

Here is an example:

@GET
@Path("/xml")
@Produces(MediaType.APPLICATION_XML)
@CircuitBreaker(successThreshold = 5, requestVolumeThreshold = 4, failureRatio=0.75, delay = 1000)
public SimpleProperty getPropertyXML () 
{
	SimpleProperty p = buildPOJO();      

	return p;
}

private SimpleProperty buildPOJO() {
 	if (new Random().nextFloat() < 0.5f) {
        System.out.println("Error!!!");
        throw new RuntimeException("Resource failure.");
    }
    SimpleProperty p = new SimpleProperty("key","value");
    return p;
}

The above code-snippet means the method getPropertyXML applies the CircuitBreaker policy. For the last 4 invocations, if 75% failed then open the circuit. The circuit will stay open for 1000ms and then back to half open. When a circuit is open, A CircuitBreakerOpenException will be thrown instead of actually invoking the method.

org.jboss.resteasy.spi.UnhandledException: org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException: getPropertyXML
	at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:78)
	at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:222)

Here is a full code example:

package com.itbuzzpress.microprofile.service;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.util.Random;

import com.itbuzzpress.microprofile.model.SimpleProperty;
import org.eclipse.microprofile.faulttolerance.Retry;
import org.eclipse.microprofile.faulttolerance.Timeout;
import org.eclipse.microprofile.faulttolerance.Fallback;
import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
@Path("/simple")
public class SimpleRESTService {
	@GET
	@Path("/text")
	@Retry(maxRetries = 4, retryOn = RuntimeException.class)
	public String getHello () 
	{
		 if (new Random().nextFloat() < 0.5f) {
            System.out.println("Error!!!");
            throw new RuntimeException("Resource failure.");
        }
		return "hello world!";
	}

	@GET
	@Path("/json")
	@Produces(MediaType.APPLICATION_JSON)
	@Timeout(250)
    @Fallback(fallbackMethod = "fallbackJSON")
	public SimpleProperty getPropertyJSON () 
	{
        SimpleProperty p = new SimpleProperty("key","value");
        randomSleep();
		return p;
	}


	public SimpleProperty fallbackJSON() 
	{
        SimpleProperty p = new SimpleProperty("key","fallback");
		return p;
	}

	@GET
	@Path("/xml")
	@Produces(MediaType.APPLICATION_XML)
	@CircuitBreaker(successThreshold = 5, requestVolumeThreshold = 4, failureRatio=0.75, delay = 1000)
	public SimpleProperty getPropertyXML () 
	{
		SimpleProperty p = buildPOJO();      
	
		return p;
	}

	 private void randomSleep() {
	 	try {  
          Thread.sleep(new Random().nextInt(500));
        }
        catch (Exception exc) {
          exc.printStackTrace();
        }	
  
    }

    private SimpleProperty buildPOJO() {
	 	if (new Random().nextFloat() < 0.5f) {
            System.out.println("Error!!!");
            throw new RuntimeException("Resource failure.");
        }
        SimpleProperty p = new SimpleProperty("key","value");
        return p;
    }

}

In order to compile and run the above example, the microprofile-fault-tolerance fraction needs to be included in your pom.xml, plus the other fractions needed such as jaxrs and cdi,:

<dependency>
    <groupId>io.thorntail</groupId>
    <artifactId>jaxrs</artifactId>
</dependency>
<dependency>
    <groupId>io.thorntail</groupId>
    <artifactId>cdi</artifactId>
</dependency>
<dependency>
  <groupId>io.thorntail</groupId>
  <artifactId>microprofile-fault-tolerance</artifactId>
</dependency>