Emulando una cola SQS en una aplicación NestJS

Blog Single

La complejidad y requisitos de las aplicaciones de hoy en día nos imponen nuevos retos tratando de testearlas en nuestros entornos locales. Los mayores desafíos nos los encontramos cuando queremos  probar nuestro software de manera end to end, sobre todo si la infraestructura se compone de servicios en la nube.  Sin embargo, la mayor parte de las veces docker entra a nuestro rescate, ofreciéndonos imágenes construidas que nos pueden ayudar a emular el comportamiento de estos servicios.

Específicamente, para trabajar con AWS podemos encontrar soluciones completas como Localstack (https://localstack.cloud/). Este servicio emula gran parte de los servicios de AWS exponiendo endpoints contra los  que podemos emular nuestra infraestructura,  la automatización de esta con Terraform o poder realizar pruebas de nuestro software end to end en nuestro proceso de integración continua sin depender del proveedor cloud.

En este post, hablaremos de una solución más ligera para emular el comportamiento de una cola AWS SQS totalmente compatible con nuestro consumer construido en Typescript y dentro del contexto de una aplicación NestJS.

¿Qué es una cola SQS?

SQS, acrónimo de Simple Queue Service, es un servicio de AWS para gestionar colas de mensajes.  Este tipo de servicios sirve generalmente para desacoplar nuestro software, procesar mensajes de manera asíncrona y escalar nuestro software de manera independiente. El estilo de mensajería se basa en el patrón PubSub, en el cual un publisher deja mensajes en un topic, sin conocer al destinatario. Posteriormente unos consumers/subscribers leen de esas colas procesando el mensaje y eliminándolo de estas.

SQS es la solución que nos ofrece AWS, pero tenemos servicios de idéntico comportamiento en Azure con Azure Queue Storage o en Google Cloud Platform con Cloud PubSub.

sqs

Emulando la cola SQS

Para emular nuestra cola sin depender de AWS vamos a usar ElasticMQ. ElasticMQ es un sistema de colas ligero que almacena los mensajes temporalmente en memoria.

ElasticMQ sigue la semántica de AWS SQS, y la gran ventaja de esto es que podemos utilizar el SDK de AWS contra este servicio. Los mensajes en SQS se reciben sondeando la cola. Cuando se recibe un mensaje, se bloquea durante un período de tiempo específico (visibilityTimeout). Si el mensaje no se elimina durante ese tiempo, volverá a estar disponible para su entrega.

El enfoque en este tipo de colas es asegurarse de que los mensajes se entreguen a los destinatarios. Sin embargo, puede suceder que un mensaje se entregue dos veces (por ejemplo, sí un consumer muere después de recibir un mensaje y procesarlo, pero antes de eliminarlo). Es por eso que debemos cumplir con el principio de idempotencia, es decir, que no se vean afectadas negativamente el consumer procesa el mismo mensaje más de una vez.

A continuación, se muestra cómo quedaría nuestro docker-compose.yml donde se incluye el servicio SQS utilizando una imagen dockerizada (https://github.com/roribio/alpine-sqs) conectado con el servicio node que levanta nuestra api.

Además, incluiremos un fichero de configuración mapeando en nuestro volumen el fichero /opt/config/elasticmq.conf donde configuraremos nuestras colas y su comportamiento:

Para mandar un mensaje a la cola podemos utilizar el CLI de AWS, indicando el endpoint, la cola y el cuerpo del mensaje:

Existen algunas soluciones no oficiales de nest como https://github.com/yannkaiser/nestjs-aws-sqshttps://github.com/aiandev/sqs-consumer-nestjs que podrían ayudarnos. Sin embargo, se decidió implementar una solución totalmente independiente. Este es el código de ejemplo de una clase abstracta con la funcionalidad necesaria para leer mensajes de una cola SQS:

OnApplicationBootstrap yOnApplicationShutdown son dos interfaces que nos permiten engancharnos a los evento lanzados cuando la aplicación se termina de levantar y cuando se apaga el servicio. Estas interfaces las utilizamos para arrancar el polling cuando nuestra aplicación esté levantada. Estas interfaces nos obligan a implementar los métodos async onApplicationBootstrap():Promise<void>yOnApplicationShutdown():void donde arrancará y finalizará la lectura de nuestra cola. Tras recibir los mensajes almacenados en la cola se ejecutará el métodohandleMessage.

El métodohandleMessage  se encarga de recibir el mensaje de la cola, llamar al manejador abstracto que se encarga de manejar el tipo de mensaje, ejecutar la lógica del manejador y eliminar el mensaje para no volver a ser recibido..

Para implementar nuestra lógica, crearemos una nueva clase que extienda de SQSQueue e implementaremos la lógica adecuada implementando el método handle . Un ejemplo de implementación concreta de un consumer puede ser el siguiente:

Dentro del contexto de una aplicación NestJS, esta clase CallbackSQS se establecería como un provider importado por el módulo correspondiente.

Conclusión

Como hemos visto a lo largo del artículo, es muy sencillo emular comportamientos de servicios en la nube apoyándonos en la cantidad de imágenes que podemos encontrar en Dockerhub. Es esencial, que podamos desarrollar en nuestros entornos locales sin depender de nuestra nube, pudiendo realizar pruebas completas de nuestro software. Esto nos dará la flexibilidad y libertad de poder trabajar y asegurar el adecuado comportamiento y la calidad necesaria para confiar en nuestro software.

Recursos

https://medium.com/@ashwanihere/managing-sqs-consumers-in-a-nodejs-application-3c1466d00077

https://medium.com/better-programming/how-to-emulate-aws-sqs-for-development-in-dockerized-ruby-on-rails-app-c0c16aadb84c

Nube de Tags

Comparte el artículo si te ha resultado interesante: