Microservicios. Arquitectura basada en eventos.

Mini-Microservice App

Descarga de código fuente

Primeramente veamos la comunicación entre los componentes de la aplicación.

Esta aplicación React va a realizar peticiones de red al Post Service y al Comments Service. Los dos servicios van a ser aplicaciones basadas en Express. Vamos a almacenar toda la información en memoria y no nos preocuparemos de manejar base de datos o entes similares.

Microservices

Despues de clonar el proyecto nos podemos posicionar en la rama Downsideone para reproducir el problema que vamos a comentar a continuación.

git checkout downsideone

El problema es de ineficiencia. Cuando pedimos todos las publicaciones existentes, por cada publicación que recuperemos se va a realizar una llamada para recuperar los comentarios asociados a cada una de ellas. Si existen 150 publicaciones se realizaran 150 llamadas para conseguir los comentarios asociados a cada una.

Request comments

Para poner en marcha el proyecto y ver el problema en vivo, debemos ejecutar los comandos mostrados a continuación en cada uno de los directorio de los servicios existentes y en el directorio del proyecto React.

npm install
npm start

Node

Después, tras crear una serie de publicaciones y comentarios, podremos ver las llamadas realizadas en nuestro navegador cada vez que cargamos la página que muestra los publicaciones existentes y sus comentarios asociados.

comments call

En comparación, en una arquitectura monolítica este problema tiene fácil solución.

monoliths

Entonces, ¿cómo solucionaremos este problema en una arquitectura de microservicios?

Como referencia, en una publicación previa que hemos creado podemos ver los diferentes tipos de comunicación que existen en este entorno: Data management between services.

Para solucionar este problema, veamos varias opciones.

Sincrono vs Asincrono: ¿cuando seleccionar uno u otro?

Ambos tipos de comunicación tienen beneficios e inconvenientes. Mientras que conseguir una comunicación asíncrona es difícil de hacerla bien esta ofrece un acoplamiento débil, por otra banda, la comunicación síncrona tiene una alto acoplamiento pero es fácil de usar y depurar.

Comunciación Síncrona en Microservicios

La comunicación síncrona es una opción a seguir, sin embargo no es la que vamos a utilizar en nuestro caso.

Sync communication

En la publicación ya mencionado anteriormente, hemos indicado los inconvenientes de este tipo de comunicación:

Es fácil de entender, sin embargo:

  • La comunicación síncrona crea dependencias entre servicios

  • Si alguna petición entre servicos falla, la petición general falla también

  • La petición completa es tan rápida como la petición más lenta

Una solución asíncrona

Para nuestro caso usaremos la comunicación asíncrona, veamos como funciona.

Primeramente, Post Service emitirá un evento cada vez que una publicación sea creada:

Posts creation

En respuesta, Comments Service emitira un evento cada vez que un comentario sea creado y Query Service reunirá toda esta informacion de las publicaciones y comentarios en una estructura de datos eficiente:

comments creation

Como resultado, Query Service no tendrá ninguna dependencia de otros servicios y será extremadamente rápido.

get posts

Si creamos varias publicaciones con comentarios asociados y paramos ambos servicios simulando que están fallando, seguiremos teniendo la información disponible.

Podemos parar los servicios ejecutando la siguiente combinación de teclas en cada uno de los directorios de los servicios a parar.

control + C

Services down

Después, comprobaremos que la información sigue estando disponible en nuestro navegador web.

Data available

Aunque la comunicación asíncrona tiene inconvenientes también es una buena solución para nosotros.

Ya hemos mencionado estos inconvenientes anteriormente, Async communication (second way)

  • Duplicación de datos

  • Difícil de entender

Unas anotaciones
  • Query Service para aprender

    Esta aplicación sólo tiene el propósito de enseñar y que seamos capaces de entender los diferentes tipos de comunicación existentes en el mundo de los Microservicios. En una aplicación real, los ‘posts’ y comentarios podrían ir juntos en un único servicio.

Bus de Eventos para Microservicios

Primeramente como indicamos anteriormente, esta aplicación tienen sólo el propósito de enseñar. En Bus de Eventos que vamos a crear no implementará un montón de características que un Bus normal tiene. Como he dicho, nuestro objetivo es tener una buena idea de lo que está sucediendo en segundo plano y eso es el motivo por el cual creamos este Bus de Eventos.

  • Implementaciones

    Muchas y diferentes implementaciones. Rabbit, Kaftka, NATS, etc…

  • ¿Como funciona?

    Recibe eventos, los publica a las oyentes.

  • Comunicación asíncrona

    Muchas características diferentes y sutiles que hacen que la comunicación asíncrona sea más fácil o más difícil.

eventBus

Moderation Service

Aunque la moderación de comentarios es muy fácil de implementar en el Comments Service existente, supongamos que necesitamos un nuevo servicio. Esta implementación parece simple pero hay una increíble complejidad oculta aquí. Por supuesto, es más simple de implementar en una arquitectura monolítica.

Eventualmente, el nuevo servicio puede tardar mucho tiempo en moderar un comentario. Por lo tanto, tan pronto como se crea el comentario, este se debe persistir en el Query Service con el estado de pendiente para que el usuario pueda ser informado de que su comentario está en espera de ser moderado.

Event Bus

Para que el Query Service sea totalmente independiente de otros servicios, el Comments Service se encargará de toda la lógica de negocio en torno al comentario.

Event Bus

Cada vez que el Comment Service actualice un comentario mediante el manejo de un evento especializado, emitiremos un solo evento muy genérico llamado simplemente ‘comment updated’. Query Service solo escucha eventos de actualización sin preocuparse por cuál fue la actualización o intentar interpretarla. Por lo tanto, Query Service no se preocupará de intentar ejecutar ninguna lógica de negocio especializada a su alrededor. En conclusión, Query Service se limitará a registrar la información actualizada.

