Skip to the content.

Arquitectura de Software


Contenidos

Conceptos, técnicas y modelos de Arquitectura de Software: Conceptos de Arquitectura de Software. Arquitecturas en el contexto de la Ingeniería del Software. Cómo se identifican los requerimientos que afectan una arquitectura de software. Conflictos entre requerimientos.

Complejidad de crear una arquitectura de software Documentación formal en ingeniería de software. Necesidades y puntos a tener en cuenta al realizar cualquier tipo de documentación. Documentación de arquitecturas de software. Cómo se documenta una arquitectura de software. Las vistas y estilos existentes para modelar una arquitectura. Los roles y la importancia relativa de los distintos aspectos de una arquitectura. Aspectos humanos de la comunicación de arquitecturas de software.

Evaluación y juicio crítico-objetivo de arquitecturas de software. Métodos y procesos formales, semi-formales e informales para evaluar arquitecturas de software. ROI de analizar una arquitectura de software. Parámetros para decidir si una arquitectura es lo suficientemente buena. Costo de evaluar arquitecturas de software.

Trabajo Práctico Final Arquitectura de Sistemas de Software

Tema

Microservicios: Patrón de SAGAS para asegurar transaccionalidad y resolver problemas de Recovery/Rollback.

Alumnos: Panebianco - Miguez - Restivo
Entrega: 09/09/2021

Índice de Contenido

Introducción 3

Problema 5

Parte 1: Patrón de SAGA 7

Ejemplo de SAGA basada en Coreografía 9

Ejemplo de SAGA basada en Orquestación 9

Orquestación Explícita 10

Orquestación Implícita 11

Parte 2: Problemas de Recovery y Rollback 13

Rollback o Transacción Compensatoria 13

Recovery ante fallas transitorias 14

Contexto Resultante 16

DEMO: SAGA con Orquestación Explícita 16

ANEXO: Referencias 21

#

Introducción

Los Microservicios, también conocidos como arquitectura de microservicios, se diferencian del enfoque tradicional y monolítico de aplicaciones donde una aplicación se compila en una sola pieza (monolito), en su lugar, en una arquitectura de microservicios, las aplicaciones se dividen en elementos más pequeños e independiente entre sí para llevar a cabo las mismas tareas1, es decir, es un estilo arquitectónico que estructura una aplicación como una colección de servicios que son2:

La arquitectura de microservicio permite la entrega rápida (Continuous Delivery), frecuente y confiable de aplicaciones grandes y complejas. También a través del desarrollo distribuido, permite a una organización hacer evolucionar su stack de tecnología y mejorar el trabajo de equipos, orientándose a estar mucho más enfocados dividiéndose las responsabilidades (1 dominio = 1 microservicio).

Entre otras ventajas3, se pueden encontrar la capacidad de recuperación, ya que si los servicios se encuentran diseñados de manera correcta cumpliendo con la independencia, al existir una falla en uno de ellos, no afectaría al resto del stack de aplicaciones, también podemos encontrar aplicaciones más abiertas, donde las mismas se convierten en APIs políglotas, donde los desarrolladores tienen la libertad de elegir el lenguaje que cumpla con lo que necesite.

A pesar de la gran cantidad de ventajas que nos ofrecen los microservicios, no todo es gratis, dividir las aplicaciones para lograr esta arquitectura requiere que sea Administrada, Coordinada, Monitoreada y también se deben gestionar los datos que se creen y modifiquen, dejándonos varios desafíos, los cuales, John Frizelle, arquitecto de plataformas de Red Hat Mobile, expuso en la Red Hat Summit del 2017, y los mismos podemos resumirlos en4:

En el siguiente gráfico se puede observar conceptualmente la comparación entre la idea de una arquitectura monolítica y de una arquitectura de microservicios6.

Problema

Al diseñar nuestra aplicación o solución utilizando una arquitectura basada en microservicios, en donde el diseño se ha concebido en base al paradigma de contextos aislados o delimitados, para así mantener su independencia, ya no tenemos una base de datos compartida entre ellos, por lo que al implementar el patrón “Database Per service7”, cada aplicación de microservicios, en caso de ser necesario, tiene su propia base de datos y algunas transacciones locales abarcan varios servicios por lo que se necesita de algún mecanismo para garantizar la consistencia de los datos en todos los servicios.

Tomando como ejemplo, en una tienda de comercio electrónico, donde los clientes tienen un límite de crédito, la aplicación debe garantizar que un nuevo pedido no supere el limite de credito y como el pedido del cliente se encuentra en diferentes bases de datos, aquí surge el problema de asegurar Integridad Transaccional (ACID8), ya que la aplicación no puede utilizar una transacción ACID local.

