Compartir a través de


Comunicación en una arquitectura de microservicios

Sugerencia

Este contenido es un extracto del libro electrónico, ".NET Microservices Architecture for Containerized .NET Applications" (Arquitectura de microservicios de .NET para aplicaciones de .NET contenedorizadas), disponible en Documentación de .NET o como un PDF descargable y gratuito que se puede leer sin conexión.

Miniatura de la portada del libro electrónico 'Arquitectura de microservicios de .NET para aplicaciones .NET contenedorizadas'.

En una aplicación monolítica que se ejecuta en un único proceso, los componentes se invocan entre sí mediante llamadas de función o método de nivel de lenguaje. Estos se pueden acoplar fuertemente si va a crear objetos con código (por ejemplo, new ClassName()), o se pueden invocar de forma desacoplada si usa la inserción de dependencias haciendo referencia a abstracciones en lugar de instancias de objeto concretas. En cualquier caso, los objetos se ejecutan dentro del mismo proceso. El mayor desafío al cambiar de una aplicación monolítica a una aplicación basada en microservicios radica en cambiar el mecanismo de comunicación. Una conversión directa de llamadas de método en curso a llamadas RPC a servicios dará lugar a una comunicación extensa y no eficaz con un mal rendimiento en entornos distribuidos. Los desafíos de diseñar el sistema distribuido correctamente son lo suficientemente conocidos que incluso hay un canon conocido como fallacies de computación distribuida que enumera las suposiciones que los desarrolladores suelen hacer al pasar de diseños monolíticos a distribuidos.

No hay una solución, pero varias. Una solución implica aislar los microservicios empresariales tanto como sea posible. Luego se usa la comunicación asincrónica entre los microservicios internos y se sustituye la comunicación específica típica de la comunicación en proceso entre objetos por la comunicación general. Para ello, puede agrupar llamadas y devolver datos que agregan los resultados de varias llamadas internas al cliente.

Una aplicación basada en microservicios es un sistema distribuido que se ejecuta en varios procesos o servicios, normalmente incluso en varios servidores o hosts. Cada instancia de servicio suele ser un proceso. Por lo tanto, los servicios deben interactuar mediante un protocolo de comunicación entre procesos, como HTTP, AMQP o un protocolo binario como TCP, en función de la naturaleza de cada servicio.

La comunidad de microservicios promueve la filosofía de "puntos de conexión inteligentes y tuberías tontas". Este eslogan fomenta un diseño lo más desacoplado posible entre microservicios y lo más cohesivo posible dentro de un único microservicio. Como se explicó anteriormente, cada microservicio posee sus propios datos y su propia lógica de dominio. Pero los microservicios que componen una aplicación de extremo a extremo suelen estar simplemente coreografiados mediante comunicaciones REST, en lugar de protocolos complejos como WS-* y de comunicaciones flexibles impulsadas por eventos en lugar de orquestadores centralizados de procesos de negocio.

Los dos protocolos usados habitualmente son solicitud/respuesta HTTP con API de recursos (al consultar la mayoría de todos) y mensajería asincrónica ligera al comunicar actualizaciones entre varios microservicios. Estos se explican con más detalle en las secciones siguientes.

Tipos de comunicación

El cliente y los servicios pueden comunicarse a través de muchos tipos diferentes de comunicación, cada uno de los cuales tiene como destino un escenario y objetivos diferentes. Inicialmente, esos tipos de comunicaciones se pueden clasificar en dos ejes.

El primer eje define si el protocolo es sincrónico o asincrónico:

  • Protocolo sincrónico. HTTP es un protocolo sincrónico. El cliente envía una solicitud y espera una respuesta del servicio. Eso es independiente de la ejecución de código de cliente, que puede ser sincrónica (el subproceso está bloqueado) o asincrónica (el subproceso no está bloqueado y al final la respuesta llega a una devolución de llamada). El punto importante aquí es que el protocolo (HTTP/HTTPS) es sincrónico y el código de cliente solo puede continuar su tarea cuando recibe la respuesta del servidor HTTP.

  • Protocolo asincrónico. Otros protocolos como AMQP (un protocolo compatible con muchos sistemas operativos y entornos en la nube) usan mensajes asincrónicos. Normalmente, el código de cliente o el remitente del mensaje no esperan una respuesta. Simplemente envía el mensaje como al enviar un mensaje a una cola de RabbitMQ o a cualquier otro agente de mensajes.

El segundo eje define si la comunicación tiene un solo receptor o varios receptores:

  • Receptor único. Cada solicitud debe procesarse exactamente por un receptor o servicio. Un ejemplo de este tipo de comunicación es el patrón Command.

  • Varios receptores. Cada solicitud puede ser procesada por entre cero y varios receptores. Este tipo de comunicación debe ser asincrónico. Un ejemplo es el mecanismo de publicación y suscripción que se usa en patrones como la arquitectura controlada por eventos. Esto se basa en una interfaz de bus de eventos o un agente de mensajes al propagar actualizaciones de datos entre varios microservicios a través de eventos; normalmente se implementa a través de un bus de servicio o un artefacto similar, como Azure Service Bus , mediante temas y suscripciones.

