Microsserviços Patterns: Gerenciando transações com SAGA

Microsserviços Patterns: Gerenciando transações com SAGA

Em microsserviços, uma das maiores preocupações era como implementar transações que abrangessem vários serviços. Transações são um ingrediente essencial de todos os aplicativos corporativos. Sem transações seria impossível manter a consistência dos dados.

ACID (Atomicidade, Consistência, Isolamento, Durabilidade) são transações que simplificam enormemente o trabalho do desenvolvedor, provendo a ilusão de que cada transação possui acesso exclusivo aos dados. Na arquitetura de microsserviços, transações que estão dentro de um único serviço, podem ainda usar transações ACID. O desafio, no entanto, está na implementação de transações para operações que atualizam dados controlados por múltiplos serviços.

No entanto, descobriu-se que a abordagem tradicional de gerenciamento de transações distribuídas não é uma boa opção para aplicações modernas. Em vez de transações ACID, uma operação que abrange serviços deve usar o que é conhecido como SAGA, uma sequência de transações locais orientadas por uma mensagem a fim de manter os dados consistentes.

Um desafio com Sagas é que eles são ACD (Atomicidade, Consistência, Durabilidade). Eles não possuem a feature de isolamento das tradicionais transações ACID. Como resultado, uma aplicação deve usar o que é conhecido como countermeasures, técnica de design que impede ou reduz o impacto de anomalias de concorrências causadas pela falta de isolamento.

De várias formas, o grande obstáculo que os desenvolvedores encontram quando aderem soluções de microsserviços é mover um único banco de dados com transações ACID para uma arquitetura com bancos de dados múltiplos com ACD Sagas. Eles estão acostumados a simplificar o modelo de transações ACID. Na realidade, muitos aplicativos usam um nível de isolamento de transação mais baixo para melhorar o desempenho. Também, muitos processos de negócios importantes, como transferência de dinheiro entre contas em bancos diferentes, são eventualmente consistentes.

Gerenciamento de transações em uma arquitetura de microsserviço

Quase todas as solicitações tratadas por uma aplicação corporativa são executadas em uma transação de banco de dados. Os desenvolvedores de aplicações corporativas usam estruturas e bibliotecas que simplificam o gerenciamento de transações. Algumas estruturas e bibliotecas fornecem uma API para explicitamente iniciar, confirmar e reverter transações.

Outros frameworks, como o framework Spring, fornecem um mecanismo declarativo. Spring fornece uma anotação @Transactional que organiza as invocações de método executado automaticamente dentro de uma transação. Como resultado, é fácil escrever lógica de negócios transacionais.

Para ser mais preciso, o gerenciamento de transações é feito diretamente em uma aplicação monolítica que acessa um único banco de dados. O gerenciamento de transações é mais desafiador em uma aplicação monolítica complexa que usa vários bancos de dados e brokers de mensagens. E em uma arquitetura de microsserviços, as transações abrangem vários serviços, cada um dos quais tem seu próprio banco de dados.

Nesta situação, a aplicação deve usar um mecanismo mais elaborado para gerenciar transações. A abordagem tradicional de usar transações distribuídas não é uma opção viável para aplicações modernas. Em vez disso, uma aplicação baseada em microsserviços deve usar sagas.

A necessidade de transações distribuídas em uma arquitetura de microsserviços

A operação createOrder() atualiza dados em diversos serviços. Deve usar um mecanismo para manter a consistência dos dados nesses serviços.



Por mais simples que pareça, há uma variedade de problemas com transações distribuídas. Um problema é que muitas tecnologias modernas, incluindo bancos de dados NoSQL como MongoDB e Cassandra, não os suportam. Além disso, transações distribuídas não são suportados por brokers de mensagens modernos, como RabbitMQ e Apache Kafka. Como resultado, se você insistir em usar transações distribuídas, não poderá usar muitas tecnologias modernas.

Superficialmente, as transações distribuídas são atraentes. Do ponto de vista de um desenvolvedor, elas têm o mesmo modelo de programação que as transações locais. Mas por causa dos problemas mencionados até agora, as transações distribuídas não são uma tecnologia viável para aplicações modernas.

Para resolver o problema mais complexo de manter a consistência de dados em uma arquitetura de microsserviços, uma aplicação deve usar um mecanismo diferente que se baseia no conceito de acoplamento fraco e assíncrono de serviços. É aqui que entram os sagas.

Usando o padrão SAGA para manter a consistência dos dados

Sagas são mecanismos para manter a consistência dos dados em uma arquitetura de microsserviços sem ter que usar transações distribuídas. Você define um saga para cada comando do sistema que precisa atualizar dados em vários serviços. Um saga é uma sequência de transações locais. Cada transação local atualiza dados dentro de um único serviço usando estruturas e bibliotecas de transações ACID.

A operação do sistema inicia a primeira etapa da saga. A conclusão de uma transação local dispara a execução da próxima transação local. Um benefício importante da mensagem assíncrona é que ela garante que todas as etapas de uma saga são executadas, mesmo que um ou mais participantes da saga estejam temporariamente indisponíveis. As sagas diferem das transações ACID em alguns aspectos importantes, elas não possuem a propriedade de isolamento das transações ACID. Também porque cada transação local confirma suas alterações, um saga deve ser revertido usando compensação de transações.

Um exemplo de Saga: a Saga de criação de ordem (Order)

O Order Service implementa a operação createOrder() usando esta saga. A primeira transação local do saga é iniciada pela solicitação externa para criar um pedido. As outras cinco transações locais são acionadas pela conclusão do anterior.

Criando um pedido usando um saga. A operação createOrder() é implementada por um saga que consiste em transações locais em diversos serviços.


