En la primera parte de este post vimos los casos y la razón por la cual podríamos requerir el uso del patrón Circuit Breaker. En esta segunda parte implementaremos un breaker sencillo en un microservicio.

El Escenario

Vamos a implementar una aplicación web muy simple que actuará como nuestro sistema, en el proyecto my-app. Esta aplicación mostrará una lista de posts de un blog, consultando a una dependencia o servicio externo que también construiremos en el proyecto my-external-dependency, de esta manera podremos simular un fallo en el servicio externo deteniendo el programa correspondiente.

Para efectos de la demostración, ambos proyectos se mantendrán muy simples, con la menor cantidad de código posible, por eso NO vamos a respetar el principio de separación de responsabilidades y veremos mezcladas algunas capas que en una aplicación real deben estar separadas, por ejemplo, controladores y servicios con lógica de negocio.

La Dependencia Externa

Para este proyecto crearemos un nuevo proyecto Maven y agregamos las dependencias de Spring Boot Web y la referencia al POM padre. Para crear el proyecto, la forma más fácil y rápida es utilizar la versión de Eclipse para Spring llamada «Spring Tool Suite» y su opción de menú File/New/Spring Starter Project. Otra opción igualmente fácil es utilizar el generador Spring Initializr. En cualquier caso, el POM debe quedar similar al siguiente:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.4.RELEASE</version>
  <relativePath/>
</parent>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
<dependencies>

Creamos una clase con nuestro controlador de un servicio REST y el código necesario para inicializar la aplicación como una aplicación de Spring Boot:

@SpringBootApplication
@Controller
public class MyExternalDependencyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyExternalDependencyApplication.class, args);
    }
    
    @RequestMapping("/posts")
    @ResponseBody
    public String[] getPosts(){
        return new String[]{
            "Protegiendo tu aplicación de fallos externos con el patrón Circuit Breaker (Parte 2)",
            "Protegiendo tu aplicación de fallos externos con el patrón Circuit Breaker (Parte 1)",
            "Consejos para sacar provecho a tu log (Parte 2)",
            "Consejos para sacar provecho a tu log (Parte 1)",    
        };
    }
}

Eso es todo para la dependencia externa. Al ejecutarlo podemos acceder en un navegador a la URL http://localhost:8080/posts para recibir la lista de posts que genera el servicio:

Si algo no te funciona o te queda claro, puedes clonar nuestro repositorio en Github para tener el código fuente completo de este post.

La Aplicación Principal

Esta será la aplicación a la que nuestro cliente accede y que invoca un llamado en el servicio externo que ya construimos. Para construir esta aplicación, debemos crear un proyecto Maven y le agregamos las mismas dependencias de nuestro servicio externo, agregando además Hystrix y Thymeleaf que es un procesador de plantillas HTML, así:

...
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
...

En esta versión de Spring Boot adicionalmente debemos incluir el POM padre de Spring Cloud y la versión de Spring Cloud:

<properties>
    <spring-cloud.version>Dalston.SR1</spring-cloud.version>
</properties>
...
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Observa o descarga el archivo POM completo aquí.

Para evitar conflictos con el puerto del servicio externo, debemos cambiar el puerto de nuestra aplicación. Para hacerlo, agrega la propiedad server.port en el archivo application.properties con un número de puerto diferente al 8080, así:

server.port=9090

Creamos un controlador y una vista HTML (plantilla de Thymeleaf) para mostrar la lista de posts. En un futuro post trataremos Thymeleaf con más detalle pues está fuera del alcance de este post:

@Controller
public class ListingController {
	
	@Autowired
	private PostsService postsService;

	@RequestMapping("/show-posts")
	public String showPosts(Model model){
		model.addAttribute("posts", postsService.getPosts());	
		return "posts-view";
	}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>Ejemplo de uso de Hystrix</title>
    </head>
    <body>
        
<h2>Lista de Posts</h2>

        
<ul>
            
<li th:each="p : ${posts}" th:text="${p}"></li>

