Manejo de reintentos en sistemas distribuidos orientados a eventos

Daniel Estiven Rico Posada
Bancolombia Tech
Published in
5 min readMar 4, 2021

Si existe una constante que podamos resaltar de los sistemas distribuidos y en general del software, es que en algún momento uno de sus componentes va a fallar, por eso, diseñar pensando en como vamos a reaccionar ante esas fallas es la manera más sana de construir software.

Las fallas pueden ser manejadas bajo diferentes estrategias, que son dependientes del tipo de arquitectura, las tecnologías que se utilicen y la infraestructura con la que se cuente.

El objetivo de este artículo es explicar desde un enfoque práctico una de las estrategias que se pueden utilizar para que las fallas de un sistema no se conviertan en un dolor de cabeza.

La estrategia consiste en definir una arquitectura pensada para manejar reintentos en un sistema distribuido que tiene una alta orientación a eventos, diseñado bajo los principios de arquitectura limpia y con enfoque declarativo.

Tecnologías a utilizar:

  • Java 11 / Spring Webflux
  • Reactor (Programación reactiva)
  • RabbitMQ
  • Reactive Commons

Requisitos:
1. Conocer de manera general los principios de arquitectura limpia que son tratados en este artículo:

2. Explorar las funcionalidades generales del plugin de arquitectura limpia creado por Bancolombia:

Arquitectura

La anterior arquitectura representa un proceso asíncrono de inscripción de una empresa a un convenio. El flujo inicia con una intención de registro por parte de una empresa, esta solicitud es recibida por el entrypoint “reactive-web”, que es un componente que expone servicios REST usando Spring Webflux. Este componente se encarga de dejar la solicitud de inscripción (Comando) en una cola de RabbitMQ, a través del driven adapter “async-event-bus”. Posteriormente responde de manera inmediata (No espera a que el proceso de registro concluya).

Como existe un nuevo comando de tipo “Subscription” en RabbitMQ, se activa el entry point “event-subscriptions” para procesar la inscripción. El procesamiento de la inscripción consiste en un llamado a un servicio externo http a través del driven adapter “rest-subs-service”.

Si la inscripción es exitosa, se emite un evento de tipo “event.notification” hacia RabbitMQ. Este evento es procesado por el driven adapter “notification-sender” para enviar el respectivo mensaje de notificación al usuario.

En caso de que la inscripción falle, porque el servicio esté caído o por otro tipo de error, se reintentará 3 veces. Este reintento consiste en devolver el mensaje a la cola para que sea procesado nuevamente después de que el tiempo de delay configurado haya pasado, situación de la que se encarga reactive-commons. Esta estrategia se conoce como reintentos basados en DLQ.

Si la inscripción se intenta procesar el número máximo de reintentos configurados, sin éxito, se activará el listener del evento de descarte del mensaje, que en este caso se encarga de guardarlo en Dynamodb a través del driven adapter “dynamodb-fallback”, para que sea monitoreado por las personas encargadas.

Implementación

El primer paso consiste en crear todos los adaptadores que requerimos haciendo uso del plugin de arquitectura limpia. Para este escenario necesitaremos los siguientes adaptadores:

  1. reactive-web: exposición de servicios REST con Spring Webflux.
  2. async-event-bus: emisión de comandos hacia rabbitMQ
  3. event-subscriptions: subscripción a los comandos y eventos de rabbitMQ.
  4. rest-subs-service: se encarga de comunicarse con el servicio externo para la inscripción de empresas.
  5. notification-sender: se encarga de notificar al usuario sobre una inscripción exitosa cuando el proceso asíncrono concluya.
  6. dynamodb-fallback: guardar los mensajes que fueron descartados por llegar al límite máximo de reintentos.

Al crear los adaptadores usando el plugin de arquitectura limpia, el proyecto debería verse de la siguiente manera:

Es importante tener en cuenta que estos adaptadores pueden llegar a ser unidades desplegables distintas si así se requiere según las necesidades propias de cada proyecto.

Integración con reactive-commons:

Reactive commons es una iniciativa Open Source utilizada en Bancolombia para facilitar la implementación de aplicaciones orientadas a eventos haciendo uso de diferentes brokers de mensajería como RabbitMQ o SQS.

Esta librería abstrae muchos de los detalles relacionados a la configuración de las topologías de un broker, la implementación de diferentes patrones de mensajería y estrategias de reintentos para tolerancia a fallas.

Uno de los principales beneficios es su alta compatibilidad con la especificación de reactive streams y su enfoque declarativo para facilitar la integración con brokers de mensajería.

Para mayor información de reactive-commons visitar:

Para utilizar la librería reactive-commons se debe agregar la siguiente dependencia en el build.gradle:

Los adaptadores que requieren de reactive-commons en el escenario planteado son:

  • async-event-bus
  • event-subscriptions

En el application.yaml de la aplicación se deben configurar unos parámetros requeridos por la librería reactive-commons relacionados a la autenticación contra el broker y también parámetros de tolerancia a fallas:

Parámetros de tolerancia a fallas:

  • withDLQRetry: Permite habilitar la estrategia de manejo de reintentos.
  • retryDelay: Indica el tiempo que debe esperarse entre cada reintento dado en milisegundos.
  • maxRetries: Corresponde a la configuración del máximo número de reintentos que se realizarán.

Configuración de los listeners:

En el adaptador “event-subscriptions” deben configurarse los listeners tanto del evento de descarte como del comando de inscripción:

Caso de uso para procesar la inscripción:

Subscription Gateway:

El adaptador concreto para subscription gateway es en este caso la implementación de un llamado http a un servicio externo:

Procesar mensajes descartados

El caso de uso encargado procesar el mensaje descartado por reintentos tiene la siguiente estructura:

Discard Gateway:

Adaptador concreto para DiscardGateway:

Para este escenario el fallback consiste en llevar el mensaje descartado a dynamodb, por esta razón se requiere un adaptador que asuma esa responsabilidad:

DynamoDB Fallback:

Exposición de servicios

La capa de exposición de servicios corresponde a un entry point desarrollado con Spring Webflux que se encarga de llamar al caso de uso de subscripción y dar respuesta al consumidor final.

Reactive web entry-point:

Caso de uso para la subscripción:

Command Gateway:

Adaptador concreto para la emisión del comando

Para la emisión del respectivo comando se hace uso de reactive commons en el adaptador async-event-bus de la siguiente manera:

La implementación completa puede ser encontrada en:

Esta es una de las diferentes estrategias de manejo de reintentos que existen y que son de gran utilidad para escenarios donde la aplicación debe comunicarse con componentes de software externos de los cuales no se tiene control y principalmente para transacciones que sean compatibles con el asincronismo.

En los escenarios donde una aplicación no puede soportar transacciones asíncronas o cuando no tienen una orientación a eventos, existen otras estrategias que serán explicadas en publicaciones posteriores.

¡Muchas gracias por leer esta publicación! Si consideras que fue de utilidad, ¡Compártela!

--

--