¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.dev
Alberto Grande 19/10/2022 Cargando comentarios…
El proceso de diseño de un sistema suele estar dirigido por el ‘happy path’, ese flujo que va a dar solución a los requisitos de funcionalidad de nuestras aplicaciones.
Es un camino de por sí complejo, por lo que habitualmente es en el que gastamos la mayor parte de nuestro esfuerzo, dejando en el olvido en muchos casos, las situaciones en el que las cosas no funcionan todo lo bien que deberían.
Como bien hemos visto durante la serie de posts sobre Ingeniería del Caos, las cosas no siempre funcionan como se espera y en ciertas situaciones las consecuencias tienen un fuerte impacto.
Como su nombre indica, son unos patrones que nos van a ayudar a preparar nuestros sistemas a situaciones de fallo, asumiendo que van a suceder (tarde o temprano, sucederán). De esta forma, podemos mejorar la reacción y, en el mejor de los casos, que el sistema continúe en funcionamiento de forma correcta, o al menos reduciendo los efectos del fallo.
Estos patrones deben aplicarse durante el diseño para que su posterior implementación sea correcta.
Es imprescindible entender bien las repercusiones que pueden producir, ya que de forma contraria podrían ser incluso perjudiciales.
Estos son, en mi opinión, los principales patrones que se deberían tener en cuenta (hay muchos más, evidentemente).
Cada uno de estos patrones tiene un conjunto de casos de uso definidos y situaciones más favorables para su implementación y, en muchos casos, es útil combinar más de uno.
Tal y como se indicaba anteriormente, es necesario un análisis previo a su implantación.
Va dirigido a las situaciones en las que se producen pequeños fallos o pérdidas de servicio de corta duración de un servicio que se está consumiendo.
Tenemos un servicio A que intenta conectar con un servicio B, la instancia del servicio que va a tratar la petición del servicio B falla, dando un error como resultado de la petición. El servicio A, en vez de propagar o tratar directamente el error, reintenta la petición. En esta ocasión, la petición va a una instancia distinta permitiendo la ejecución satisfactoria.
No tiene sentido aplicar este patrón en todas las circunstancias, es necesario tener en cuenta la casuística del fallo para considerar si tiene sentido o no hacer un reintento. Es decir, un 404 (Not Found) o un 403 (Forbidden) es poco probable que se ‘solucionen’ con un reintento.
En determinadas circunstancias puede ser útil establecer un pequeño tiempo de espera entre los reintentos, de forma que el servicio destino tenga tiempo para recuperarse.
Al introducir reintentos estamos multiplicando el tiempo de procesamiento, ya que en lugar de una única petición (con sus latencias por comunicación, tiempo de ejecución, etc), estamos lanzando varias (tantas como intentos de reintento). Esta situación puede ser crítica si el problema es de larga duración y no se controla de manera adecuada.
El fallo en la respuesta a la petición de un servicio no nos garantiza la no ejecución. Sobre servicios no idempotentes, el reintento podría producir una ‘duplicidad’ de la operación.
Busca evitar un fallo en cascada cuando una petición a un servicio da error, generando una respuesta sintética que permita simular la respuesta.
El servicio A realiza una petición al servicio B, que falla devolviendo un error.
El servicio A reacciona ante esta situación simulando una respuesta ‘estándar’ del servicio B, y continúa tomando la ejecución como válida. El resultado global de la operación es correcto (como si no hubiera habido ningún fallo).
La respuesta sintética, por lo general, debe ser lo más ‘vacía’ posible, indicando de esta forma la ausencia de información. Pensemos, por ejemplo, en un servicio que nos proporciona información sobre productos de upselling sobre una compra. En este caso, la respuesta del fallback sería que no hay en este momento ningún producto que ofrecer, y el frontal se encargará de no mostrar directamente esta información.
Es necesario tener cuidado con la respuesta ‘por defecto’ que realiza la petición del servicio A, ya que no siempre el resultado global va a ser válido bajo estas circunstancias, invalidando por lo tanto el uso de este patrón.
Estamos ‘ocultando’ el fallo de cara al resto de servicios, lo que en ciertas situaciones puede no ser bueno.
Busca evitar un fallo en cascada cuando una petición a un servicio da error.
Para ello, se establece un ‘circuito’ con un estado a la comunicación entre los servicios, de forma que se pueda abrir o cerrar en función del comportamiento del servicio destino.
El servicio A realiza peticiones al servicio B, las cuales devuelven errores en la ejecución. Ante esta situación, el ‘circuito’ se abre y cualquier petición hacia el servicio B se sustituye por una respuesta ‘por defecto’ (bien un error directo o utilizando el patrón fallback, con una respuesta sintética que permita continuar la ejecución controlada del servicio A).
Periódicamente, el circuito da paso a algunas peticiones para comprobar si el servicio B se ha recuperado. Si las peticiones se ejecutan satisfactoriamente, el circuito se cierra dejando pasar todas las siguientes peticiones. En caso contrario, el circuito quedará abierto.
Es necesario incorporar una pieza o lógica que controle el resultado de las peticiones y que permitirá la apertura o cierre del circuito, bien como componente externo al servicio A o como código que controle esta situación dentro del propio servicio.
El período de recuperación del fallo puede extenderse, ya que la pieza que abre y cierra el servicio puede necesitar muchas peticiones para verificar si el servicio destino funciona adecuadamente.
La periodicidad en las pruebas de ‘conexión’ al servicio que está produciendo el error puede hacer complicado delimitar el momento en el que el servicio ha empezado a fallar o ha vuelto a funcionar. Nuevamente, nos podemos encontrar en casos de uso en el que esto puede ser un inconveniente.
Es el más básico (casi obvio), y por ello está implementado en la mayoría de los frameworks con valores por defecto.
El objetivo es evitar esperas ‘infinitas’ en las peticiones a los servicios en situaciones de fallo, pero sin una respuesta de fallo.
El servicio A realiza una petición al servicio B. El servicio B sufre un problema durante el proceso de la petición, produciendo que la petición se queda ‘pillada’ sin respuesta (ni positiva ni negativa). El servicio A, tras esperar el tiempo determinado en el timeout, asume que se ha producido un error y finaliza la petición con un error de timeout.
El timeout es un parámetro de cliente, es decir, el que realiza la petición decide cuánto tiempo va a esperar. Que se produzca un timeout en la petición no implica que la operación no se haya ejecutado correctamente en el servidor (y simplemente haya tardado más en su ejecución que el tiempo de espera).
En situaciones de alta carga de los servicios, en los que los tiempos de respuesta se incrementan, estos falsos positivos pueden
producirse frecuentemente.
Habitualmente no se presta la atención necesaria a la configuración de timeouts, lo que puede llevar a comportamientos inesperados. Es necesario entender el flujo de llamadas entre los distintos servicios para poder configurar de forma optimizada los parámetros de timeout, principalmente cuando existen servicios de agregación en el flujo.
En este artículo hemos visto los 4 patrones que considero esenciales cuando trabajamos con servicios distribuidos, principalmente porque considero que son aplicables en muchos de los proyectos que nos podemos llegar a encontrar en nuestro día a día, ya que tienen unos casos de uso muy comunes.
Existen muchos más, algunos orientados a arquitecturas específicas o situaciones concretas cuyo encaje puede ser más óptimo. Debemos aprender de todos ellos para tenerlos siempre listos en nuestra caja de herramientas y escoger siempre el más adecuado en función del caso de uso.
¡Nos vemos en el siguiente post!
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.