Una aplicación basada en microservicios suele usar una combinación de estos estilos de comunicación. El tipo más común es la comunicación de un solo receptor con un protocolo sincrónico como HTTP/HTTPS al invocar un servicio HTTP de API web normal. Los microservicios también suelen usar protocolos de mensajería para la comunicación asincrónica entre microservicios.

Estos ejes son buenos para conocerlos, para que tenga claridad sobre los posibles mecanismos de comunicación, pero no son las preocupaciones importantes al construir microservicios. Ni la naturaleza asincrónica de la ejecución de subprocesos de cliente ni la naturaleza asincrónica del protocolo seleccionado son los puntos importantes al integrar microservicios. Lo que es importante es poder integrar los microservicios de forma asincrónica a la vez que se mantiene la independencia de los microservicios, como se explica en la sección siguiente.

La integración asincrónica de microservicios exige la autonomía del microservicio

Como se mencionó, el punto importante al crear una aplicación basada en microservicios es la forma en que se integran los microservicios. Lo ideal es intentar minimizar la comunicación entre los microservicios internos. Cuantos menos comunicaciones haya entre microservicios, mejor. En muchos casos, deberá integrar los microservicios de alguna forma. Cuando tenga que hacerlo, la regla crítica aquí es que la comunicación entre los microservicios debe ser asincrónica. Esto no significa que tenga que usar un protocolo específico (por ejemplo, mensajería asincrónica frente a HTTP sincrónico). Solo significa que la comunicación entre microservicios solo se debe realizar mediante la propagación de datos de forma asincrónica, pero intente no depender de otros microservicios internos como parte de la operación de solicitud/respuesta HTTP del servicio inicial.

Si es posible, nunca dependa de la comunicación sincrónica (solicitud/respuesta) entre varios microservicios, ni siquiera para las consultas. El objetivo de cada microservicio es ser autónomo y estar disponible para el consumidor del cliente, incluso si los demás servicios que forman parte de la aplicación de un extremo a otro están inactivos o están en mal estado. Si cree que necesita realizar una llamada de un microservicio a otros microservicios (como realizar una solicitud HTTP para una consulta de datos) para poder proporcionar una respuesta a una aplicación cliente, tiene una arquitectura que no será resistente cuando se produzca un error en algunos microservicios.

Además, tener dependencias HTTP entre microservicios, como al crear ciclos de solicitud y respuesta largos con cadenas de solicitudes HTTP, como se muestra en la primera parte de la figura 4-15, no solo hace que los microservicios no sean autónomos, sino que también su rendimiento se vea afectado en cuanto uno de los servicios de esa cadena no funciona bien.

Cuanto más agregue dependencias sincrónicas entre microservicios, como las solicitudes de consulta, peor será el tiempo de respuesta general para las aplicaciones cliente.

Diagrama que muestra tres tipos de comunicaciones entre microservicios.

Figura 4-15. Antipatrones y patrones en la comunicación entre microservicios

Como se muestra en el diagrama anterior, en la comunicación sincrónica se crea una "cadena" de solicitudes entre microservicios al atender la solicitud de cliente. Esto es un antipatrón. En los microservicios de comunicación asincrónica, use mensajes asincrónicos o sondeo http para comunicarse con otros microservicios, pero la solicitud de cliente se atiende inmediatamente.

Si el microservicio necesita generar una acción adicional en otro microservicio, si es posible, no realice esa acción de forma sincrónica y como parte de la operación original de solicitud y respuesta del microservicio. En su lugar, házlo de forma asincrónica (mediante mensajería asincrónica o eventos de integración, colas, etc.). Pero, tanto como sea posible, no invoque la acción de forma sincrónica como parte de la operación original de solicitud y respuesta sincrónica.

Y por último (y aquí es donde surgen la mayoría de los problemas al desarrollar microservicios), si tu microservicio inicial necesita datos que originalmente pertenecen a otros microservicios, no dependas de realizar solicitudes sincrónicas para esos datos. En su lugar, replique o propague esos datos (solo los atributos que necesita) en la base de datos del servicio inicial mediante la coherencia final (normalmente mediante eventos de integración, como se explica en las secciones próximas).

Como se indicó anteriormente en la sección Identificación de los límites del modelo de dominio para cada microservicio , duplicar algunos datos en varios microservicios no es un diseño incorrecto; por el contrario, al hacerlo, puede traducir los datos en el idioma o términos específicos de ese dominio adicional o contexto enlazado. Por ejemplo, en la aplicación eShopOnContainers tiene un microservicio denominado identity-api que se encarga de la mayoría de los datos del usuario con una entidad denominada User. Sin embargo, cuando necesite almacenar datos sobre el usuario dentro del Ordering microservicio, lo almacenará como una entidad diferente denominada Buyer. La Buyer entidad comparte la misma identidad con la entidad original User , pero puede tener solo los pocos atributos necesarios para el Ordering dominio y no todo el perfil de usuario.

