Patrones de microservicios: Manejar transacciones con SAGA

Patrones de microservicios: Manejar transacciones con SAGA

En los microservicios, una de las mayores preocupaciones era cómo implementar transacciones que abarquen múltiples servicios. Las transacciones son un ingrediente esencial de todas las aplicaciones empresariales. Sin transacciones, sería imposible mantener la coherencia de los datos.

ACID (Atomicidad, Consistencia, Aislamiento, Durabilidad) son transacciones que simplifican enormemente el trabajo del desarrollador, brindando la ilusión de que cada transacción tiene acceso exclusivo a los datos. En la arquitectura de microservicios, las transacciones que se encuentran dentro de un único servicio aún pueden usar transacciones ACID. El desafío, sin embargo, radica en implementar transacciones para operaciones que actualicen datos controlados por múltiples servicios.

Sin embargo, resultó que el enfoque tradicional de gestión de transacciones distribuidas no es una buena opción para las aplicaciones modernas. En lugar de transacciones ACID, una operación que abarque servicios debe utilizar lo que se conoce como SAGA, una secuencia de transacciones basadas en mensajes locales para mantener la coherencia de los datos.

Um desafio con sagas es que son ACD (Atomicidad, Consistencia, Durabilidad). No tienen la característica de aislamiento de las transacciones ACID tradicionales. Como resultado, una aplicación debe utilizar lo que se conoce como contramedidas, una técnica de diseño que previene o reduce el impacto de las anomalías de concurrencia causadas por la falta de aislamiento.

En muchos sentidos, el mayor obstáculo que encuentran los desarrolladores al adoptar soluciones de microservicios es trasladar una única base de datos con transacciones ACID a una arquitectura de múltiples bases de datos con ACD Sagas. Están acostumbrados a simplificar el modelo de transacción ACID. En realidad, muchas aplicaciones utilizan un nivel de aislamiento de transacciones más bajo para mejorar el rendimiento. Además, muchos procesos comerciales importantes, como la transferencia de dinero entre cuentas en diferentes bancos, finalmente son consistentes.

Gestión de transacciones en una arquitectura de microservicio

Casi todas las solicitudes manejadas por una aplicación empresarial se ejecutan en una transacción de base de datos. Los desarrolladores de aplicaciones empresariales utilizan marcos y bibliotecas que simplifican la gestión de transacciones. Algunos marcos y bibliotecas proporcionan una API para iniciar, confirmar y revertir transacciones explícitamente.

Otros frameworks, como Spring, proporcionan un mecanismo declarativo. Spring proporciona una anotación @Transactional que organiza las invocaciones de métodos ejecutadas automáticamente dentro de una transacción. Como resultado, es fácil escribir lógica empresarial transaccional.

Para ser más precisos, la gestión de transacciones se realiza directamente en una aplicación monolítica que accede a una única base de datos. La gestión de transacciones es más desafiante en una aplicación monolítica compleja que utiliza múltiples bases de datos y intermediarios de mensajes. Y en una arquitectura de microservicios, las transacciones abarcan múltiples servicios, cada uno de los cuales tiene su propia base de datos.

En esta situación, la aplicación debe utilizar un mecanismo más elaborado para gestionar las transacciones. El enfoque tradicional de utilizar transacciones distribuidas no es una opción viable para las aplicaciones modernas. En cambio, una aplicación basada en microservicios debería utilizar sagas.

La necesidad de transacciones distribuidas en una arquitectura de microservicios

La operación createOrder() actualiza datos en múltiples servicios. Debe utilizar un mecanismo para mantener la coherencia de los datos en estos servicios.



Por más simple que parezca, existen una variedad de problemas con las transacciones distribuidas. Un problema es que muchas tecnologías modernas, incluidas las bases de datos NoSQL como MongoDB y Cassandra, no las admiten. Además, las transacciones distribuidas no son compatibles con los corredores de mensajes modernos como RabbitMQ y Apache Kafka. Como resultado, si insiste en utilizar transacciones distribuidas, no podrá utilizar muchas tecnologías modernas.

