Implementar Circuit Breaker con Laravel

De la gran cantidad de patrones que existen, quiero mostrar cómo utilizar el patrón Circuit Breaker, este amigo que está concebido para ayudarnos en los momentos difíciles, si, si, me refiero a ese domingo por la tarde que estas con tu familia viendo juntos vuestra serie favorita de “Netflix” y salta la típica incidencia de que se ha caído el sistema, un microservicio o simplemente que algo ha dejado de ir bien, ese que sabes que es no es muy importante por ser domingo, pero que, a pasar de eso, no te deja la conciencia tranquila y te pones a revisar que ha ocurrido. En esos momentos piensas, que bien que invertí tiempo en implementar Circuit Breaker, revisas que todo sigue funcionado y vuelves con tu familia.

Esta es la tranquilidad que nos proporciona un Circuit Breaker, vigila que todo funciona según lo esperado incluso si hay errores en el sistema.

¿Qué es Circuit Breaker?

En el mundillo de la electricidad, un Circuit Breaker o disyuntor, es un mecanismo automático que es capaz de abrir y cerrar un circuito eléctrico en determinadas circunstancias, por ejemplo, el diferencial del cuadro eléctrico de nuestra casa cuando excede la potencia que tenemos contratada se dispara abriendo el circuito y dejándonos si electricidad. En el mundillo del software, es algo similar, ponemos determinadas condiciones que se deben de verificar en cada proceso, controlando que todo es correcto y que todo funciona correctamente y en caso de error realizamos las acciones correctoras que creamos oportunas. Esto tiene grandes ventajas, la que yo creo que es mas importante, es que, si ya sabemos que tenemos un error, ¿porque no tomamos medidas desde el primer momento?, es decir, cuando realizamos el análisis de un proceso contamos con que puede fallar y normalmente realizamos acciones como, notificar por correo electrónico a soporte o si somos mas precavidos y queremos la alerta instantánea la publicamos en un grupo de Slack o alguna otra cosa similar, aquí es donde entra nuestro Circuit Breaker, cuando detectamos el error abrimos el circuito, ganando en tiempo de respuesta ya que las siguientes peticiones al proceso se encontraran el circuito abierto y no volverán a generar error, pongamos un ejemplo, tenemos un microservicio que se encarga de enviar mensajes de texto a través de sms, para ello utilizamos un endpoint que nos proporciona nuestro proveedor de telefonía, aquí entra a la partida un proveedor externo, algo que no podemos garantizar que no falle, por lo que si intentamos enviar un mensaje de texto y el proveedor está caído nosotros nos remitimos un mensaje por slack o cualquier otro sistema, advirtiendo de que hay un fallo, el tiempo a partir de este momento y hasta que el proveedor de telefonía este totalmente operativo otra vez es crucial y nuestro sistema puede ser tan complejo que no podamos detener nuestro envió de mensajería por lo que cada nueva petición sabemos que va ha fallar y no podemos parar de generar errores. Con Circuit Breaker no podemos hacer que el proveedor de telefonía no falle, pero si que podemos preguntarle al circuito si la petición anterior tubo un error, si la hubo, entonces el circuito estará abierto por lo que no realizaremos la nueva petición al proveedor de telefonía, en esta situación podemos meter el mensaje en una cola de envío para enviarlo cuando el proveedor ya esté totalmente operativo. En caso de que no haya error, el circuito estará cerrado por lo que, si se generara un error en la petición, podremos notificar el error a soporte o slack como veníamos haciendo y además abrir el circuito para que la siguiente petición no genere error. Con un envío consecutivo de mensajes y suponiendo que un mensaje en condiciones normales tarda un segundo, si tenemos un error en la petición el tiempo de respuesta hasta notificar el error puede incrementarse de forma exponencial, por lo que, consideremos que donde un funcionamiento normal tarda un segundo, en condiciones de error puede tardar tres segundos, esto por cientos de peticiones puede hacer que es microservicio de envío llegue a bloquearse.

Así que, viendo las ventajas que nos proporciona, vamos a implementarlo.

Implementación en Laravel

La implantación del patrón Circuit Breaker es muy sencilla, o por lo menos eso quiero mostrar en este ejemplo, vamos a ir a lo sencillo, descargaremos el código de un ejemplo del siguiente repositorio “https://github.com/dmorenotoribio/operations” y seguiremos unos cuantos pasos para ponerlo en marcha. Para todo aquel quiera profundizar más, lo animo a que investigue en la carpeta “Circuit”.

1. Lo primero, es que, nos creemos nuestro propio proyecto, ya que a mi, por lo menos, no me gusta tener que meter en el composer de laravel referencias de otros proyectos ya que esto no me ayuda a comprender que está haciendo el código, por eso, creamos nuestro proyecto y después copiamos la carpeta “Circuit” dentro de la carpeta de la aplicación “App”. Para crear el proyecto, debemos de utilizar la versión 5.6 de laravel, ya que en versiones superiores no esta probado y puede que no funcione correctamente.

composer create-project — prefer-dist laravel/laravel=”5.6.*” operations

2. Una vez tenemos el proyecto creado y la carpeta copiada, meteremos la referencia a esta carpeta para que el service provider de laravel nos genere las referencias necesarias a las clases. Vamos a la clase situada en “config/app.php” en la parte de abajo donde ser registran los providers insertamos esta línea.

App\Circuit\Provider\CircuitBreakerServiceProvider::class,

3. Con esto ya hecho, en la carpeta “config“ del proyecto, creamos una clase de configuración la cual llamaremos “circuit_breaker.php”. Esta clase es muy importante ya que contiene la configuración de los tiempos de espera y los intentos que va a manejar el circuito.

La clave “defaults” contiene los valores por defecto que asignamos al circuito, la clave “services” contiene cada uno de nuestros servicios de forma independiente de manera que podemos tener varios servicios con intentos de fallo y tiempos de corte diferentes. Los valores a tener en cuenta son los siguientes :

attempts_threshold : En esta clave, especificamos cuantos intentos se tienen que hacer antes de abrir el circuito y declarar que el servicio a fallado.

attempts_ttl : En esta clave, especificamos la ventana de tiempo (en minutos) en la que estamos validando la acción y realizando los intentos antes de declarar que el servicio a fallado.

failure_ttl : En esta clase, especificamos el tiempo que permanecerá el servicio abierto y fuera de actividad. Hay que tener en cuenta que aunque el servicio se restablezca si el tiempo que hemos definido aquí no ha transcurrido no se volverá a cerrar el circuito.

4. Esto es todo lo que necesitamos para tener listo nuestro circuito, ahora solamente falta probarlo, podemos crear un controlador, una ruta api, y poner este simple código con el que podemos manejar para abrir y cerrar el circuito.

El funcionamiento es muy sencillo, utilizamos el patrón haciendo uso de la clase que implementa una fachada, con esto evitamos tener que acoplarnos a la implementación del patrón y poder ampliar en cualquier momento.

Seguidamente, en cada uno de nuestros procesos que queramos controlar preguntamos si el circuito está disponible (1), en el caso de que anteriormente se hubiera generado un error, el circuito se encontrara abierto (4) por lo que no se ejecutara nada de código. En caso de que el circuito esté disponible se realizaran las acciones necesarias, si se genera error (2) marcamos el circuito con error y terminamos la acción. Si la acción es correcta marcamos el suceso como correcto (3).

Con este ejemplo podemos ver como un simple patrón nos ayuda a dormir tranquilos y no preocuparnos si un sistema ajeno a nosotros falla, controlar aquellas excepciones que hacen que se rompa el sistema y meter tiempos de espera para no saturar procesos que están caídos. En definitiva, un patrón muy interesante que tiene muchas aplicaciones.