Puede usar cualquier protocolo para comunicar y propagar datos de forma asincrónica entre microservicios con el fin de tener coherencia final. Como se mencionó, podría usar eventos de integración mediante un bus de eventos o un agente de mensajes, o incluso podría usar HTTP sondeando los otros servicios en su lugar. No importa. La regla importante es no crear dependencias sincrónicas entre los microservicios.

En las secciones siguientes se explican los varios estilos de comunicación que puede considerar usar en una aplicación basada en microservicios.

Estilos de comunicación

Hay muchos protocolos y opciones que puede usar para la comunicación, en función del tipo de comunicación que quiera usar. Si usa un mecanismo de comunicación sincrónico basado en solicitudes o respuestas, los protocolos como los enfoques HTTP y REST son los más comunes, especialmente si está publicando los servicios fuera del host de Docker o el clúster de microservicios. Si está comunicándose entre los servicios de manera interna (dentro del entorno Docker o del clúster de microservicios), también podría querer utilizar mecanismos de comunicación en formato binario (como WCF mediante TCP y formato binario). Como alternativa, puede usar mecanismos de comunicación asincrónicos basados en mensajes, como AMQP.

También hay varios formatos de mensaje como JSON o XML, o incluso formatos binarios, que pueden ser más eficaces. Si el formato binario elegido no es un estándar, probablemente no es una buena idea publicar públicamente los servicios con ese formato. Puede usar un formato no estándar para la comunicación interna entre los microservicios. Puede hacerlo al comunicarse entre microservicios dentro del host de Docker o el clúster de microservicios (por ejemplo, orquestadores de Docker) o para aplicaciones cliente propietarias que se comunican con los microservicios.

Comunicación de solicitud/respuesta con HTTP y REST

Cuando un cliente usa la comunicación de solicitud/respuesta, envía una solicitud a un servicio y, a continuación, el servicio procesa la solicitud y devuelve una respuesta. La comunicación de solicitud y respuesta es especialmente adecuada para consultar datos para una interfaz de usuario en tiempo real (una interfaz de usuario activa) desde aplicaciones cliente. Por lo tanto, en una arquitectura de microservicios probablemente usará este mecanismo de comunicación para la mayoría de las consultas, como se muestra en la figura 4-16.

Diagrama en el que se muestran las comms de solicitud y respuesta para consultas dinámicas y actualizaciones.

Figura 4-16. Uso de la comunicación de solicitud/respuesta HTTP (sincrónica o asincrónica)

Cuando un cliente usa la comunicación de solicitud/respuesta, se supone que la respuesta llegará en un breve tiempo, normalmente menos de un segundo o unos segundos como máximo. Para las respuestas retrasadas, debe implementar la comunicación asincrónica basada en patrones de mensajería y tecnologías de mensajería, que es un enfoque diferente que se explica en la sección siguiente.

Un estilo arquitectónico popular para la comunicación de solicitud/respuesta es REST. Este enfoque se basa en, y estrechamente acoplado, el protocolo HTTP, adoptando verbos HTTP como GET, POST y PUT. REST es el enfoque de comunicación arquitectónico más usado al crear servicios. Puede implementar servicios REST al desarrollar ASP.NET servicios de API web core.

Hay un valor adicional al usar servicios REST HTTP como lenguaje de definición de interfaz. Por ejemplo, si usa metadatos de Swagger para describir la API de servicio, puede usar herramientas que generan código auxiliar de cliente que puede detectar y usar directamente los servicios.

Recursos adicionales

Notificaciones push y comunicación en tiempo real basada en HTTP

Otra posibilidad (normalmente con fines diferentes a REST) es una comunicación de uno a varios en tiempo real con marcos de nivel superior, como ASP.NET SignalR y protocolos como WebSockets.

Como se muestra en la figura 4-17, la comunicación HTTP en tiempo real significa que puede tener código de servidor que inserta contenido en clientes conectados a medida que los datos estén disponibles, en lugar de tener que esperar al servidor a que un cliente solicite nuevos datos.

Diagrama que muestra las comunicaciones push y en tiempo real basadas en SignalR.

Figura 4-17. Comunicación de mensajes asincrónica de uno a varios en tiempo real

SignalR es una buena manera de lograr la comunicación en tiempo real para transmitir contenido a los clientes desde un servidor backend. Puesto que la comunicación está en tiempo real, las aplicaciones cliente muestran los cambios casi al instante. Normalmente, esto se controla mediante un protocolo como WebSockets, mediante muchas conexiones de WebSockets (una por cliente). Un ejemplo típico es cuando un servicio comunica un cambio en la puntuación de un juego deportivo a muchas aplicaciones web cliente simultáneamente.