A primera vista, las transacciones distribuidas son atractivas. Desde la perspectiva del desarrollador, tienen el mismo modelo de programación que las transacciones locales. Pero debido a los problemas mencionados hasta ahora, las transacciones distribuidas no son una tecnología viable para las aplicaciones modernas.

Para resolver el problema más complejo de mantener la coherencia de los datos en una arquitectura de microservicios, una aplicación debe utilizar un mecanismo diferente que se base en el concepto de acoplamiento de servicios asincrónico y flexible. Aquí es donde entran las sagas.

Uso del estándar SAGA para mantener la coherencia de los datos

Las sagas son mecanismos para mantener la coherencia de los datos en una arquitectura de microservicios sin tener que utilizar transacciones distribuidas. Usted define una saga para cada comando del sistema que necesita actualizar datos en múltiples servicios. Una saga es una secuencia de transacciones locales. Cada transacción local actualiza los datos dentro de un único servicio utilizando bibliotecas y estructuras de transacciones ACID.

El funcionamiento del sistema inicia la primera etapa de la saga. La finalización de una transacción local desencadena la ejecución de la siguiente transacción local. Un beneficio importante de la mensajería asincrónica es que garantiza que se ejecuten todos los pasos de una saga, incluso si uno o más participantes de la saga no están disponibles temporalmente. Las sagas se diferencian de las transacciones ACID en algunos aspectos importantes: no tienen la propiedad de aislamiento de las transacciones ACID. Además, debido a que cada transacción local confirma sus cambios, una saga debe revertirse mediante la compensación de transacciones.

Un ejemplo de Saga: la Saga de creación de la Orden

El Order Service implementa la operación createOrder() usando esta saga. La primera transacción de saga local se inicia mediante la solicitud externa para crear una orden. Las otras cinco transacciones locales se activan con la finalización de la anterior.

Creando un pedido usando una saga. La operación createOrder() se implementa mediante una saga que consta de transacciones locales entre múltiples servicios.


Esta saga consta de las siguientes transacciones locales:

  1. Order Service — Crea una Order en un estado APPROVAL_PENDING.
  2. Consumer Service — Verifica si el consumer puede hacer un pedido.
  3. Kitchen Service — Valida los detalles del pedido y crea un ticket en CREATE_PENDING.
  4. Accounting Service — Autoriza la tarjeta de crédito del consumer.
  5. Kitchen Service — Altera el estado del ticket para AWAITING_ACCEPTANCE.
  6. Order Service — Altera el estado del pedido a APPROVED.

A primera vista, las sagas parecen sencillas, pero su uso presenta algunos desafíos. Un desafío es la falta de aislamiento entre sagas. Otro desafío es revertir los cambios cuando ocurre un error. Echemos un vistazo a cómo hacer esto.

Las sagas utilizan transacciones de compensación para devolver cambios

Una gran característica de las transacciones ACID tradicionales es que la lógica empresarial puede revertir fácilmente una transacción si detecta una infracción de una regla empresarial. Ejecuta una instrucción ROLLBACK y la base de datos revierte todos los cambios realizados hasta el momento. Desafortunadamente, las sagas no se pueden revertir automáticamente porque cada paso confirma los cambios en la base de datos local.

Esto significa, por ejemplo, que si la autorización de la tarjeta de crédito falla en el cuarto paso de Saga Create Order, la aplicación debe deshacer explícitamente los cambios realizados en los primeros tres pasos. Debes escribir lo que se conoce como compensación de transacciones.