        </ul>

    </body>
</html>

Finalmente creamos nuestra clase de servicio que será invocada por el controlador y esta a su vez, invocará al servicio externo utilizando Hystrix. Esta es la clase que debes observar con detenimiento y es el punto central de este post:

@Service
public class PostsService {
    @HystrixCommand(fallbackMethod="defaultPosts")
    public String[] getPosts() {
        return new RestTemplate().getForObject("http://localhost:8080/posts", String[].class);
    }

    public String[] defaultPosts(){
        return new String[]{"Post #1", "Post #2"};
    }
}

Vamos a analizar las líneas más importantes:

  • Línea 1: por ahora el circuit breaker de Hystrix sólo funciona con clases @Service o @Component
  • Línea 3: La anotación @HystrixCommand declara un circuit breaker para el método anotado. Opcionalmente, utilizando el parámetro fallbackMethod podemos declarar un método que será invocado como contingencia cuando falle el método anotado. En este caso nuestro método de contigencia es defaultPosts.
  • Línea 5: Es la invocación al servicio externo, aquí es donde se puede producir el fallo
  • Línea 8: Es el método que se invocará si falla el llamado al servicio externo

Finalmente debemos activar Hystrix en nuestra aplicación agrando la anotación @EnableCircuitBreaker en una clase de configuración:

@SpringBootApplication
@EnableCircuitBreaker
public class MyAppApplication {

	public static void main(String[] args) {
		SpringApplication.run(MyAppApplication.class, args);
	}
}

Ahora iniciamos tanto el servicio externo como la aplicación y nos dirigimos a la página principal en http://localhost:9090/show-posts. La aplicación invoca al servicio externo y deberíamos ver lo siguiente:

Ahora detén el servicio externo pero deja en ejecución la aplicación  y refresca la página que estás viendo, deberías recibir algo así:

Si te fijas bien, estás viendo la respuesta del método defaultPosts en lugar de la respuesta del servicio. Esto indica que falló el llamado al servicio externo y se ejecutó el método de contingencia como era de esperarse.

Cada vez que falle el llamado al servicio externo, se ejecutará el método de contingencia hasta que se alcance un límite (configurable) y empezarás a ver un mensaje en el log como este:
java.lang.RuntimeException: Hystrix circuit short-circuited and is OPEN. Si inicias nuevamente el servicio externo, pasarán 5 segundos (también configurable) desde la última falla hasta que el circuit breaker pase al estado semi-abierto y permita el llamado al servicio externo, volviendo todo a la normalidad.

Si quieres ver como configurar estos y otros valores, descarga el proyecto de nuestro repositorio y consulta guía de configuración de Hystrix.

Bonus: Dashboard de Hystrix

Spring Boot nos ofrece también un sencillo dashboard para ver el estado de nuestros breakers. Para configurarlo, agrega las siguientes dependencias al POM de la aplicación principal:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId
</dependency>

Para activarlo, agrega la anotación @EnableHystrixDashboard a una clase de configuración, reinicia la aplicación principal y ve a http://localhost:9090/hystrix e ingresa como valor a monitorear, el stream de histrix de nuestra aplicación que está en http://localhost:9090/hystrix.stream y listo!, veras algo similar a esto:

Conclusión

Los puntos de integración con sistemas externos siempre tendrán la más alta probabilidad de producir un fallo en nuestra aplicación y debemos protegerla para evitar que esos fallos la arrastren consigo.

En caso de fallo de una dependencia externa, debemos procurar una degradación sútil de las funcionalidades para lo cual podemos utilizar el patrón Circuit Breaker mediante la librería Hystrix.

Hystrix tiene muchas más opciones interesantes que vale la pena explorar, como el dashboard, el stream de eventos para monitoreo (Turbine Stream) y los collapsers, que te ayudan a agrupar peticiones múltiples y similares a sistemas externo, por ejemplo cuando el usuario refresca repetidamente una página web, para así reducir el número de peticiones que se hacen a dicho sistema externo.