Message Queue Server en Node.js con AWS SQS

AWS SQS nos permite desacoplar sistemas grandes al proporcionarles un mecanismo de comunicación débilmente acoplado. En este artículo, usaremos Node.js y AWS SQS para crear un sistema de mensajería, ideal para microservicios.

Introducción

Con el aumento de la complejidad de los sistemas de software modernos, surgió la necesidad de dividir los sistemas que habían superado su tamaño inicial. Este aumento en la complejidad de los sistemas hizo que fuera más difícil mantenerlos, actualizarlos y actualizarlos.

Esto allanó el camino para los microservicios que permitieron que los sistemas monolíticos masivos se dividieran en servicios más pequeños que están débilmente acoplados pero que interactúan para brindar la funcionalidad total de la solución monolítica inicial. El acoplamiento suelto proporciona agilidad y facilita el proceso de mantenimiento y la adición de nuevas funciones sin tener que modificar sistemas completos.

Es en estas arquitecturas de microservicios que los Sistemas de colas son útiles para facilitar la comunicación entre los servicios separados que componen la configuración completa.

En esta publicación, nos sumergiremos en los sistemas de colas, en particular [Servicio de cola simple] de Amazon (https://aws.amazon.com/sqs/) y demostraremos cómo podemos aprovechar sus funciones en un entorno de microservicio.

¿Qué es la cola de mensajes?

Antes de que internet y el correo electrónico entraran en escena, las personas a largas distancias se comunicaban principalmente a través del intercambio de cartas. Las cartas contenían los mensajes que se compartirían y se publicaron en la estación de correos local desde donde se transferirían a la dirección del destinatario.

Esto podría haber diferido de una región a otra, pero la idea era la misma. Las personas confiaron en los intermediarios para que les entregaran sus mensajes a medida que avanzaban con sus vidas.

Cuando un sistema se divide en componentes o servicios más pequeños que se espera que funcionen juntos, necesitarán comunicarse y pasar información de un servicio a otro, según la funcionalidad de los servicios individuales.

La cola de mensajes facilita este proceso al actuar como el "servicio de oficina de correos" para los microservicios. Los mensajes se colocan en una cola y los servicios de destino recogen y actúan sobre los que están dirigidos a ellos. Los mensajes pueden contener cualquier cosa, como instrucciones sobre los pasos a seguir, los datos sobre los que actuar o guardar, o los trabajos asincrónicos que se realizarán.

La cola de mensajes es un mecanismo que permite que los componentes de un sistema se comuniquen e intercambien información de manera asíncrona. Esto significa que los sistemas débilmente acoplados no tienen que esperar una retroalimentación inmediata sobre los mensajes que envían y pueden liberarse para continuar manejando otras solicitudes. Cuando llega el momento y se requiere la respuesta, el servicio puede buscar la respuesta en la cola de mensajes.

Estos son algunos ejemplos de colas de mensajes o intermediarios populares:

Casos de uso de Message Queuing

Las colas de mensajes no son necesarias para todos los sistemas, pero hay ciertos escenarios en los que vale la pena el esfuerzo y los recursos necesarios para configurarlos y mantenerlos. Cuando se utilizan adecuadamente, las colas de mensajes son ventajosas de varias maneras.

En primer lugar, las colas de mensajes admiten el desacoplamiento de sistemas grandes al proporcionar el mecanismo de comunicación en un sistema débilmente acoplado.

La redundancia se refuerza mediante el uso de colas de mensajes al mantener el estado en caso de que falle un servicio. Cuando un servicio fallido o defectuoso reanuda las operaciones, todas las operaciones que debía manejar seguirán en la cola y puede recuperarlas y continuar con las transacciones, que de otro modo podrían haberse perdido.

La cola de mensajes facilita el procesamiento por lotes de operaciones, como el envío de correos electrónicos o la inserción de registros en una base de datos. Las instrucciones por lotes pueden guardarse en una cola y procesarse todas al mismo tiempo en orden en lugar de procesarse una por una, lo que puede ser ineficiente.

Los sistemas de colas también pueden ser útiles para garantizar la coherencia de las operaciones al garantizar que se ejecuten en el orden en que se recibieron. Esto es especialmente importante cuando se han replicado componentes o servicios particulares de un sistema para manejar una mayor carga. De esta forma, el sistema escalará bien para manejar la carga y también garantizará que las transacciones procesadas sean consistentes y en orden, ya que todos los servicios replicados obtendrán sus instrucciones de la cola de mensajes que actuará como la única fuente de verdad.

Servicio de cola simple de Amazon - SQS

Como la mayoría de las otras ofertas de Amazon Web Services, Simple Queue Service (SQS) es una solución de cola de mensajes que Amazon distribuye y administra completamente, de manera muy similar a la informática sin servidor a través de [Cáliz] (https://github.com/aws/chalice ).

SQS nos permite enviar y recibir mensajes o instrucciones entre componentes de software, lo que nos permite implementar y escalar microservicios en nuestros sistemas sin la molestia de configurar y mantener un sistema de colas.

Al igual que otros servicios de AWS, SQS se escala dinámicamente en función de la demanda y, al mismo tiempo, garantiza la seguridad de los datos que pasan a través del cifrado (opcional) de los mensajes.

Proyecto de demostración {#proyecto de demostración}

Para explorar Amazon Simple Queue Service, crearemos un sistema desacoplado en Node.js, en el que cada componente interactuará con los demás enviando y recuperando mensajes de SQS.

Dado que somos una organización pequeña que no tiene el ancho de banda para manejar los pedidos a medida que ingresan, tendremos un servicio para recibir los pedidos de los usuarios y otro que entregará todos los pedidos publicados ese día a nuestra bandeja de entrada de correo electrónico en un determinada hora del día para el procesamiento por lotes. Todos los pedidos se almacenarán en la cola hasta que sean recogidos por nuestro segundo servicio y enviados a nuestra bandeja de entrada de correo electrónico.

Nuestros microservicios consistirán en API simples de Node.js, una que recibe la información del pedido de los usuarios y otra que envía correos electrónicos de confirmación a los usuarios.

El envío de confirmaciones por email de forma asíncrona a través de la cola de mensajería permitirá que nuestro servicio de pedidos siga recibiendo pedidos a pesar de la carga ya que no tiene que preocuparse por enviar los emails.

Además, en caso de que el servicio de correo se caiga, una vez que vuelva a funcionar, continuará enviando correos electrónicos desde la cola, por lo tanto, no tendremos que preocuparnos por los pedidos perdidos.

Servicios web de Amazon

Para este proyecto, necesitaremos una cuenta de AWS activa y válida a la que podemos suscribirnos en la página de inicio de AWS. AWS requiere que no solo ofrezcamos algunos datos personales, sino también nuestros datos de facturación. Para evitar que se nos facture por este proyecto de demostración, utilizaremos la Capa gratuita de AWS con fines de prueba y desarrollo.

También necesitaremos instalar la herramienta AWS CLI para poder interactuar con nuestros recursos de AWS desde nuestras máquinas. Las instrucciones para instalar la herramienta AWS CLI en varias plataformas se pueden encontrar aquí.

Con la herramienta AWS CLI en su lugar, podemos dirigirnos a la Consola de AWS y debajo de nuestro menú desplegable de perfil hay una sección llamada "Mis credenciales de seguridad". Aquí podremos crear credenciales que se utilizarán al interactuar con la consola de AWS.

Estas credenciales también serán utilizadas por la herramienta CLI de Amazon, que configuraremos ejecutando:

1
$ aws configure

Obtendremos un aviso para completar nuestra ID de clave de acceso, Clave de acceso secreta y regiones predeterminadas y formatos de salida. Los dos últimos son opcionales, pero necesitaremos la clave de acceso y el secreto que obtuvimos del panel de la consola de AWS.

Con nuestra cuenta de AWS en funcionamiento y la CLI de AWS configurada, podemos configurar nuestro Servicio de cola simple de AWS navegando a la página de inicio de SQS.

Como podemos ver en la siguiente captura de pantalla, después de especificar nuestro nombre de cola como nodeshop.fifo, se nos presentan dos opciones de cola:

inicialización de sqs

Tenemos la opción de elegir entre una Cola Estándar o una Cola FIFO. Una cola estándar no mantiene el orden de los mensajes que recibe y es más adecuada para proyectos que priorizan el rendimiento sobre el orden de los eventos.

Una cola FIFO, por otro lado, mantiene el orden de los mensajes recibidos y también se recuperan en el mismo orden primero en entrar, primero en salir.

Dado que construiremos una mini plataforma de compras, es importante mantener el orden de las solicitudes ya que esperamos vender artículos a las personas en el orden de sus compras. Una vez que hemos elegido el tipo de cola que requerimos, podemos modificar alguna configuración adicional de nuestra cola:

sqs fifo configuración avanzada

Podemos configurar el tiempo antes de que un mensaje se elimine automáticamente de una cola y el tamaño de un mensaje, entre otras opciones. Por ahora, configuraremos nuestra cola FIFO utilizando los valores predeterminados. Con nuestra cola lista, ahora podemos crear nuestras API de Node.js que leerán y escribirán en nuestra cola FIFO de Amazon SQS.

Configuración de Node.js y NPM

Las instrucciones para configurar Node.js en múltiples plataformas se pueden encontrar aquí en el Sitio web oficial de Node.js. Node Package Manager (NPM) se envía con Node.js y podemos verificar nuestra instalación de la siguiente manera:

1
2
3
4
5
6
7
# Node.js version
$ node -v
v12.12.0

# NPM version
$ npm -v
6.11.3

El siguiente paso es configurar nuestras carpetas de la siguiente manera:

1
2
3
4
5
# create folder and move into it
$ mkdir nodeshop_apis && cd $_

# create the orders and emails services folders
$ mkdir orderssvc emailssvc

Configuración de las API de nodo

Construiremos primero el servicio pedidos ya que es el que recibe los pedidos de los usuarios y publica la información en nuestra cola. Nuestro servicio de correos electrónicos leerá de la cola y enviará los correos electrónicos.

Inicializaremos un proyecto de Node.js e instalaremos Marco Express.js, que usaremos para construir nuestra API minimalista. También instalaremos el middleware analizador de cuerpo para manejar los datos de nuestra solicitud y también validarlos.

Para lograr esto en nuestra carpeta raíz:

1
2
3
4
5
# initialize node project
$ npm init

# install express and body-parser
$ npm install express body-parser --save

Una vez que Express y body-parser estén instalados, se agregarán automáticamente a la sección de dependencias de nuestro archivo package.json gracias a la opción --save.

Como tendremos varios servicios que se ejecutarán al mismo tiempo, también instalaremos el paquete npm-ejecutar-todo para ayudarnos inicie todos nuestros servicios al mismo tiempo y no tenga que ejecutar comandos en varias ventanas de terminal:

1
$ npm install npm-run-all --save

Con npm-run-all instalado, modifiquemos ahora la entrada scripts en nuestro archivo package.json para incluir los comandos para iniciar nuestros servicios y un comando para ejecutarlos todos:

1
2
3
4
5
6
7
8
9
{
  // Truncated for brevity...
  "scripts": {
    "start-orders-svc": "node ./orderssvc/index.js 8081",
    "start-emails-svc": "node ./emailssvc/index.js",
    "start": "npm-run-all -p -r start-orders-svc"
  },
  // ...
}

Agregaremos los comandos start-orders-svc y start-emails-svc para ejecutar nuestros servicios orders y emails respectivamente. Luego configuraremos el comando start para ejecutarlos usando npm-run-all.

Con esta configuración, ejecutar todos nuestros servicios será tan fácil como ejecutar el siguiente comando:

1
$ npm start

Podemos crear nuestra API orders en el archivo index.js de la siguiente manera:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const express = require('express');
const bodyParser = require('body-parser');

const port = process.argv.slice(2)[0];
const app = express();

app.use(bodyParser.json());

app.get('/index', (req, res) => {
    res.send("Welcome to NodeShop Orders.")
});

console.log(`Orders service listening on port ${port}`);
app.listen(port);

Después de agregar las bibliotecas requeridas a nuestra aplicación Express, el punto final "/index" responderá simplemente enviando un mensaje de bienvenida. Finalmente, la API escuchará en un puerto que especificaremos al iniciarla.

Iniciaremos la aplicación ejecutando el comando npm start e interactuaremos con nuestras API utilizando la Solicitud de cartero:

Solicitud de obtención del cartero

Implementaremos el servicio emails más adelante. Por ahora, nuestro servicio de ‘pedidos’ está configurado y ahora podemos implementar nuestra lógica comercial.

Implementación: Servicio de pedidos

Para implementar nuestra lógica de negocios, comenzaremos con el servicio pedidos que recibirá nuestros pedidos y los escribirá en nuestra cola de Amazon SQS.

Lo lograremos mediante la introducción de una nueva ruta y un controlador para manejar la entrada de pedidos del usuario final y enviar los datos del pedido a nuestra cola de Amazon SQS.

Antes de implementar el controlador, necesitaremos instalar Amazon SDK para Node.js:

1
$ npm install aws-sdk --save

Nuestro nuevo punto de enlace "/pedido" recibirá una carga útil que contiene los datos del pedido y la enviará a nuestra cola de SQS mediante el SDK de AWS:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// ./orderssvc/index.js

//
// Code removed for brevity...
//

// Import the AWS SDK
const AWS = require('aws-sdk');

// Configure the region
AWS.config.update({region: 'us-east-1'});

// Create an SQS service object
const sqs = new AWS.SQS({apiVersion: '2012-11-05'});
const queueUrl = "SQS_QUEUE_URL";

// the new endpoint
app.post('/order', (req, res) => {

    let orderData = {
        'userEmail': req.body['userEmail'],
        'itemName': req.body['itemName'],
        'itemPrice': req.body['itemPrice'],
        'itemsQuantity': req.body['itemsQuantity']
    }

    let sqsOrderData = {
        MessageAttributes: {
          "userEmail": {
            DataType: "String",
            StringValue: orderData.userEmail
          },
          "itemName": {
            DataType: "String",
            StringValue: orderData.itemName
          },
          "itemPrice": {
            DataType: "Number",
            StringValue: orderData.itemPrice
          },
          "itemsQuantity": {
            DataType: "Number",
            StringValue: orderData.itemsQuantity
          }
        },
        MessageBody: JSON.stringify(orderData),
        MessageDeduplicationId: req.body['userEmail'],
        MessageGroupId: "UserOrders",
        QueueUrl: queueUrl
    };

    // Send the order data to the SQS queue
    let sendSqsMessage = sqs.sendMessage(sqsOrderData).promise();

    sendSqsMessage.then((data) => {
        console.log(`OrdersSvc | SUCCESS: ${data.MessageId}`);
        res.send("Thank you for your order. Check you inbox for the confirmation email.");
    }).catch((err) => {
        console.log(`OrdersSvc | ERROR: ${err}`);

        // Send email to emails API
        res.send("We ran into an error. Please try again.");
    });
});

El SDK de AWS requiere que construyamos un objeto de carga útil especificando los datos que estamos enviando a la cola, en nuestro caso lo definimos como sqsOrderData.

Luego pasamos este objeto a la función sendMessage() que enviará nuestro mensaje a la cola usando las credenciales que usamos para configurar la CLI de AWS. Finalmente, esperamos la respuesta y notificamos al usuario que su pedido ha sido recibido con éxito y que debe revisar el correo electrónico de confirmación.

Para probar el servicio orders, ejecutamos el comando npm start y enviamos la siguiente carga útil a localhost:8081/order:

1
2
3
4
5
6
{
    "itemName": "Phone case",
    "itemPrice": "10",
    "userEmail": "[correo electrónico protegido]",
    "itemsQuantity": "2"
}

Esto enviará nuestro pedido al servicio orders, desde donde se enviará el mensaje a nuestra cola SQS. Podemos ver el pedido en la cola de SQS a través de la consola de AWS, como se muestra:

sqs ver mensajes

Nuestro servicio de “pedidos” ha podido recibir el pedido de un usuario y enviar con éxito los datos a nuestra cola en el Simple Queue Service.

Implementación: Servicio de correos electrónicos

Nuestro servicio de pedidos está listo y ya recibiendo pedidos de los usuarios. El servicio emails se encargará de leer los mensajes almacenados en la cola y enviar correos electrónicos de confirmación a los usuarios. Este servicio no recibe una notificación cuando se realizan los pedidos y, por lo tanto, debe seguir revisando la cola para ver si hay nuevos pedidos.

Para garantizar que nuestro servicio de “correos electrónicos” compruebe continuamente si hay nuevos pedidos, utilizaremos la biblioteca “sqs-consumer” que comprobará de forma continua y periódica si hay nuevos pedidos y enviará los correos electrónicos a los usuarios. sqs-consumer también eliminará los mensajes de la cola una vez que los haya leído correctamente de la cola.

Comenzaremos instalando la biblioteca sqs-consumer ejecutando el siguiente comando:

1
$ npm install sqs-consumer --save

Ahora podemos implementar el servicio emails de la siguiente manera:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
const AWS = require('aws-sdk');
const { Consumer } = require('sqs-consumer');

// Configure the region
AWS.config.update({region: 'us-east-1'});

const queueUrl = "SQS_QUEUE_URL";

// Configure Nodemailer to user Gmail
let transport = nodemailer.createTransport({
    host: 'smtp.googlemail.com',
    port: 587,
    auth: {
        user: 'Email address',
        pass: 'Password'
    }
});

function sendMail(message) {
    let sqsMessage = JSON.parse(message.Body);
    const emailMessage = {
        from: 'sender_email_adress',    // Sender address
        to: sqsMessage.userEmail,     // Recipient address
        subject: 'Order Received | NodeShop',    // Subject line
        html: `<p>Hi ${sqsMessage.userEmail}.</p. <p>Your order of ${sqsMessage.itemsQuantity} ${sqsMessage.itemName} has been received and is being processed.</p> <p> Thank you for shopping with us! </p>` // Plain text body
    };

    transport.sendMail(emailMessage, (err, info) => {
        if (err) {
            console.log(`EmailsSvc | ERROR: ${err}`)
        } else {
            console.log(`EmailsSvc | INFO: ${info}`);
        }
    });
}

// Create our consumer
const app = Consumer.create({
    queueUrl: queueUrl,
    handleMessage: async (message) => {
        sendMail(message);
    },
    sqs: new AWS.SQS()
});

app.on('error', (err) => {
    console.error(err.message);
});

app.on('processing_error', (err) => {
    console.error(err.message);
});

console.log('Emails service is running');
app.start();

Crearemos una nueva aplicación sqs-consumer utilizando la función Consumer.create() y proporcionaremos la URL de consulta y la función para manejar los mensajes obtenidos de la cola de SQS.

En nuestro caso, la función sendMail() tomará el mensaje obtenido de la cola, extraerá los detalles del pedido del usuario y luego enviará un correo electrónico al usuario usando Nodemailer. Consulta nuestro artículo enviar correos electrónicos en Node.js, si quieres saber más.

Nuestro servicio de emails ya está listo. Para integrarlo a nuestro script de ejecución, simplemente modificaremos la opción scripts en nuestro package.json:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  // Truncated for brevity...
  "scripts": {
    "start-orders-svc": "node ./orderssvc/index.js 8081",
    "start-emails-svc": "node ./emailssvc/index.js",
    // Update this line
    "start": "npm-run-all -p -r start-orders-svc start-emails-svc"
  },
  // ...
}

Cuando enviamos un nuevo pedido a través del servicio de “pedidos”, recibimos el siguiente correo electrónico en nuestra bandeja de entrada:

sqs email inbox

Conclusión

En esta publicación, usamos Node.js y Express para crear una API destinada a recibir los pedidos de los usuarios y publicar los detalles del pedido en nuestra cola de SQS en AWS. Luego creamos otro servicio para buscar los mensajes tal como se publicaron en la cola y enviar correos electrónicos de confirmación a los usuarios que publicaron los pedidos.

Separamos la lógica de pedidos de la lógica de administración de correo electrónico y reunimos los dos servicios mediante un sistema de cola de mensajes. De esta forma, nuestro servicio de “pedidos” puede manejar la colocación de pedidos mientras que el servicio de “correos electrónicos” envía los correos electrónicos a los usuarios.

El código fuente de este proyecto está disponible aquí en GitHub./