Event Bus

Para ilustrar todo esto, en el Moderation Service pondremos un filtro. Este filtro rechazará todos los comentarios que incluyan la palabra ‘orange‘.

Moderation Service

Rejected status

Simulemos que una vez que se crea un comentario, no se modera inmediatamente. Que una persona esté a cargo de tal tarea y puede llevar 5 minutos, 2 horas, un día o más (se necesita una intervención humana).

Para ello, detenemos Moderation Service.

 Control + C

stop service

Al crear un comentario se nos informa en pantalla que está esperando a ser moderado.

Pending status

Si volvemos a iniciar el servicio, encontramos un problema. El evento fue enviado al Moderation Service mientras estaba parado (interrupción temporal) y ahora cuando se vuelve a iniciar el servicio ese evento no llega al su destino, se ha perdido. Digamos que ahora toda la aplicación está un poco desincronizada.
Después de reiniciar el Moderation Service, el último comentario creado seguirá esperando la moderación sin ser gestionado correctamente.

Para reproducir este problema, posicionemonos en la rama ‘moderationservice’:

git checkout moderationservice

Microservicios: Manejo de eventos perdidos

El primer escenario que puede tener lugar. ¿Qué sucede con nuestra aplicación cuando un servicio deja de funcionar durante un período de tiempo?

En primer lugar, el Moderation Service deja de funcionar durante un tiempo y luego los eventos B y C no llegan a su destino. Un poco más tarde el Moderation Service se recupera pero los eventos se han perdido. Finalmente, el servicio no tiene forma de averiguar que estos eventos han ocurrido y poder recuperarlos.

Es posible que se pierdan eventos mientras un servicio está experimentando una cierta cantidad de tiempo de inactividad.

Microservices

Por otro lado, el segundo escenario que debemos considerar es el siguiente. Si creamos y añadimos un servicio un año después o iniciamos un servicio mucho más tarde que los demás, estaremos trayendo servicios en línea a futuro.

¿Cómo manejamos esto?

Debemos sincronizar el servicio con toda la aplicación recuperando todos los eventos pasados y gestionándolos en el servicio.

Event Bus

Como posibles soluciones las siguientes.

Petición síncrona

Antes, imaginemos que desde hace un tiempo estamos ejecutando los servicios Posts y Comments. Un año después decidimos incorporar el Query Service

Al iniciar este servicio, obviamente debe realizar solicitudes para obtener todas las publicaciones existentes en el Posts Service hasta el momento y otra solicitud para obtener todos los comentarios existentes hasta el momento en el Comments Service.

Sync Request

Como resultado, ya tenemos el código en producción y este cambio nos obligaría a implementar el código de todas las llamadas en Query Service y crear un ‘endpoint’ en cada uno de los servicios existentes para responder a estas solicitudes. Imaginemos que tenemos muchos servicios involucrados en un cambio de este tipo, debemos modificar todos esos servicios. Entonces, consideramos esto un inconveniente.
Por otro lado, estas solicitudes se realizarían iniciando Query Service para sincronizar el servicio con la aplicación. Posteriormente solo se limitará a emitir y recibir eventos para mantener la sincronización, estas solicitudes no se volverán a realizar. Entonces, estaríamos implementando un código cuya única función es poner Query Service en línea

Acceso directo a la base de datos

Otra posible solución sería el acceso directo a las bases de datos. Esta sería una excepción a la regla de la que ya hemos hablado en Data Management Between Service, cada servicio debe tener una base de datos privada.

data base

En lugar de realizar la solicitud síncrona al servicio, se realiza directamente a su base de datos. Ahora, la ventaja de esto es que ahora Query Service podría ejecutar sus propias consultas y recuperar todos los diferentes datos que necesita de Post DB y Comments DB. Después de sincronizar todos estos datos una vez más, Query Service podría comenzar a escuchar los diferentes eventos.

Sin embargo, este enfoque tiene un inconveniente. Si los servicios utilizan diferentes tipos de bases de datos (MongoDB, PostgreSQL, etc…), el código a implementar en el Query Service debe tener en cuenta todas estas diferencias. Eso es mucho código adicional que tendremos que escribir.

Persistiendo Eventos

 Finalmente, como tercera opción tenemos la siguiente solución que podemos llamar Store Events. Así es como la gente realmente implementa los microservicios. Vamos a dar acceso a Query Service a todos los eventos que han ocurrido en el pasado. Además de emitir y recibir eventos, Event Bus almacenará todos los eventos internamente en algún tipo de estructura de datos, en alguna base de datos o algo similar (en nuestra aplicación de aprendizaje usamos la memoria).

Event Bus

De esta manera, resolvemos el problema de traer servicios en línea a futuro. Podemos ver en el código de Query Service cómo se recuperan los eventos cuando se inicia el servicio.

const res = await axios.get("http://localhost:4005/events");

Event Bus

No solo resuelve el problema de poner los servicios en línea a futuro, sino que también resuelve el problema de la posibilidad de perder eventos mientras un servicio está experimentando una cierta cantidad de tiempo de inactividad. Cuando se inicia Moderation Service , este recupera eventos pasados.

Event Bus

Para comprobar que todo esto funciona correctamente, podemos parar un servicio u otro y crear publicaciones con comentarios. Al iniciar o reiniciar el servicio se recuperan todos los eventos creados anteriormente y la aplicación se sincroniza correctamente con todo lo ocurrido en el pasado.

0 comentarios

Dejar un comentario

¿Quieres unirte a la conversación?
Siéntete libre de contribuir!

Deja una respuesta

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