Diseñando microservicios con ExpressJS

En este artículo, explicaré cómo desarrollar una arquitectura basada en microservicios en NodeJS. Para ilustrarlo, creamos una aplicación de gestión de tickets, aquí el diagrama para explicarlo

Antes de comenzar, no explicaré todos los detalles relacionados con ExpressJS, para eso puedes descargar los archivos del proyecto desde el siguiente enlace de Github.

https://github.com/yildizberkay/microservices-with-expressjs

Affiliated Ad

Escenario

En el diagrama anterior, Nginx tiene dos funciones. El primero es administrar todo el tráfico y el segundo verificar la autenticación del usuario. No profundizaré en la autorización en esta publicación. Podría ser un tema de otra publicación.

También contamos con servicios de autenticación y tickets, y solo tienen un modelo. El servicio de autenticación gestiona el registro y la verificación de autenticación, mientras que el servicio de entradas gestiona las entradas que pertenecen a los usuarios.

Tenemos dos puntos finales expuestos sin ninguna autenticación para registrarse e iniciar sesión. Mientras que un middleware gestiona la autenticación del servicio de autenticación, Nginx gestiona la autenticación del resto de servicios. Si llega alguna solicitud al servicio de entradas, Nginx pedirá validez del token al servicio de autenticación y, según el resultado, las rechazará o aceptará. Si se acepta la solicitud, Nginx también pasará la identificación del usuario activo al servicio de tickets con el encabezado.

Diseño de microservicios

Estructura de carpetas de un microservicio

- tests
- src
  - db
    - config
    - models
  - enums
  - exceptions
  - middlewares
  - services
  - utils
  - index.js
  - routes.js
  - server.js


La carpeta tests es un reflejo de la carpeta src. Las pruebas se llevan a cabo con la misma ruta y los mismos nombres. Los nombres tienen la extensión .test.js como sufijo.


src/db/config contiene perfiles de base de datos. Estos perfiles se pueden modificar con parámetros de entorno. En esta implementación, tenemos 3 perfiles como desarrollo, prueba y producción. Los modelos de base de datos se guardan en la carpeta src/db/models. Usamos Sequelize como ORM, pero no profundizaré en los detalles, solo definiré Modelos y lo usaré. Si deseas obtener detalles sobre este ORM, tiene buena documentación (en inglés).

src/exceptions contiene clases de excepciones específicas de microservicio. Una clase de excepción contiene un nombre y httpStatusCode. globalErrorHandler usa estos dos parámetros para generar una respuesta de error. Toda la lógica de negocio se encuentra en la carpeta src/services. Los archivos de lógica de negocio se pueden separar por nombres de modelo. No creé una carpeta de rutas porque son servicios simples que constan de un solo modelo y tienen un punto final principal. Si planeas descomponer tus microservicios mediante otra estrategia, puedes crear una carpeta de rutas y almacenar sus rutas en ella.

Manejo de la autenticación con middleware

El servicio de autenticación utiliza el estándar JWT en la autenticación de usuarios. authHandler es nuestro middleware clave. Toma la clave de autorización y la analiza. En la línea 5 se verifica JWT. Si no es válido, devuelve el código de estado 401.

Comunicación entre Nginx y el servicio de autenticación

El punto final /check-token devuelve el código de estado 200 y agrega la propiedad User-Id al encabezado. Esta propiedad será procesada por Nginx más tarde.

Obtener la identificación de usuario del servicio de tickets

Nginx pasa User-Id en el encabezado a todos los demás servicios implementados con auth.

Diseño de puerta de enlace

Nginx como puerta de enlace

Nginx es un servidor web único, puedes configurarlo como balanceador de carga o puerta de enlace con solo unas pocas líneas de código. Mientras Nginx está trabajando en un contenedor Docker, puede ver otros servicios en la misma red.

En el siguiente código, maneja todas las solicitudes provenientes de 8080. location/auth detecta los puntos finales que comienzan con /auth y pasa la solicitud siguiente a http://service-auth:3000 con proxy_pass. Puedes preguntarte cómo Nginx reconocerá este nombre de host. Docker establecerá este nombre de host para todos los contenedores ubicados en la misma red.

server {
  listen 8080;
  location /auth {
    rewrite ^/auth/(.*) /$1 break;
    
    proxy_pass        http://service-auth:3000;
    proxy_redirect    off;
    proxy_set_header  Host $host;
    ...
  }
}

Esta es una simple demostración. Si tus servicios se vuelven más complicados y saturados, también puede utilizar soluciones de descubrimiento de servicios como Consul.

Autenticación

El módulo auth_request maneja todo lo relacionado con la autenticación antes de que la solicitud entre en servicio. Después de que se complete la solicitud de autenticación, auth_request_set establece cualquier valor proveniente del resultado de la autenticación en una variable local como $user_id para que podamos usarlo más tarde para pasarlo.

location /ticket {
  auth_request /check-token;
  auth_request_set $user_id $sent_http_user_id;

  rewrite ^/ticket/(.*) /$1 break;

  proxy_pass        http://service-ticket:3000;
  proxy_set_header  User-Id $user_id;
  ...
}

Entonces, ¿dónde está la definición de /check-token? Definimos una ubicación interna y se pasa directamente al extremo relacionado del servicio de autenticación.

location /check-token {
  internal;
  proxy_pass        http://service-auth:3000/check-token;
  ...
}

Balanceo de carga

Puedes tener un grupo de servicios de autenticación y poner un balanceador de carga interno. Para hacer esto, puedo sugerir 2 estrategias:

Estrategia 1: Nginx upstream

upstream sale en nuestra ayuda si deseas utilizar Nginx. Se utiliza para combinar un grupo de servidores y también tiene capacidad de balanceo de carga.

upstream service-auth {
  server service-auth-1:3000;
  server service-auth-2:3000;
  server service-auth-3:3000;
}

server {
  listen 8080;
  location /auth {
    rewrite ^/auth/(.*) /$1 break;
    
    proxy_pass        http://service-auth;
    proxy_redirect    off;
    proxy_set_header  Host $host;
  }
}

https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/

Estrategia 2: Gestores de procesos como PM2

El servidor se puede ejecutar con un administrador de procesos como PM2 o StrongLoop. Proporcionan el modo de clúster y cualquier número de la aplicación puede funcionar en un contenedor.

https://pm2.keymetrics.io/docs/usage/docker-pm2-nodejs/#starting-a-configuration-file

Conclusión

Gracias por leer este artículo. He tratado de explicarlo de manera simple, tiene muchos más detalles, por supuesto, pero estos detalles están moldeados por tus necesidades. También creé scripts de implementación, pero explicaré los detalles en otra publicación. Puedes encontrar todos los códigos de ejecución de la publicación en el siguiente enlace de Github.

Finalmente, ten en cuenta que no existe una única forma correcta. Especialmente en JavaScript 🙂

Repositorio de Github: https://github.com/yildizberkay/microservices-with-expressjs

Puedes ver el artículo original en inglés en: https://itnext.io/designing-microservices-with-expressjs-eb23e4f02192

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *