Neste artigo, demonstrarei como devemos lidar com cenários de transações ao usar Saga Pattern e realizar rollbacks destas entre microserviços de forma compilada juntamente com um exemplo abordando os principais tópicos desse padrão.
O objetivo aqui é usar esse artigo como consulta e revisão no futuro, aguardo seu feedback e boa leitura!
A ideia da Saga
A ideia do uso de Sagas iniciou de um problema específico em sistemas que utilizam transações longas e/ou sequenciais (chamadas originalmente de Long Lived Transactions ou LLT ) com operações envolvendo atomicidade em banco de dados e interação com outros sistemas. A forma mais comum da LLT é distribuir transações, ou seja, realizar operações em diversos serviços ou bases de dados que manipulam dados relacionados.
O padrão Saga amadureceu bastante com o tempo e hoje possui características auxiliares às arquiteturas modernas de sistemas distribuídos, base de dados como NoSQL e message brokers na qual devem lidar com a consistência dos dados e encontrar o equilíbrio seguindo o Teorema CAP.
Antes de apresentar esse pattern, vamos entender o problema das transações distribuídas!
Problemas com Transações Distribuídas e ACID
Atualmente, encontramos arquiteturas de microserviços em sistemas modernos utilizando transações distribuídas seguindo o modelo ACID. Essa abordagem possui bastante limitações e pode acarretar problemas ao sincronizar dados e desfazer operações por conta de alguma indisponibilidade ou apenas por cancelamento de um processo.
O primeiro problema se torna aparente justamente na dependência de um microserviço realizar uma operação que necessita do inicio de outra operação em outro microserviço e o mande uma requisição.
O segundo problema é encontrado quando os microserviços podem ser únicos de forma arquitetural e são construídos seguindo padrões como o modelo de database-per-microservice onde tem-se a liberdade de selecionar qual o tipo do banco de dados e como persiste os dados e se comunica de uma forma própria (via mensageria ou HTTP por exemplo). Então fica mais complicado ainda seguir o modelo ACID pois deve haver um gerenciamento grande a cada transação realizada e, caso alguma falhe, deve avisar as demais que houve um problema e devem realizar rollbacks!
Há outras maneiras de manter a consistência de dados com transações distribuídas além do Saga - já apresento mais abaixo, prometo - como os protocolos inter-process, two-phase commit, Try-Confirm-Cancel (TCC), entre outros. O foco desse artigo é o Saga, pois é o mais utilizado e adequado a maioria das soluções e arquiteturas modernas de software.
O Padrão Saga
Uma saga é um fluxo representado com uma sequência de transações seguindo uma certa ordem de forma síncrona e/ou assíncrona. Essas transações não são distribuídas e por isso cada transação é local e após confirmar o fim da transação, chama a próxima e assim sucessivamente até o fim da Saga. Podemos ter um identificador da saga para cada transação no encadeamento e obter um rastreamento do fluxo inteiro.
Orquestração e Coreografia
A saga pode ser implementada seguindo dois modelos atuais:
Com um Orquestrador, também conhecido como Saga Execution Coordinator (SEC), controlando cada transação da Saga e orientando qual será a próxima transação ou desfazer commits de transações passadas quando alguma transação ocorrer uma falha (falaremos disso a seguir). Assim como um maestro e sua orquestra, sabe sempre o próximo passo e quem deve realizá-lo.
Nesse exemplo simples, temos um Broker que gerencia e facilita toda a comunicação entre os 3 serviços. O lado ruim é que se estiver fora do ar, todas os outros serviços e apps estarão também. Pois dependem fortemente do Broker.
Ou seguindo o modelo de Coreografia, assim como em um Flash mob (eu devo estar velho!) cada integrante - ou um microserviço, no nosso caso - sabe como e quando deve realizar a sua parte e pode ou não aguardar a vez de outro integrante. Voltando para nossa realidade, os microserviços conhecem a Saga e sabem aonde vão atuar e iniciar sua transação local.
No exemplo abaixo, temos um fluxo que envolve 4 sistemas (A, B, C e D) e possui 3 etapas a serem realizadas. Em cada etapa, os sistemas sabem atuar e agir através de uma mensagem, nesse caso usamos um broker de mensageria e chamada HTTP. A desvantagem disso, é a administração e monitoramento de cada sistema para garantir o funcionamento de ponta a ponta.
Desafios ao implementar o padrão
Independente da abordagem implementada, e também em qualquer outro padrão e tecnologia , haverão obstáculos a serem considerados e que entram na balança do Trade-Off.
Segue uma lista com alguns possíveis problemas:
- Implementação da observabilidade de forma mais detalhada para cada etapa do fluxo de uma Saga;
- Haverão pontos de falha, e deve-se entender se é possível e como revertê-los em cada aplicação e banco de dado s detalho mais sobre com um exemplo a seguir);
- Debugar ou rastrear uma entidade ou agregado pode ser mais difícil por conta de ter diversas. Por isso um identificador da Saga e uma coordenação entre as aplicações durante todo o fluxo se faz necessário;
- Grande probabilidade do uso de Eventos no fluxo da Saga, aumentando ainda mais a complexidade e obrigando a ter um maior controle do que é enviado e recebido em tópicos e filas;
Exemplificando com Caso de uso
Para exemplificar o uso da Saga, usamos um caso de uso ficticio envolvendo três microservicos em uma área de Marketing. Segue um resumo do uso de cada uma:
- O primeiro é um serviço de criação de Posts via API pelo funcionário de Marketing, o Post Service;
- O segundo é um serviço de envio de e-mail para clientes externos quando houver um novo Post e se comunica via mensageria, o Notification Service;
- O último é um serviço externo que recebe o post via mensageria e persiste em um banco de dados para um WebSite externo, o WebSite Service;
A Saga se baseia na criação de um Post de marketing (com promoções, cupons, etc) que deve ser notificado aos clientes e atualizado no site.
Abaixo, segue o fluxo de sucesso da Saga completa:
Tudo muito bonito! Maaas, e se, digamos, ao criar um novo Post, a notificação é feita aos clientes via e-mail mas não é atualizada no site. Temos uma falha crítica e devemos tratá-la, a seguir demonstro como lidarmos com esse problema.
Lidando com falhas em uma Saga
Padrão Retry (Retentativas)
Quando há uma falha, podemos seguir retentando um passo da Saga, seja uma transação local ou uma chamada para próxima ação, por uma certa quantidade de vezes combinada com um intervalo tempo até termos certeza que houve a falha e e ela não será resolvida automaticamente. Essa abordagem de retentar alguma ação chamamos de Retry Pattern.
No fluxo acima, imagine a situação em que a mensageria ficou offline por alguns segundos e a mensagem para o serviço do WebSite não foi enviada (passos 3). Partindo que o Post Service possui uma política de retentativas das mensagens que tenta por determinada quantidade de vezes a cada x minutos, temos o seguindo fluxo:
Muito Bom né? o serviço realizou diversas tentativas até estar no ar novamente e não precisamos ter um alerta gritando por aí.
Mas…e quando temos certeza da falha, mesmo depois de aplicar todas as políticas de retentativa? Precisamos realizar alguma outra ação para resolver, ou até desfazer, a nossa Saga. A resposta pode estar nas transações compensatórias!
Transações Compensatórias
Ok, houve uma falha no nosso fluxo e não conseguimos continuar com a Saga, devemos compensar esse erro e desfazer as ações anteriores.
Para isso, o padrão Saga possui as Transações Compensatórias como um mecanismo indicador para que uma transação feita seja…desfeita e pode avisar todas as outras aplicações ou microserviços do fluxo que devem desfazer as suas transações localmente também.
Com isso, seguimos com nosso caso de falha crítica onde clientes receberam as notificações de novas promoções e descontos mas não há nenhuma promoção no site!! Devemos desfazer e mandar uma errata para eles como uma compensação. Então, ajustamos o ponto no fluxo onde ocorre o erro e chegamos a uma resolução:
Os passos 3a e 3b fazem parte da transação compensatória assim que o passo 2 é feito e uma parte falha.
Diminuindo Rollbacks
Também podemos evitar que falhas e compensações aconteçam simplesmente analisando os pontos de falha e reorganizando o processo, também é possível revendo requisitos funcionais e não-funcionais.
Revendo todo o fluxo, podemos evitar a falha apresentada no tópico anterior simplesmente ajustando o envio do Post a ser atualizado no site primeiro antes de enviar as notificações aos clientes. Ou seja, enviamos a mensagem ao WebSite Service (passo 3), depois de obter uma resposta indicando que a mensagem chegou com sucesso (passos 4a e 4b), enviamos a mensagem de notificação aos clientes (passo 5).
Isso evita o transtorno tratado anteriormente e também deixa o sistema mais resiliente e rastreável a falhas!
Por fim, segue o fluxo refatorado:
E é isso…
Nesse artigo vimos detalhes do Saga que não são encontrados tão facilmente juntamente com problemas que aparecem ao implementá-lo. Esse padrão é comumente utilizado com diversos padrões como CQRS e Event-Driven, agregando a arquitetura do sistema e fortificando sua estrutura em diversas aplicações e produtos.
Espero que tenha aumentado mais o seu conhecimento e leque de técnicas aplicadas a engenharia e arquitetura de software. Até a próxima o/
Referências e Links úteis
- Example using Saga with Kafka and.Net: https://github.com/luanmds/kafka-dotnet-study/tree/main/sample-02
- https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga
- https://medium.com/codex/compensating-transaction-in-microservices-15b1f88a7c29
- https://livebook.manning.com/book/microservices-patterns/chapter-4/
- https://blog.sofwancoder.com/try-confirm-cancel-tcc-protocol
- https://en.wikipedia.org/wiki/Inter-process_communication
- https://en.wikipedia.org/wiki/Two-phase_commit_protocol
- https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf
- https://en.wikipedia.org/wiki/Long-lived_transaction
- https://www.lifewire.com/the-acid-model-1019731
Top comments (1)
Muuuuito esclarecedor e ótimos exemplos