Supongamos que falla la (n + 1)ésima transacción de una saga. Los efectos de n transacciones anteriores deben deshacerse. Conceptualmente, cada uno de estos pasos, Ti, tiene una transacción compensatoria correspondiente, Ci, que deshace los efectos de Ti. Para deshacer los efectos de estos primeros n pasos, saga debe ejecutar cada Ci en orden inverso. La secuencia de pasos es T1… Tn, Cn… C1, como se muestra en la siguiente figura. En este ejemplo, Tn+1 falla, lo que requiere deshacer los pasos T1... Tn.

Cuando un paso de una saga falla, debido a una violación de las reglas de negocio, la saga debe deshacer explícitamente las actualizaciones realizadas en los pasos anteriores mediante la realización de transacciones compensatorias.

Saga ejecuta las transacciones de compensación en el orden inverso a las transacciones posteriores: Cn… C1. La mecánica de secuenciación Cis no es diferente de la secuenciación Tis. La finalización de Ci debería desencadenar la ejecución de Ci-1.

Considere, por ejemplo, la Saga Create Order. Esta saga puede fracasar por diversos motivos:

  • La información del consumidor no es válida o el consumidor no tiene permiso para crear pedidos.
  • La información del restaurante no es válida o el restaurante no puede aceptar pedidos (orders).
  • No se pudo autorizar la tarjeta de crédito del consumidor.

Si una transacción local falla, el mecanismo de coordinación de saga debe realizar una compensación por las transacciones que rechazan el pediro (order) y posiblemente el ticket.

La siguiente tabla muestra las transacciones de compensación para cada etapa de Saga Create Order. Es importante tener en cuenta que no todos los pasos necesitan transacciones de compensación. Los pasos de solo lectura, como verificar ConsumerDetails(), no necesitan compensar transacciones. Tampoco pasos como AuthorizeCreditCard() que van seguidos de pasos que siempre tienen éxito.


Para ver cómo se utilizan las transacciones de compensación, imagine un escenario en el que falla la autorización de la tarjeta de crédito de un consumidor. En este escenario, saga ejecuta las siguientes transacciones locales:

  1. Order Service — Crea una orden en un estado APPROVAL_PENDING.
  2. Consumer Service — Verifica si el consumidor puede hacer un pedido.
  3. Kitchen Service — Valida los detalles del pedido y crea un ticket en el estado CREATE_PENDING.
  4. Accounting Service — Autoriza la tarjeta de crédito del consumidor, que falla.
  5. Kitchen Service — Altera el estado del ticket para CREATE_REJECTED.
  6. Order Service — Altera el estado del pedido para REJECTED.

Los pasos quinto y sexto son transacciones compensatorias que deshacen las actualizaciones realizadas por el Kitchen Service y el Order Service, respectivamente. La lógica de coordinación de una saga es responsable de secuenciar la ejecución de transacciones a plazo y de compensación.


Resumen

Algunas operaciones del sistema necesitan actualizar datos distribuidos en múltiples servicios.

Las transacciones distribuidas tradicionales basadas en XA/2PC no son una buena opción para las aplicaciones modernas. Un mejor enfoque es utilizar el patrón Saga. Una saga es una secuencia de transacciones locales que se coordinan mediante mensajes. Cada transacción local actualiza los datos en un solo servicio. Debido a que cada transacción local realiza cambios, si una saga debe revertirse debido a una violación de una regla comercial, debe realizar transacciones de compensación para deshacer explícitamente los cambios.

Diseñar una lógica de negocios basada en sagas puede ser un desafío porque, a diferencia de las transacciones ACID, las sagas no están aisladas unas de otras. A menudo se deben utilizar contramedidas, que son estrategias de diseño que evitan anomalías de concurrencia causadas por el modelo de transacción ACID. Es posible que una aplicación incluso necesite utilizar el bloqueo para simplificar la lógica empresarial, incluso si al hacerlo se corre el riesgo de bloquearse.

¡Saludos!

💡
Las opiniones y comentarios emitidos en este artículo son propiedad única de su autor y no necesariamente representan el punto de vista de Listopro.

Listopro Community da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.