En una aplicación monolítica que se ejecuta en un proceso único, los componentes se invocan entre sí utilizando llamadas a métodos o funciones que están a nivel de lenguaje. Estos pueden estar fuertemente acoplados si se están creando objetos con código (por ejemplo, new ClassName()) o se pueden invocar de forma desacoplada, si se está usando Inyección de Dependencias, al hacer referencia a abstracciones en lugar de instancias de objetos concretos. De cualquier manera, 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 las llamadas a métodos en llamadas RPC a los servicios generará una comunicación excesiva y poco eficiente, que no funcionará bien en entornos distribuidos. Los desafíos de diseñar un sistema distribuido de manera adecuada son tan conocidos que incluso existe un estándar de Las falacias de la computación distribuida, que enumera los supuestos que los desarrolladores suelen hacer al pasar de diseños monolíticos a diseños distribuidos.
No hay una solución, sino varias. Una opción es aislar los microservicios del negocio tanto como sea posible. A continuación, utilizar la comunicación asíncrona entre los microservicios internos y reemplazar la comunicación detallada, típica de la comunicación entre objetos, con una comunicación más agregada. Esto se puede hacer agrupando llamadas y devolviendo al cliente los datos que consolidan los resultados de varias llamadas internas.
Una aplicación basada en microservicios es un sistema distribuido que se ejecuta en múltiples procesos o servicios, generalmente incluso en múltiples servidores o hosts. Cada instancia de servicio es típicamente un proceso. Por lo tanto, los servicios deben interactuar utilizando un protocolo de comunicación entre procesos como HTTP, AMQP o un protocolo binario como TCP, según la naturaleza de cada servicio.
La comunidad de microservicios promueve la filosofía de "endpoints inteligentes y conexiones tontas". Este lema promueve un diseño lo más desacoplado posible entre los microservicios y lo más cohesivo posible dentro de un mismo microservicio. Como se explicó anteriormente, cada microservicio posee sus propios datos y lógica de dominio. Pero los microservicios que componen una aplicación de principio a fin, simplemente se organizan mediante el uso de comunicaciones REST, en lugar de protocolos complejos como WS-*, y comunicaciones flexibles basadas en eventos, en lugar de orquestadores de procesos de negocio centralizados.
Los dos protocolos utilizados comúnmente son petición/respuesta HTTP con APIs de recursos (principalmente para consultas) y mensajería asíncrona para realizar actualizaciones entre microservicios. Esto se explica con más detalle en las siguientes secciones.
Los clientes y los servicios pueden tener distintos tipos de comunicación, cada una de los cuales apunta a un escenario y objetivos diferentes. Inicialmente, esos tipos de comunicaciones se pueden clasificar en dos ejes o dimensiones.
El primer eje define si el protocolo es síncrono o asíncrono:
El segundo eje define si la comunicación tiene uno o múltiples receptores:
Normalmente se usa una combinación de estos estilos de comunicación, en una aplicación basada en microservicios. El tipo más común es la comunicación de un solo receptor con un protocolo síncrono como HTTP/HTTPS cuando se invoca un servicio API Web normal. Los microservicios también suelen utilizar protocolos de mensajería para la comunicación asíncrona entre ellos.
Es bueno conocer estas dos dimensiones, para tener claridad sobre los posibles mecanismos de comunicación, sin embargo, estás no son las preocupaciones importantes al desarrollar microservicios. Ni la naturaleza asíncrona de la ejecución ni del protocolo seleccionado, son los puntos importantes al integrar microservicios. Lo importante es poder integrar sus microservicios de forma asíncrona, manteniendo la independencia entre ellos, como se explica en la siguiente sección.
Como se mencionó anteriormente, el punto más importante al desarrollar una aplicación basada en microservicios es la integración de éstos. La idea principal es hacer lo posible para minimizar la comunicación entre los microservicios internos. Pero, por supuesto, en muchos casos se deben integrar de algún modo. Cuando se necesita esto, la regla clave es que la comunicación sea asíncrona. Eso no significa que se tenga que usar un protocolo específico (por ejemplo, mensajería asíncrona versus HTTP síncrono). Simplemente significa que la comunicación entre los microservicios se debe hacer sólo mediante la propagación de datos de forma asíncrona, haciendo todo lo posible para no depender de otros microservicios internos como parte de la petición/respuesta HTTP del servicio inicial.
Si es posible, nunca dependa de la comunicación síncrona (petición/respuesta) entre múltiples microservicios, ni siquiera para las consultas. El objetivo de cada microservicio es ser autónomo y estar disponible para el cliente, incluso si los otros servicios que forman parte de la totalidad de la aplicación están inactivos o funcionando mal. Si es necesario hacer una llamada desde un microservicio a otro (como realizar una petición HTTP para una consulta de datos) para poder proporcionar una respuesta a la aplicación cliente, entonces estaremos frente a una arquitectura que no será resiliente, cuando fallen algunos microservicios.
Además, tener dependencias HTTP entre microservicios, como cuando se crean cadenas largas de peticiones/respuestas HTTP, como se muestra en la primera parte de la Figura 4-15, no sólo hace que los microservicios no sean autónomos, sino que su rendimiento se vea afectado tan pronto como uno de los servicios en esa cadena no esté funcionando bien.
Cuantas más dependencias síncronas entre microservicios existan, tales como consultas u operaciones relacionadas, peor será el tiempo de respuesta general para las aplicaciones cliente.
Figura 4-15. Patrones y anti-patrones en la comunicación entre microservicios
Si un microservicio necesita iniciar una acción adicional en otro microservicio, si es posible, no lo haga de forma síncrona como parte de la petición/respuesta original. En cambio, hágalo de forma asíncrona (utilizando mensajes asíncronos o eventos de integración, colas, etc.).
Finalmente, y es aquí donde surgen la mayoría de los problemas al construir microservicios, si el microservicio inicial necesita datos que son propiedad de otros, no se debe confiar en realizar peticiones síncronas para obtenerlos. En su lugar, se deben replicar o propagar esos datos, pero sólo los atributos necesarios, a la base de datos del servicio inicial (el que recibe la petición inicial), mediante el uso de consistencia eventual (generalmente mediante eventos de integración, como se explica en las próximas secciones).
Como se señaló anteriormente en la sección Identificar los límites del modelo de dominio para cada microservicio, el duplicar algunos datos en varios microservicios no es un diseño incorrecto. Por el contrario, eso puede traducir los datos al lenguaje específico o a los términos de ese dominio adicional o Bounded Context. Por ejemplo, en la aplicación eShopOnContainers se tiene un microservicio llamado Identity.API que está a cargo de la mayoría de los datos del usuario con una entidad llamada User. Sin embargo, cuando es necesario guardar datos del usuario en del microservicio de Pedidos, se hace como una entidad diferente llamada Comprador. La entidad Comprador comparte la misma identificación con la entidad Usuario original, pero puede tener sólo los pocos atributos necesarios para el dominio Pedidos y no el perfil completo del usuario.
Para tener una consistencia eventual, se puede utilizar cualquier protocolo para comunicar y propagar datos de forma asíncrona entre microservicios. Como se mencionó anteriormente, se podrían usar eventos de integración con bus de eventos o un message broker o, incluso, se podría usar HTTP polling hacia otros servicios. En realidad, no importa. La importante es no crear dependencias síncronas entre los microservicios.
En las secciones siguientes se explican los múltiples estilos de comunicación que se pueden usar en una aplicación basada en microservicios.
Existen muchos protocolos y opciones que pueden usar, según el tipo de comunicación que se requiera. Si se está usando un mecanismo basado en petición/respuesta síncrona, el protocolo HTTP y el enfoque REST son los más comunes, especialmente si los servicios están publicados fuera del host Docker o del cluster de microservicios. Para la comunicación interna entre servicios (dentro del servidor Docker o cluster de microservicios), también pueden utilizar mecanismos de comunicación binarios, como la comunicación remota de Service Fabric o WCF utilizando el protocolo TCP. Por otro lado, también se pueden usar mecanismos asíncronos basados en mensajes como AMQP.
También hay múltiples formatos de mensaje como JSON o XML, o incluso formatos binarios, que pueden ser más eficientes. Si el formato binario seleccionado no es un estándar, probablemente no sea una buena idea publicar los servicios con ese formato. Se puede usar un formato no estándar para la comunicación interna entre los microservicios, si trabaja dentro de un host Docker o cluster de microservicio (orquestadores Docker o Azure Service Fabric), o para aplicaciones cliente propietarias que hablen con los microservicios.
Cuando un cliente usa la comunicación petición/respuesta, envía una petición a un servicio, luego el servicio procesa la procesa y envía una respuesta. La comunicación petición/respuesta es especialmente adecuada para consultar datos de una interfaz de usuario en tiempo real desde las aplicaciones cliente. Por lo tanto, en una arquitectura de microservicio probablemente usará este mecanismo de comunicación para la mayoría de las consultas, como se muestra en la Figura 4-16.
Figura 4-16. Usando comunicación por petición/respuesta HTTP (síncrona o asíncrona)
Cuando un cliente usa la comunicación de petición/respuesta, supone que la respuesta llegará en un tiempo corto, generalmente menos de un segundo o unos pocos segundos como máximo. Para las respuestas con demora, se debe implementar una comunicación asíncrona basada en patrones de mensajes y tecnologías de mensajería, que es un enfoque diferente que explicamos en la siguiente sección.
Un estilo arquitectónico popular para la comunicación petición/respuesta es REST. Este enfoque se basa en el protocolo HTTP y está estrechamente relacionado con él, comprende los verbos HTTP como GET, POST y PUT. REST es el enfoque de comunicación que se utiliza con más frecuencia al crear servicios. Se pueden implementar servicios REST al trabajar con ASP.NET Core Web API.
Hay una ventaja adicional al usar los servicios HTTP REST como el lenguaje de definición de la interfaz. Por ejemplo, si se utilizan los metadatos de Swagger para describir la API del servicio, se pueden usar herramientas que generen stubs para los clientes, que pueden descubrir y consumir directamente los servicios.
Otra posibilidad (generalmente para usos diferentes de REST) es una comunicación en tiempo real y uno a muchos con frameworks de nivel superior como ASP.NET SignalR y protocolos como WebSockets.
Como muestra la Figura 4-17, la comunicación HTTP en tiempo real significa que el servidor puede enviar el contenido a los clientes conectados, a medida que los datos estén disponibles, en lugar de que el servidor espere las peticiones de nuevos datos por los clientes.
Figura 4-17. Comunicación asíncrona por mensaje en tiempo real uno-a-muchos
Dado que la comunicación se realiza en tiempo real, las aplicaciones cliente muestran los cambios casi instantáneamente. Esto generalmente es manejado por un protocolo como WebSockets, usando muchas conexiones, una por cliente. Un ejemplo típico es cuando un servicio comunica un cambio en el puntaje de un juego de deportes a muchas aplicaciones web cliente simultáneamente.
La mensajería asíncrona y la comunicación orientada a eventos, son fundamentales cuando se propagan cambios entre múltiples microservicios a sus modelos de dominio relacionados. Como se mencionó anteriormente en la discusión de microservicios y Bounded Contexts (BC), los modelos (Usuario, Cliente, Producto, Cuenta, etc.) pueden significar diferentes cosas para diferentes microservicios o BC. Eso significa que cuando se producen cambios, se necesita alguna forma de reconciliar los cambios en los diferentes modelos. Una solución es la consistencia eventual y la comunicación basada en eventos basada en mensajes asíncronos.
Al usar mensajería, los procesos se comunican intercambiando mensajes de forma asíncrona. Un cliente realiza un comando o una petición a un servicio enviándole un mensaje. Si el servicio necesita responder, envía un mensaje diferente al cliente. Como se trata de una comunicación basada en mensajes, el cliente supone que la respuesta no se recibirá de inmediato y que es posible que no haya respuesta.
Un mensaje está compuesto por un encabezado (metadatos como identificación o información de seguridad) y un cuerpo. Los mensajes se envían generalmente a través de protocolos asíncronos como AMQP.
La infraestructura preferida para este tipo de comunicación en la comunidad de microservicios es un message broker liviano, que es distinto de los grandes brokers y orquestadores utilizados en SOA. En un bróker ligero de mensajería, la infraestructura suele ser "tonta", actuando sólo como un intermediario, con implementaciones simples como RabbitMQ o un bus de servicio escalable en la nube como Azure Service Bus. En este escenario, la mayoría del procesamiento "inteligente" aún vive en los endpoints que producen y consumen mensajes, es decir, en los microservicios.
Otra regla que se debe seguir, en la medida de lo posible, es usar sólo mensajes asíncronos entre los servicios internos y usar comunicación síncrona (como HTTP) sólo desde las aplicaciones cliente a los servicios de interfaz de usuario (API Gateways más el primer nivel de microservicios).
Hay dos tipos de mensajería asíncrona: comunicación basada en mensajes de receptor único o en mensajes de receptores múltiples. En las siguientes secciones entraremos en detalles sobre ellos.
La comunicación asincrónica basada en mensajes con un receptor único, significa que hay una comunicación punto a punto que envía un mensaje a exactamente uno de los consumidores que está leyendo desde el canal y que el mensaje se procesa sólo una vez. Sin embargo, hay situaciones especiales. Por ejemplo, en un sistema en la nube que intenta recuperarse automáticamente de fallas, el mismo mensaje podría enviarse varias veces. Debido a la red u otras fallas, el cliente debe poder volver a intentar el envío de mensajes y el servidor debe implementar la lógica necesaria para que una operación sea idempotente, para que cada mensaje sólo se procese una vez.
La comunicación basada en mensajes de un receptor único, es especialmente adecuada para enviar comandos asíncronos de un microservicio a otro, como se muestra en la Figura 4-18 que ilustra este enfoque.
Una vez que se comience a enviar comunicaciones basadas en mensajes (ya sea con comandos o eventos), se debe evitar mezclarlas con la comunicación HTTP síncrona.
Figura 4-18. Un microservicio recibiendo un mensaje asíncrono
Tenga en cuenta que cuando los comandos provienen de aplicaciones cliente, se pueden implementar como comandos síncronos HTTP. Se deben usar comandos basados en mensajes cuando se necesite mayor escalabilidad o cuando ya se esté trabajando en un proceso del negocio basado en mensajes.
Para lograr un enfoque más flexible, también se puede usar un mecanismo de publicación/suscripción para que un mensaje del remitente esté disponible para los microservicios subscriptores o para aplicaciones externas. Esto le ayuda a seguir el principio open/closed en el servicio de envío. De esta forma, se pueden agregar suscriptores adicionales en el futuro sin la necesidad de modificar el servicio del remitente.
Cuando utiliza una comunicación por publicación/suscripción, es posible que esté utilizando una interfaz tipo bus de eventos para publicar eventos hacia cualquier suscriptor.
Cuando se utiliza una comunicación asíncrona basada en eventos, un microservicio publica un evento de integración cuando ocurre algo dentro de su dominio y otro microservicio debe enterarse de ello, como un cambio de precio en un microservicio de catálogo de productos. Los microservicios adicionales se suscriben a los eventos para que puedan recibirlos de forma asíncrona. Cuando eso sucede, los receptores pueden actualizar sus propias entidades de dominio, lo que puede provocar que se publiquen más eventos de integración. Este sistema de publicación/suscripción generalmente se realiza mediante una implementación de un bus de eventos. El bus de eventos se puede diseñar como una abstracción o interfaz, con la API que se necesita para publicar eventos y suscribirse o anular la suscripción a eventos. El bus de eventos también puede tener una o más implementaciones basadas en cualquier bróker entre procesos y mensajería, como una cola de mensajería o un bus de servicio que admite comunicación asíncrona y un modelo de publicación/suscripción.
Se recomienda que sea completamente claro para el usuario final, si un sistema utiliza consistencia eventual manejada por eventos de integración. El sistema no debe usar un enfoque que imite los eventos de integración, usando SignalR o polling desde el cliente. Tanto los usuarios finales como el dueño del negocio deben aceptar explícitamente la consistencia eventual en el sistema y darse cuenta de que, en muchos casos, esto no genera ningún problema para el negocio, siempre que esto sea explícito. Esto es importante porque los usuarios podrían esperar ver algunos resultados de inmediato y esto puede no ocurrir con la consistencia eventual.
Como se señaló anteriormente en la sección de Retos y soluciones para la administración de datos distribuidos, se pueden usar eventos de integración para implementar tareas del negocio que abarcan múltiples microservicios. Por lo tanto, tendrá una consistencia eventual entre esos servicios. Una transacción eventualmente consistente se compone de una colección de acciones distribuidas. En cada acción, el microservicio relacionado actualiza una entidad de dominio y publica otro evento de integración que plantea la siguiente acción dentro de la misma tarea del negocio, considerada como un todo.
Un punto importante es que se puede necesitar comunicación con múltiples microservicios que están suscritos al mismo evento. Para hacerlo, se pueden usar los mensajes de publicación/suscripción basados en eventos, como se muestra en la Figura 4-19. Este mecanismo de publicación/suscripción no es exclusivo de la arquitectura de microservicios. Es similar a la forma en que deben comunicarse los Bounded Contexts en DDD, o la forma en que propagan las actualizaciones desde la base de datos de escritura hacia la base de datos de consulta en el patrón arquitectónico Command and Query Responsibility Segregation (CQRS). El objetivo es tener una consistencia eventual entre múltiples fuentes de datos en un sistema distribuido.
Figura 4-19. Comunicación asíncrona por mensaje basada en eventos
La implementación determinará qué protocolo usar para las comunicaciones basadas en mensajes y basadas en eventos. AMQP puede ayudar a lograr una comunicación confiable a través de una cola.
Cuando se usa un bus de eventos, se puede usar un nivel de abstracción (como una interfaz de bus de eventos) basado en una implementación usando la API de un message broker como RabbitMQ o un bus de servicio como Azure Service Bus with Topics. También se puede usar un bus de servicio de nivel superior como NServiceBus, MassTransit o Brighter para coordinar el bus de eventos y el sistema de publicación/suscripción.
Hay diferentes niveles en las tecnologías de mensajería disponibles para implementar un bus de eventos abstracto. Por ejemplo, productos como RabbitMQ y Azure Service Bus se ubican en un nivel inferior al de otros productos como, NServiceBus, MassTransit o Brighter, que pueden funcionar sobre RabbitMQ y Azure Service Bus. La elección depende de la cantidad de características avanzadas en el nivel de aplicación y la escalabilidad que necesita para su aplicación. Para implementar sólo un bus de eventos como prueba de concepto para su entorno de desarrollo, como lo hemos hecho en el ejemplo de eShopOnContainers, podría ser suficiente una implementación simple sobre RabbitMQ ejecutándose en un contenedor Docker.
Sin embargo, para los sistemas de misión crítica y de producción que necesitan alta escalabilidad, se debería evaluar el Azure Service Bus. Para abstracciones de mayor nivel y características que facilitan el desarrollo de aplicaciones distribuidas, recomendamos evaluar otros buses de servicio comerciales y open source, como NServiceBus, MassTransit y Brighter. Por supuesto, también se pueden construir funciones propias de bus de servicio usando tecnologías de menor nivel de abstracción como RabbitMQ y Docker. Pero ese trabajo de fontanería puede resultar demasiado costoso para una aplicación empresarial a la medida.
Un desafío al implementar una arquitectura basada en eventos entre múltiples microservicios, es cómo actualizar atómicamente el estado en el microservicio original y publicar de forma flexible su evento de integración relacionado en el bus de eventos, basado de alguna manera en transacciones. Estas son algunas formas de lograrlo, aunque también podría haber otros enfoques.
Otros Temas que se deben considerar cuando se usa comunicación asíncrona, son la idempotencia del mensaje y la deduplicación del mensaje. Estos temas se tratan en la sección Implementación de comunicación basada en eventos entre microservicios (eventos de integración) más adelante en esta guía.
Una API es un contrato entre el servicio y sus clientes. Un microservicio podrá evolucionar de forma independiente sólo si no rompe el contrato de su API y es por esto que el contrato es tan importante. Si cambia el contrato, tendrá un impacto en sus aplicaciones cliente o en su API Gateway.
La naturaleza de la definición de la API depende del protocolo que esté utilizando. Por ejemplo, si usa mensajes (como AMQP), la API está definida por los tipos de mensajes. Si está utilizando servicios HTTP y RESTful, la API está definida por las URL y los formatos JSON de petición y respuesta.
Sin embargo, incluso si define cuidadosamente a su contrato inicial, una API de servicio deberá evolucionar con el tiempo. Cuando eso sucede y especialmente si la API es pública y consumida por múltiples aplicaciones cliente, normalmente no se puede obligar a todos los clientes a actualizarse al nuevo contrato API. Por lo general, se necesita desplegar incrementalmente las nuevas versiones de un servicio, de manera que las versiones antiguas y nuevas de un contrato de servicio se ejecuten simultáneamente. Por lo tanto, es importante tener una estrategia para el control de versiones de la API del servicio.
Cuando los cambios de API son pequeños, por ejemplo, si agregan atributos o parámetros, los clientes que usan una API anterior deberían poder cambiar y trabajar con la nueva versión del servicio. Es posible que pueda proporcionar valores predeterminados para cualquier atributo faltante que se requiera y los clientes podrían ignorar cualquier atributo de respuesta adicional.
Sin embargo, a veces es necesario realizar cambios importantes e incompatibles a una API de servicio. Como es posible que no pueda obligar a las aplicaciones o servicios clientes a actualizarse de inmediato a la nueva versión, un servicio debe admitir versiones anteriores de la API por algún tiempo. Si está utilizando un mecanismo basado en HTTP, como REST, un enfoque es insertar el número de versión de la API en la URL o en un encabezado HTTP. Luego, puede decidir entre implementar ambas versiones del servicio simultáneamente en la misma instancia de servicio o implementar instancias diferentes y que cada una maneje una versión de la API. Una buena solución para esto es el patrón Mediator (por ejemplo, la librería MediatR) para desacoplar las diferentes versiones de implementación en manejadores independientes.
Finalmente, si está utilizando una arquitectura REST, Hypermedia es la mejor solución para versionar sus servicios y permitir API evolutivas.
Cada microservicio tiene un nombre único (URL) que se usa para resolver su ubicación. Un microservicio debe ser direccionable, es decir, debe tener una dirección para poder llegar a él, donde sea que se esté ejecutando. Si tiene que pensar en qué computadora está ejecutando un microservicio en particular, las cosas pueden ir mal rápidamente. De la misma manera que el DNS resuelve una URL a una computadora en particular, un microservicio necesita tener un nombre único para que su ubicación actual sea reconocible. Los microservicios necesitan nombres direccionables que los hagan independientes de la infraestructura en la que se ejecutan. Esto implica que hay una interacción entre cómo se implementa su servicio y cómo se descubre, porque es necesario que haya un registro de servicios. En el mismo sentido, cuando una computadora falla, el registro de servicios debe poder indicar dónde se está ejecutando el servicio.
El patrón de registro de servicios es una parte clave del descubrimiento de servicios. El registro es una base de datos que contiene las ubicaciones de red de las instancias de servicio. Un registro de servicios necesita estar altamente disponible y actualizado. Los clientes pueden almacenar en caché las ubicaciones de red obtenidas del registro de servicio. Sin embargo, esa información finalmente se desactualiza y los clientes ya no pueden descubrir instancias de servicio. En consecuencia, un registro de servicio consta de un cluster de servidores que utiliza un protocolo de replicación para mantener la consistencia.
El servicio de descubrimiento de microservicios está incorporado en algunos entornos de despliegue (llamados clusters, que se tratarán en una sección posterior). Por ejemplo, en un entorno de Azure Container Service, Kubernetes y DC/OS con Marathon, se puede gestionar el registro y la eliminación de las instancias del servicio. También ejecutan un proxy en cada host del cluster que funciona como enrutador de descubrimiento del lado del servidor. Otro ejemplo es el Azure Service Fabric, que también proporciona un registro de servicios a través de su servicio de nombres.
Tenga en cuenta que existe cierto solapamiento entre el registro de servicio y el patrón API Gateway, lo que también ayuda a resolver este problema. Por ejemplo, el Service Fabric Reverse Proxy es una implementación de API Gateway que se basa en el Service Fabric Naming Service y que ayuda a resolver la dirección a los servicios internos.
La arquitectura de microservicios a menudo comienza con el manejo de los datos y la lógica del lado del servidor. Sin embargo, un enfoque más avanzado es diseñar la interfaz de usuario (UI) basada también en microservicios. Eso significa tener una interfaz de usuario compuesta producida por los microservicios, en lugar de tener microservicios en el servidor y una aplicación cliente monolítica que consuma los microservicios. Con este enfoque, se pueden construir microservicios completos, que incluyan tanto la lógica como la presentación visual.
La figura 4-20 muestra el enfoque más sencillo, que consiste simplemente en consumir microservicios desde una aplicación cliente monolítica. Por supuesto, podría tener un servicio ASP.NET MVC de por medio, generando el HTML y JavaScript. La figura es una simplificación donde se destaca que tiene una interfaz de usuario única y monolítica, que consume los microservicios, que sólo se enfocan en la lógica y los datos y no en la interfaz de usuario (HTML y JavaScript).
Figura 4-20. Una interfaz de usuario monolítica consumiendo microservicios del back-end
En contraste, una interfaz de usuario compuesta es, precisamente, generada y compuesta por los mismos microservicios. Algunos de los microservicios controlan el aspecto visual de áreas específicas de la interfaz de usuario. La diferencia clave es que tiene componentes de interfaz de usuario de cliente (por ejemplo, clases de TypeScript) basados en plantillas y el ViewModel para esas plantillas proviene de cada microservicio.
Cuando arranca la aplicación cliente, cada uno de los componentes de la interfaz de usuario (clases de TypeScript, por ejemplo) se registran con un microservicio de infraestructura capaz de proporcionar los ViewModels para un escenario determinado. Si el microservicio cambia la presentación, la interfaz de usuario cambia también.
La Figura 4-21 muestra una versión de este enfoque de interfaz de usuario compuesta. Esto es una vista simplificada, porque es posible que otros microservicios estén consolidando partes más detalladas, dependiendo de las tecnologías que se estén usando, por ejemplo, si está creando una aplicación web tradicional (ASP.NET MVC) o una SPA (Single Page Application).
Figura 4-21. Ejemplo de una UI compuesta definida por microservicios de back-end
Cada uno de esos microservicios de composición de interfaz de usuario sería similar a una pequeña API Gateway. Pero en este caso, cada uno es responsable de un área pequeña de interfaz de usuario.
Un enfoque de interfaz de usuario compuesta, manejado por microservicios, puede ser más o menos desafiante, dependiendo de las técnicas de front-end que se utilicen. Por ejemplo, no usará las mismas técnicas para construir una aplicación web tradicional, para crear una SPA o una aplicación móvil nativa (como cuando se desarrollan aplicaciones Xamarin, que puede ser todavía más desafiante para este enfoque).
La aplicación de referencia eShopOnContainers utiliza el enfoque de interfaz de usuario monolítica por múltiples razones. Primero, es una introducción a microservicios y contenedores. Una interfaz de usuario compuesta no sólo es más avanzada, sino que también tiene una mayor complejidad al diseñarla y desarrollarla. En segundo lugar, eShopOnContainers también proporciona una aplicación móvil nativa basada en Xamarin, lo que la haría más compleja en el lado del cliente (en C#).
Sin embargo, recomendamos las siguientes referencias para obtener más información sobre la interfaz de usuario compuesta basada en microservicios.
Lidiar con fallas inesperadas es uno de los problemas más difíciles de resolver, especialmente en un sistema distribuido. Gran parte del código que se escribe involucra el manejo de excepciones y ahí es también donde se gasta más tiempo en las pruebas. El problema es más complicado que sólo escribir el código para manejar las fallas. ¿Qué sucede cuando la máquina donde se ejecuta el microservicio falla? No sólo se necesita detectar la falla del microservicio (un problema difícil en sí mismo), sino que también se necesita algo para reiniciarlo.
Un microservicio debe ser resiliente a las fallas y ser capaz de reiniciarse, a menudo en otra máquina por un tema de disponibilidad. Esta resiliencia también debe incluir el estado que se guardó en nombre del microservicio, desde dónde puede el microservicio recuperar este estado y si el microservicio se puede reiniciar con éxito. En otras palabras, es necesario que haya resiliencia en la capacidad de cómputo (el proceso debe poder reiniciarse en cualquier momento), así como también resiliencia en el estado o los datos, es decir manteniendo la consistencia e integridad de los datos.
Los problemas de resiliencia se combinan, y amplifican, en otros escenarios, como cuando ocurren fallas durante una actualización de la aplicación. El microservicio, trabajando con el sistema desplegado, necesita determinar si puede avanzar hacia la versión más nueva o, en su lugar, regresar a una versión anterior para mantener un estado consistente. Se deben tener en cuenta cuestiones como si hay suficientes máquinas disponibles para seguir avanzando y cómo recuperar versiones anteriores del microservicio. Esto requiere que el microservicio emita información de su estado de salud, para que la aplicación general y el orquestador puedan tomar estas decisiones.
Además, la resiliencia está relacionada con el comportamiento esperado de los sistemas basados en la nube. Como se mencionó anteriormente, un sistema basado en la nube debe manejar fallas y debe intentar recuperarse automáticamente de ellas. Por ejemplo, en caso de fallas en la red o en el contenedor, las aplicaciones o servicios cliente deben tener una estrategia para volver a intentar el envío de mensajes o peticiones, ya que en muchos casos las fallas en la nube son parciales. La sección Implementación de aplicaciones resilientes en esta guía, aborda cómo manejar fallas parciales. Describe técnicas como reintentos con retroceso exponencial o el patrón Interruptor Automático en .NET Core, mediante el uso de librerías como Polly, que ofrece una gran variedad de políticas para manejar este tema.
Puede parecer obvio, pero a menudo se pasa por alto, que un microservicio debe informar sobre su estado de salud y eventos de diagnóstico. De lo contrario, hay poca información desde una perspectiva de operaciones. La correlación de eventos de diagnóstico a través de un conjunto de servicios independientes y el manejo de las variaciones del reloj de la máquina, para dar sentido al orden de los eventos es todo un desafío. De la misma manera como se interactúa con un microservicio, en base a protocolos y formatos acordados, es necesario estandarizar cómo registrar eventos de diagnóstico y de salud, que finalmente terminarán en un almacén de eventos para consulta y visualización. En un enfoque de microservicios, es clave estandarizar en un formato único de registro. Es necesario que haya un enfoque coherente para consultar los eventos de diagnóstico de la aplicación.
El estado de salud es diferente de los diagnósticos. La salud se refiere a que el microservicio informe su estado actual para tomar las medidas adecuadas. Un buen ejemplo es cuando trabaja con los mecanismos de actualización y despliegue para mantener la disponibilidad. Aunque un servicio actualmente no esté saludable debido a un bloqueo del proceso o reinicio de la máquina, el servicio aún podría estar operativo y lo último que necesita es empeorar esto realizando una actualización. El mejor enfoque es hacer primero una investigación o esperar un tiempo para que se recupere el microservicio. Los eventos de salud de un microservicio ayudan a tomar decisiones informadas y, de hecho, ayudan a crear servicios de capaces de recuperarse por sí mismos.
En la sección Implementando controles del estado de salud en servicios ASP.NET Core de esta guía, explicamos cómo usar la nueva librería ASP.NET HealthChecks (que probablemente se incluya en ASP.NET Core 2.1) en sus microservicios, para que puedan informar su estado a un servicio de monitorización para tomar las medidas adecuadas cuando sea necesario.
Los logs de eventos proporcionan información sobre cómo se está ejecutando una aplicación o servicio, incluidas excepciones, advertencias y mensajes informativos simples. Por lo general, cada registro está en formato de texto con una línea por evento, aunque las excepciones también muestran a menudo el seguimiento de la pila en varias líneas.
En aplicaciones monolíticas basadas en servidor, simplemente se pueden escribir registros en un fichero en el disco y luego analizarlo con cualquier herramienta. Como la ejecución de la aplicación está limitada a un servidor o máquina virtual únicos, generalmente no es demasiado complejo analizar el flujo de eventos. Sin embargo, en una aplicación distribuida en la que se ejecutan múltiples servicios en muchos nodos de un cluster, poder correlacionar los eventos distribuidos es todo un desafío.
Una aplicación basada en microservicios no debe tratar de almacenar la secuencia de salida de eventos o ficheros de registro por sí mismo, ni siquiera tratar de administrar el enrutamiento de los eventos a un lugar central. Esto debe ser transparente, lo que significa que cada proceso debe simplemente escribir su flujo de eventos a una salida estándar, que será recolectado por la infraestructura del entorno donde se está ejecutando. Un ejemplo de estos enrutadores de flujos de eventos es Microsoft.Diagnostic.EventFlow, que recopila los flujos de eventos de múltiples fuentes y las publica en sistemas de salida. Estos pueden incluir salidas estándar simples para un entorno de desarrollo o sistemas en la nube como Application Insights, OMS (para aplicaciones on-premises) y Azure Diagnostics. También hay buenas plataformas y herramientas de análisis de registros de terceros que pueden buscar, alertar, monitorizar los registros e informar, incluso en tiempo real, como Splunk.
Cuando se crea una aplicación basada en microservicios, es necesario manejar la complejidad. Por supuesto, que un microservicio único es fácil de manejar, pero docenas o cientos de tipos y miles de instancias de microservicios representan un problema complejo. No se trata sólo de construir una arquitectura de microservicio; también se necesita alta disponibilidad, capacidad de direccionamiento, resiliencia, control del estado de salud y diagnósticos si se espera mantener un sistema estable y consistente.
Figura 4-22. Una plataforma de Microservicios es fundamental para la gestión de la salud de una aplicación
Los problemas complejos que se muestran en la Figura 4-22 son muy difíciles de resolver por uno mismo. Los equipos de desarrollo deben enfocarse en resolver problemas del negocio y crear aplicaciones personalizadas con enfoques basados en microservicios. No deberían enfocarse en resolver problemas complejos de infraestructura. Si lo hicieran, el costo de cualquier aplicación basada en microservicios sería enorme. Por eso, existen plataformas orientadas a microservicios, denominadas orquestadores o clusters de microservicio, que se enfocan en resolver los problemas difíciles, en cuanto a ejecutar un servicio y utilizar los recursos de infraestructura de manera eficiente. Esto reduce la complejidad de crear aplicaciones que utilizan un enfoque de microservicios.
Distintos orquestadores pueden sonar similares, pero los diagnósticos y controles de salud ofrecidos por cada uno de ellos difieren en características y estado de madurez, a veces, dependiendo de la plataforma del sistema operativo, como se explica en la siguiente sección.
https://github.com/Azure/diagnostics-eventflow
https://docs.microsoft.com/azure/azure-diagnostics
https://docs.microsoft.com/azure/log-analytics/log-analytics-windows-agents
https://msdn.microsoft.com/library/dn440729(v=pandp.60).aspx
https://msdn.microsoft.com/library/system.diagnostics.tracing.eventsource(v=vs.110).aspx
Es esencial usar orquestadores en el entorno de producción, si las aplicaciones se basan en microservicios o simplemente se dividen en varios contenedores. Como se presentó anteriormente, en un enfoque basado en microservicios, cada microservicio posee su modelo y datos para que sea autónomo desde el punto de vista del desarrollo y despliegue. Pero incluso con una aplicación más tradicional que se compone de servicios múltiples, como SOA, también tendrá múltiples contenedores o servicios que componen la aplicación del negocio y que deben desplegarse como un sistema distribuido. Este tipo de sistemas son complejos de escalar y administrar, por lo tanto y sin lugar a dudas, es necesario un orquestador si desea tener una aplicación multi-contenedores y escalable en producción.
La Figura 4-23 muestra el despliegue en un cluster de una aplicación compuesta de múltiples microservicios en contenedores.
Figura 4-23. Un cluster de contenedores
Este parece un enfoque lógico. Pero, ¿cómo se maneja el balanceo de carga, el enrutamiento y la orquestación de estas aplicaciones compuestas?
El Docker Engine básico en hosts Docker individuales, cumple con las necesidades de gestión de instancias de una sola imagen en un host, pero esto no es suficiente cuando se trata de administrar varios contenedores implementados en múltiples hosts para aplicaciones más complejas y distribuidas. En la mayoría de los casos, se necesita una plataforma de administración que inicie automáticamente los contenedores, los escale creando varias instancias de una imagen y los suspenda o apague cuando sea necesario e incluso, controle cómo acceden a recursos como la red y el almacenamiento de datos.
Para ir más allá de la administración de contenedores individuales o aplicaciones compuestas muy simples y avanzar hacia aplicaciones empresariales más grandes con microservicios, es necesario usar plataformas de orquestación y clustering.
Desde el punto de vista de la arquitectura y el desarrollo, si se están creando aplicaciones empresariales grandes, compuestas por aplicaciones basadas en microservicios, es importante comprender las siguientes plataformas y productos que soportan escenarios avanzados:
Clusters y orquestadores. Cuando se necesitan escalar aplicaciones en muchos hosts Docker, como cuando se trata de una aplicación grande basada en microservicios, es crítico poder administrar todos esos hosts como un cluster único, abstrayéndose la complejidad de la plataforma subyacente. Eso es lo que proporcionan los orquestadores y clusters de contenedores. Algunos ejemplos de orquestadores son Azure Service Fabric, Kubernetes, Docker Swarm y Mesosphere DC/OS. Los últimos tres orquestadores, open source, están disponibles en Azure a través del Azure Container Service.
Programadores. La programación significa tener la capacidad de que un administrador inicie contenedores en un cluster, así que los programadores también proporcionan una interfaz de usuario. Un programador de cluster tiene varias responsabilidades: usar los recursos del cluster de manera eficiente, establecer las restricciones indicadas por el usuario, balancear la carga de contenedores de manera eficiente entre los nodos o hosts, ser robusto contra errores y, a su vez, proporcionar alta disponibilidad.
Los conceptos de cluster y programador están estrechamente relacionados, por lo que los productos ofrecidos por diferentes proveedores suelen proporcionar ambas capacidades. La siguiente lista muestra las opciones de plataforma y software más importantes, para clusters y programadores. Estos orquestadores generalmente se ofrecen en nubes públicas como Azure.
Varios proveedores de servicios en la nube ofrecen soporte para orquestación de contenedores y clusters Docker, incluidos Microsoft Azure, Amazon EC2 Container Service y Google Container Engine. Microsoft Azure ofrece soporte para orquestación y clusters Docker a través del Azure Container Service (ACS), como se explica en la sección siguiente.
Otra opción es utilizar Microsoft Azure Service Fabric (una plataforma de microservicios), que también es compatible con Docker en Linux y contenedores Windows. Service Fabric se ejecuta en Azure o en cualquier otra nube y también se puede ejecutar en las instalaciones internas (on-premises).
Un cluster Docker agrupa varios hosts Docker y los expone como un host Docker virtual único, para poder desplegar varios contenedores. El cluster manejará toda la fontanería compleja de gestión, como la escalabilidad, estado de salud, etc. La Figura 4-23 muestra cómo se asigna un cluster Docker para aplicaciones compuestas a Azure Container Service (ACS).
ACS proporciona una forma de simplificar la creación, configuración y administración de un cluster de máquinas virtuales que están preconfiguradas para ejecutar aplicaciones contenerizadas. Con una configuración optimizada de las herramientas de programación y orquestación populares open source, ACS permite utilizar las habilidades existentes o recurrir a un cuerpo de expertos de la comunidad, cada vez más grande, para implementar y gestionar aplicaciones basadas en contenedores en Microsoft Azure.
Azure Container Service optimiza la configuración de las herramientas y tecnologías populares open source, para clusters Docker, específicamente para Azure. Se puede obtener una solución abierta que ofrece portabilidad tanto para sus contenedores como para la configuración de las aplicaciones. Basta con seleccionar el tamaño, la cantidad de hosts y las herramientas del orquestador y el ACS maneja todo lo demás.
Figur 4-24. Opciones para manejar clusters en Azure Container Service
El ACS aprovecha las imágenes de Docker para garantizar que los contenedores de su aplicación sean totalmente portátiles. Permite la elección de plataformas de orquestación open source como DC/OS (con Apache Mesos), Kubernetes (creado originalmente por Google) y Docker Swarm, para garantizar que estas aplicaciones se puedan escalar a miles o incluso a decenas de miles de contenedores.
El servicio Azure Container le permite aprovechar las características de nivel empresarial de Azure, manteniendo la portabilidad de las aplicaciones, incluso a nivel de las capas de orquestación.
Figura 4-25. Orquestadores en ACS
Como se muestra en la Figura 4-25, Azure Container Service es simplemente la infraestructura provista por Azure para implementar DC/OS, Kubernetes o Docker Swarm, pero ACS no implementa ningún orquestador adicional. Por lo tanto, ACS no es un orquestador como tal, sino sólo una infraestructura que aprovecha los orquestadores open source existentes.
El objetivo de Azure Container Service, desde un punto de vista de uso, es proporcionar un entorno de alojamiento de contenedores usando herramientas y tecnologías populares open source. Con este fin, expone los endpoints estándar del API para el orquestador seleccionado. Al usar estos endpoints, se puede aprovechar cualquier software que pueda comunicarse con ellos. Por ejemplo, en el caso de Docker Swarm, puede optar por usa la interfaz de línea de comandos Docker (CLI) y para DC/OS, también puede usar su CLI.
Para comenzar a usar Azure Container Service, se debe desplegar un cluster de Azure Container Service desde el Azure Portal, usando una plantilla de Azure Resource Manager o la CLI. Las plantillas disponibles incluyen Docker Swarm, Kubernetes, y DC/OS. Las plantillas de inicio rápido se pueden modificar para incluir una configuración adicional o avanzada de Azure. Para obtener más información sobre el despliegue de un cluster de Azure Container Service, vea Implementar un cluster de Azure Container Service.
No se incurre en costes adicionales por el software instalado por defecto como parte de ACS. Todas las opciones predeterminadas se implementan con software open source.
Actualmente, ACS está disponible para las máquinas virtuales Linux de las series A, D, DS, G y GS en Azure. Sólo se le cobrará por las instancias de cómputo que elija, así como también por los demás recursos de infraestructura subyacentes consumidos, como almacenamiento y redes. No hay cargos adicionales para ACS.
Actualmente Azure Container Services se está orientando principalmente hacia Kubernetes y se maneja como AKS. ACS, que todavía está disponible, maneja Docker Swarm and DC/OC, además de Kubernetes, como ya se ha mencionado.
El Azure Service Fabric surgió de la transición de Microsoft, de entregar productos de caja, que por lo general eran de estilo monolítico, a la entrega de servicios. La experiencia de crear y operar servicios a grandes escalas, como Azure SQL Database, Azure Cosmos DB, Azure Service Bus o el back-end de Cortana, le dio forma a Service Fabric. La plataforma evolucionó con el tiempo a medida que más y más servicios la adoptaron. Es importante destacar que Service Fabric se tuvo que ejecutar no sólo en Azure sino también en despliegues independientes de Windows Server.
El objetivo de Service Fabric es resolver los problemas difíciles que surgen al construir y ejecutar servicios y usar los recursos de infraestructura de forma eficiente, de modo que los equipos de trabajo puedan resolver los problemas del negocio usando un enfoque de microservicios.
Service Fabric cubre dos aspectos generales, para facilitar la creación de aplicaciones que utilizan un enfoque de microservicios:
El Service Fabric es independiente de cómo crea un servicio y puede usar cualquier tecnología. Sin embargo, proporciona APIs de programación integradas que facilitan la creación de microservicios.
Como se muestra en la Figura 4-26, se pueden crear y ejecutar microservicios en Service Fabric, ya sea con procesos simples o con contenedores Docker. También es posible mezclar microservicios basados en contenedores con microservicios basados en procesos dentro del mismo cluster Service Fabric.
Figura 4-26. Desplegando microservicios como procesos o como contenedores en Azure Service Fabric
Los clusters de Service Fabric basados en hosts Linux y Windows pueden ejecutar contenedores Docker Linux y contenedores de Windows, respectivamente.
Para obtener información actualizada sobre el soporte de contenedores en Azure Service Fabric, consulte Service Fabric and containers.
Service Fabric es un buen ejemplo de una plataforma en la que puede definir una arquitectura lógica (microservicios del negocio o Bounded Contexts) diferente de la implementación física, como se presentó en la sección Arquitectura lógica frente a arquitectura física. Por ejemplo, si implementan Reliable Services en Azure Service Fabric, que se presentan en la sección Microservicios sin estado vs microservicios con estado más adelante, se un microservicio conceptual del negocio con múltiples servicios físicos.
Como se muestra en la Figura 4-27 y viéndolo desde una perspectiva de microservicio lógico o del negocio, cuando se implementa un Service Fabric Stateful Reliable Service, generalmente se deben implementar dos niveles de servicios. El primero es el servicio de back-end confiable con estado, que maneja múltiples particiones (cada partición es un servicio con estado). El segundo es el servicio de front-end, o servicio de gateway, que se encarga del enrutamiento y la consolidación de datos entre múltiples particiones o instancias de servicios con estado. Ese servicio de gateway también maneja la comunicación del lado del cliente, con bucles de reintento que acceden al servicio de back-end. Se llama un servicio de gateway si implementa un servicio personalizado, por otro lado, puede usar el proxy inverso incluido en Service Fabric.
Figura 4-27. Microservicio del negocio con varias instancias del servicio stateful y un gateway personalizado como front-end
En cualquier caso, cuando utilizan los Service Fabric Stateful Reliable Services, también se tiene un microservicio lógico o del negocio (Boundex Context) que generalmente está compuesto de múltiples servicios físicos. Cada uno de ellos, así como el servicio de gateway y el de partición se podrían implementar como servicios ASP.NET Web API, tal como se muestra en la figura 4-27.
En Service Fabric, puede agrupar y desplegar servicios como una Service Fabric Application, que es la unidad de empaquetado y despliegue para el orquestador o cluster. Por lo tanto, la Service Fabric Application se podría asignar a este Bounded Context o microservicio de negocio autónomo, para desplegar estos servicios de forma autónoma.
También se pueden desplegar servicios en imágenes de contenedor dentro de un cluster de Service Fabric. Como se muestra en la Figura 4-28, la mayoría de las veces solo habrá un contenedor por servicio.
Figure 4-28. Microservicio del negocio con varios servicios (contenedores) en Service Fabric
Sin embargo, los llamados contenedores "sidecar" (dos contenedores que se deben desplegar juntos como parte de un servicio lógico) también se pueden manejar en Service Fabric. Lo importante es que un microservicio del negocio sea el límite lógico alrededor de varios elementos con alta cohesión. En muchos casos, podría tratarse de un servicio único con un modelo de datos también único, pero también podría tener varios servicios físicos.
No se pueden desplegar Service Fabric Reliable Stateful Services en contenedores (al menos hasta mediados de 2017), sólo puede desplegar servicios sin estado y servicios de actores (actor services) en contenedores. Sin embargo, hay que tener presente que sí se pueden mezclar servicios en procesos simples con servicios en contenedores en la Service Fabric Application, como se muestra en la Figura 4-29.
Figura 4-29. Microservicio del negocio mapeado a una aplicación Service Fabric con contenedores y servicios con estado
Para obtener más información sobre el soporte de contenedores en Azure Service Fabric, consulte Service Fabric and containers.
Como se mencionó anteriormente, cada microservicio (Bounded Context lógico) debe poseer su modelo de dominio (datos y lógica). En el caso de microservicios sin estado, las bases de datos serán externas, empleando bases de datos relacionales como SQL Server o no relacionales como NoSQL, MongoDB o Azure Cosmos DB.
Pero los mismos servicios también pueden tener estado en Service Fabric, lo que significa que hay datos se encuentran dentro del microservicio. Esta información podría existir no solo en el mismo servidor, sino dentro del proceso de microservicio, en la memoria y persistido en discos duros y replicado en otros nodos. La figura 4-30 muestra los diferentes enfoques.
Figura 4-30. Microservicios con estado versus sin estado
Un enfoque sin estado es perfectamente válido y es más fácil de implementar que los microservicios con estado, ya que el enfoque es similar a los patrones conocidos tradicionales. Pero los microservicios sin estado implican una latencia entre el proceso y las fuentes de datos. También están involucradas más partes móviles cuando intenta mejorar el rendimiento con caché y colas adicionales. El resultado es que se puede terminar con arquitecturas complejas con demasiados niveles.
Por el contrario, los Reliable Services pueden sobresalir en escenarios avanzados, porque no hay latencia entre la lógica del dominio y los datos. El procesamiento intensivo de datos, los back-ends de juego, las bases de datos como servicio y otros escenarios de baja latencia se benefician de los servicios con estado, que habilitan el manejo de un estado local para lograr un acceso más rápido.
Los servicios sin estado y con estado son complementarios. Por ejemplo, se puede ver en el diagrama de la derecha, en la Figura 4-30, que un servicio con estado podría dividirse en múltiples particiones. Para acceder a esas particiones, se podría usar un servicio sin estado que actúe como un gateway que sepa cómo abordar cada partición en función de las claves de la partición.
Sin embargo, los servicios con estado tienen inconvenientes. Se requiere manejar un nivel de complejidad mayor para que se puedan escalar. La funcionalidad que generalmente sería implementada por sistemas de bases de datos externos, se debe manejar en el servicio para tareas tales como la replicación de datos y el particionamiento de datos. Sin embargo, esta es una de las áreas en las que un orquestador como Azure Service Fabric con sus stateful reliable services puede ayudar más, al simplificar el desarrollo y el ciclo de vida de los microservicios con estado que utilizan la Reliable Services API y los Reliable Actors.
Otros frameworks de microservicios que permiten servicios con estado, soportan el patrón Actor y que mejoran la tolerancia a errores y la latencia entre la lógica del negocio y los datos son Microsoft Orleans de Microsoft Research y Akka.NET. Ambos frameworks están mejorando actualmente su soporte para Docker.
Tenga en cuenta que los contenedores Docker no tienen estado. Si desea implementar un servicio con estado, necesita uno de los framework prescriptivos y de nivel superior mencionados anteriormente.