Este saga consiste nas seguintes transações locais:

  1. Order Service — Crie um Order em um estado APPROVAL_PENDING.
  2. Consumer Service — Verifique se o consumer pode fazer um pedido.
  3. Kitchen Service — Valide os detalhes do pedido e crie um Ticket no CREATE_PENDING.
  4. Accounting Service — Autoriza o cartão de crédito do consumer.
  5. Kitchen Service — Altere o estado do Ticket para AWAITING_ACCEPTANCE.
  6. Order Service — Altere o estado do pedido para APPROVED.

Superficialmente, os sagas parecem diretos, mas existem alguns desafios para usar eles. Um desafio é a falta de isolamento entre os sagas. Outro desafio é reverter as alterações quando um erro ocorre. Vamos dar uma olhada em como fazer isso.

Sagas usam Transações de compensação para retornar mudanças

Uma grande característica das transações ACID tradicionais é que a lógica de negócios pode facilmente reverter uma transação, se detectar a violação de uma regra de negócios. Ele executa um ROLLBACK declaração, e o banco de dados desfaz todas as alterações feitas até agora. Infelizmente, os sagas não podem ser revertidos automaticamente, porque cada etapa confirma suas alterações no banco de dados local.

Isso significa, por exemplo, que se a autorização do cartão de crédito falhar na quarta etapa da Saga Create Order, o aplicativo deve explicitamente desfazer as alterações feitas nas três primeiras etapas. Você deve escrever o que é conhecido como compensação de transações.

Suponha que a (n + 1)ª transação de um saga falhe. Os efeitos das transações n anteriores devem ser desfeitas. Conceitualmente, cada uma dessas etapas, Ti, tem uma transação compensatória correspondente, Ci, que desfaz os efeitos do Ti. Para desfazer o efeitos desses primeiros n passos, o saga deve executar cada Ci na ordem inversa. A sequência de passos é T1 … Tn, Cn … C1, conforme mostra a figura abaixo. Neste exemplo, Tn+1 falha, o que requer que as etapas T1 … Tn sejam desfeitas.

Quando uma etapa de um saga falha, devido a uma violação de regra de negócios, o saga deve explicitamente desfazer as atualizações feitas pelas etapas anteriores, executando transações compensatórias.

O saga executa as transações de compensação na ordem inversa das transações posteriores: Cn … C1. A mecânica de sequenciamento do Cis não é diferente da sequênciados Tis. A conclusão de Ci deve desencadear a execução de Ci-1.

Considere, por exemplo, a Saga Create Order. Esta saga pode falhar por uma variedade de razões:

  • As informações do consumer são inválidas ou o consumer não tem permissão para criar ordens.
  • As informações do restaurante são inválidas ou o restaurante não pode aceitar pedidos (orders).
  • Falha na autorização do cartão de crédito do consumer.

Se uma transação local falhar, o mecanismo de coordenação do saga deve executar a compensação de transações que rejeitam o Pedido (Order) e possivelmente o Bilhete (Ticket).

A tabela abaixo mostra as transações de compensação para cada etapa do Saga Create Order. É importante observar que nem todas as etapas precisam de transações de compensação. Etapas somente leitura, como verificação ConsumerDetails(), não precisam de transações de compensação. Nem passos como authorizeCreditCard() que são seguidos por etapas que sempre são bem-sucedidas.


Para ver como as transações de compensação são usadas, imagine um cenário em que ocorre uma falha na autorização do cartão de crédito do consumidor (Consumer). Neste cenário, o saga executa as seguintes transações locais:

  1. Order Service — Crie um Order em um estado APPROVAL_PENDING.
  2. Consumer Service — Verifique se o consumidor pode fazer um pedido.
  3. Kitchen Service — Valide os detalhes do pedido e crie um Ticket no estado CREATE_PENDING.
  4. Accounting Service — Autoriza o cartão de crédito do consumidor, que falha.
  5. Kitchen Service — Altere o estado do Ticket para CREATE_REJECTED.
  6. Order Service — Altere o estado do pedido para REJECTED.

A quinta e a sexta etapas são transações compensatórias que desfazem as atualizações feitas pelo Serviço de Cozinha (Kitchen Service) e pelo Serviço de Encomendas (Order Service), respetivamente. A lógica de coordenação de um saga é responsável por sequenciar a execução de transações a termo e de compensação.


Resumo

Algumas operações do sistema precisam atualizar dados espalhados por vários serviços.

Transações distribuídas tradicionais baseadas em XA/2PC não são uma boa opção para aplicações modernas. Uma abordagem melhor é usar o padrão Saga. Um saga é uma sequência de transações locais que são coordenadas usando mensagens. Cada transação local atualiza os dados em um único serviço. Como cada transação local realiza mudanças, se uma saga deve reverter devido à violação de uma regra de negócio, ela deve executar transações de compensação para desfazer alterações explicitamente.

Projetar lógica de negócios baseada em saga, pode ser desafiador porque, ao contrário de transações ACID, as sagas não estão isoladas umas das outras. Muitas vezes você deve usar countermeasures, que são estratégias de design que evitam anomalias de simultaneidade causadas ​​pelo modelo de transação ACID. Um aplicativo pode até precisar usar bloqueio para simplificar a lógica de negócios, mesmo que isso arrisque impasses.

💡
As opiniões e comentários expressos neste artigo são de propriedade exclusiva de seu autor e não representam necessariamente o ponto de vista da Revelo.

A Revelo Content Network acolhe todas as raças, etnias, nacionalidades, credos, gêneros, orientações, pontos de vista e ideologias, desde que promovam diversidade, equidade, inclusão e crescimento na carreira dos profissionais de tecnologia.