Es decir, como resultante de aplicar una arquitectura de microservicios, tenemos:

  1. Base de datos por Servicio y;
  2. Api Composition, es tener una “Consistencia Eventual9” de los datos.

Cuando el negocio requiere un conjunto de datos, resultado de una consulta compleja (por ejemplo, en un esquema con una sola base de datos centralizada, una query resolvería éste problema), cada servicio sería el responsable de generar una “vista” parcial de los datos necesarios para componer la vista final (Consistencia eventual).

Entonces, ¿cómo mantener una consistencia de los datos en todos los servicios? La solución a este problema es utilizar el patrón SAGA.

Parte 1: Patrón de SAGA

El patrón SAGA es una forma alternativa de mantener la consistencia de los datos. Pero, ¿qué es una SAGA? El implementar una transacción del negocio que abarque múltiples servicios es una “SAGA”, es decir, una SAGA es una secuencia de transacciones locales.

Cada transacción local actualiza su propia base de datos (Database per Service) y publica un mensaje o evento para activar la siguiente transacción local de la SAGA (Transactional Outbox10).

Si una transacción local falla porque viola una regla del negocio, la SAGA ejecuta una serie de transacciones compensatorias que deshacen los cambios realizados por las transacciones locales anteriores (dejan las transacciones en un estado consistente).

Hay dos formas de manejar la coordinación de SAGAS:

  1. Coreografía: cada transacción local publica eventos de dominio que desencadenan transacciones locales en otros servicios.
  2. Orquestación: un orquestador (objeto) le dice a los participantes qué transacciones locales ejecutar.

| Importante: El patrón de la SAGA es difícil de depurar y su complejidad aumenta con la cantidad de microservicios. El patrón requiere un modelo de programación complejo que desarrolla y diseña transacciones de compensación (rollback) para revertir y deshacer cambios. Además es importante implementar mecanismos de “observabilidad”: Patrón: Log aggregation. Patrón: Distributed Tracing Patrón: Audit Logging | | :—- |

Ejemplo de SAGA basada en Coreografía

Cuando se utiliza coreografía, cada microservicio es responsable de una parte del flujo total de la transacción, no existe una entidad interna o externa que se encargue de organizar y asegurar que todos los pasos de la transacción se ejecuten en orden.

En el escenario de una coreografía, cada microservicio tiene la “responsabilidad” de conocer los pasos que le corresponden en una transacción de negocio y actuar en consecuencia.

El siguiente ejemplo consta de los siguientes pasos:

  1. El Order Service recibe la solicitud POST/Order y crea un pedido en estado PENDIENTE
  2. Luego emite un evento de Order Created.
  3. El event handler del Customer Service intenta reservar crédito
  4. Luego emite un evento que indica el resultado.
  5. El event handler de Order Service aprueba o rechaza el pedido.

Ejemplo de SAGA basada en Orquestación

La orquestación se basa en el concepto de tener a una entidad como responsable de conocer todos los pasos y secuencia de ejecución de una transacción de negocio.

Esta entidad puede ser tanto Externa como Interna (Orquestación Explícita), por ejemplo, otro microservicio u otra clase quien se encarga de orquestarlo, o bien, esta orquestación puede ser realizada por el propio servicio (Orquestación Implícita).

Orquestación Explícita

En este ejemplo podemos observar por un lado una orquestación explícita dada por una entidad externa, tal como el Orchestrator en la primer imagen o el CreateOrderSAGA como microservicio separado en la imagen siguiente.

Por otro lado, en la siguiente imagen se puede observar también una orquestación explícita, pero la misma no es realizada por una entidad externa, sino que quién la orquesta es el propio Order Service por medio de CreateOrderSaga.

Orquestación Implícita

Por ejemplo en el caso de Order Service, es él quien actúa como orquestador de la SAGA.

En este tipo de orquestación implícita la SAGA se controla/ejecuta dentro del microservicio de Order Service, es decir, es parte del código de ejecución de una transacción en Order Service, dónde en cada uno de los servicios tenemos un Event Handler para así poder manejar esta transacción.

Parte 2: Problemas de Recovery y Rollback

Rollback o Transacción Compensatoria

Siguiendo al ejemplo anterior, en él mismo se detalla el camino feliz, pero… ¿qué pasaría si el cliente no tiene saldo? en este caso deberíamos de hacer un rollback, es decir, volver al estado anterior.

Para hacer “rollback” o revertir la transacción general en caso de una falla, el patrón de SAGAS se basa en la noción de “Transacciones Compensatorias11” dónde cada transacción local aplicada previamente debe poder “deshacerse” ejecutando otra transacción que aplique la inversión de los cambios realizados anteriormente.

Ahora bien, ¿qué sucede si falla un paso del flujo? Supongamos que el paso de Payment falla porque la tarjeta de crédito del cliente ha caducado. En ese caso, la cantidad de crédito previamente reservada en el servicio de atención al cliente debe liberarse nuevamente. Para ello, el servicio de pedidos envía una solicitud de compensación al servicio de atención al cliente. Así es como se vería el intercambio de mensajes en este caso:

Como conclusión, en un patrón de SAGAS, no se hace un verdadero “rollback” de las transacciones, debido a que los datos no se pueden revertir, porque los participantes de la SAGA realizan cambios en sus bases de datos locales y esos cambios son “transacciones” per se.

Recovery ante fallas transitorias

¿Qué pasa cuando en una transacción de la SAGA (un paso), se corta alguna conexión de red o debido a que un servicio no se encuentra levantado y no se termina de devolver el status final al servicio que originó todo? Es decir, ¿qué sucede cuando hay fallas transitorias?

Retry Pattern12 permite que una aplicación maneje fallas transitorias cuando intenta conectarse a un servicio o recurso de red, volviendo a intentar de forma transparente la operación fallida. Retry puede mejorar la estabilidad de la aplicación.

Si una aplicación detecta una falla cuando intenta enviar una solicitud a un servicio remoto, puede manejar la falla utilizando alguna de las siguientes estrategias:

Para las fallas transitorias más comunes, se debe elegir el período entre reintentos para distribuir las solicitudes de múltiples instancias de la aplicación de la manera más uniforme posible. Esto reduce la posibilidad de que un servicio ocupado continúe sobrecargado. Si muchas instancias de una aplicación abruman continuamente un servicio con solicitudes de reintento, el servicio tardará más en recuperarse.

Si la solicitud aún falla, la aplicación puede esperar y hacer otro intento. Si es necesario, este proceso se puede repetir con retrasos crecientes entre reintentos, hasta que se haya intentado un número máximo de solicitudes. El retraso se puede aumentar de forma incremental o exponencial, según el tipo de falla y la probabilidad de que se corrija durante este tiempo.

Contexto Resultante

Podemos encontrar para el patrón SAGA los siguientes beneficios:

A pesar de ello, podemos mencionar para esta solución tiene los siguientes inconvenientes:

DEMO: SAGA con Orquestación Explícita

En el presente ejemplo, se plantea utilizar Debezium, Kafka y 3 servicios de negocio con 1 base de datos correspondiente a cada uno de estos servicios, es decir 3 bases de datos:

Internamente, cada vez que llega un Request al primer servicio de la SAGA, Order Service, el microservicio ejecuta parte de una transacción (el primer paso) y lo persiste en su base de datos.

En paralelo, se escribe un log de transacción (OUTBOX table) que permite a Debezium iniciar el flujo de eventos utilizando Kafka para que la SAGA siga su flujo de ejecución.

El patrón de Outbox proporciona una solución para que los servicios realicen escrituras en su base de datos local y envíen mensajes a través de Apache Kafka, sin depender de “escrituras duales” (dual write) inseguras.

Luego de esa actualización en Outbox table, continúa el siguiente flujo de eventos:

En el siguiente diagrama de secuencia se puede observar el flujo nominal (aquel sin errores, es decir, el camino feliz) de una transacción utilizando SAGAS:

Al realizar una trazabilidad de los logs, eventos y tópicos de kafka ejecutados por todos los microservicios, el resultado es el siguiente:

#

#

ANEXO: Referencias

  1. RedHat Microservicios: https://www.redhat.com/es/topics/microservices 

  2. Microservicios: https://microservices.io/index.html 

  3. RedHat ventajas de microservicios: https://www.redhat.com/es/topics/microservices/what-are-microservices 

  4. The Truth about Microservices, Brian Atkisson: https://developers.redhat.com/blog/2017/05/04/the-truth-about-microservices 

  5. Efecto de los microservicios en sus datos: https://developers.redhat.com/blog/2016/08/02/the-hardest-part-about-microservices-your-data#the_challenge_of_data_with_microservices 

  6. https://martinfowler.com/articles/microservices.html 

  7. https://microservices.io/patterns/data/database-per-service.html 

  8. https://en.wikipedia.org/wiki/ACID 

  9. https://martinfowler.com/articles/microservice-trade-offs.html#consistency 

  10. https://microservices.io/patterns/data/transactional-outbox.html 

  11. https://en.wikipedia.org/wiki/Compensating_transaction 

  12. https://docs.microsoft.com/en-us/azure/architecture/patterns/retry