Saga Pattern - Gerenciando transações distribuídas
by Raphael Tavares, Senior Software Engineer
Relembrando Microsserviços
Para explicar este padrão arquitetural, primeiro precisamos pontuar uma característica da arquitetura de microsserviços, que é o fato de cada microsserviço ter seu próprio banco de dados.
Essa premissa existe por alguns motivos, entre eles o fato de que, se diferentes microsserviços compartilhassem o mesmo banco de dados, haveria um acoplamento indesejado e acabaríamos caindo no anti-pattern conhecido como monolíto distribuído.
Um possível problema causado por esse anti-pattern é a gestão de mudança na estrutura de um banco de dados compartilhados por múltiplos times, onde é necessário envolver a revisão dos times para garantir que não haverá alteração em cada parte. Isso gera problemas de planejamento e burocracia, podendo ser evitado ao respeitar a regra de ouro dos microsserviços.
Acordamos então em manter um banco de dados para cada serviço, certo? Dessa forma, os databases serão uma camada abstraída por trás da API de seu serviço e somente a aplicação específica terá acesso a ele.
Por que precisamos do Saga Pattern
Até então já temos um bom direcionamento para arquitetar nossa aplicação, mas ao adotar um banco de dados por serviço, surge um novo problema: Perdemos as propriedades ACID nas transações.
Relembrando rapidamente: uma transação é uma sequência de operações que, para um agente externo, deve parecer uma única ação.
E os príncipios ACID são: Atomicidade, Consistência, Isolamento e Durabilidade.
Quando usávamos um banco de dados unificado, podíamos contar com a funcionalidade de transação nativa do próprio banco para realizar múltiplas operações agrupadas em uma só.
No entanto, com um DB separado para cada serviço, como gerenciamos a consistência de dados através dos serviços em uma chamada “transação distribuída”?
A resposta: O Saga Pattern.
No saga pattern, os serviços performam operações que fazem parte de uma transação distribuída. Cada operação desencadeia a próxima, que provavelmente ocorrerá em outro microsserviço, gerando uma cascata de ações. Caso alguma dessas falhe, o saga pattern é encarregado de instruir os serviços anteriores a aplicarem operações compensatórias, que terão o efeito inverso das operações originais, restaurando, assim, o banco de dados ao seu estado inicial.
Após a aplicação dessas compensações, dependendo da implementação, pode haver um retry ou um rollback completo, acompanhado de um retorno de erro.
Implementações
Podemos implementar o Saga Pattern utilizando um de outros dois Padrões Arquiteturais, que são:
- Execution Orchestrator Pattern
- Choreography Pattern
No Execution Orchestrator Pattern, temos um serviço Orquestrador que gerencia todo o fluxo das transações distribuídas, chamando os serviços em ordem e esperando por suas respostas.
Dependendo da resposta de cada serviço, o orquestrador decide se deve prosseguir chamando o próximo, ou dar rollback na transação, chamando o serviço anterior mas para uma operação compensatória.
De forma similar, na implementação utilizando o Choreography Pattern nós temos um message broker ao invés de um serviço orquestrador e os serviços são inscritos nos eventos relevantes.
A diferença é que na ausência de um serviço orquestrador, os próprios serviços que participam da transação distribuída ficam encarregados de engatilhar a próxima operação, seja ela a próxima operação do caminho feliz ou a do caminho triste. Tudo isso através do envio de mensagens ao broker.
Com qualquer uma dessas estratégias, conseguimos realizar transações atomicamente envolvendo diversas aplicações e sem um banco de dados centralizado, resolvendo problemas de consistência e evitando bugs no futuro. :D
Um exemplo palpável
Para terminarmos tendo uma noção prática de tudo que foi explicado, vamos a um exemplo prático. Na explicação passo a passo do fluxo ocultei os caminhos interrompidos de erro da imagem para não poluir o fluxograma, mas ainda os mencionarei.
Cenário
Temos um produto de venda de tickets para eventos no geral, onde diversas organizações postam seus eventos e o usuário tem a possibilidade de comprar os ingressos. O usuário fará um pedido e faremos validações de segurança, cobrança e por fim reservaremos o ticket, realizando a cobrança e enviando um email para o cliente.
Fluxo
- O usuário compra um ticket no site e o site manda a request para o Orquestrador.
- O Orquestrador envia a request para o serviço de pedidos;
- Que salva o pedido como Pending;
- E envia a resposta de volta para o Orquestrador.
- caso a resposta seja de erro, o orquestrador retorna o erro para o usuário.
- O orquestrador chama o serviço de Segurança, para validar a identidade do cliente.
- O serviço valida os dados em seu banco de dados;
- E envia a resposta de volta para o Orquestrador.
- caso a resposta seja negativa ou de erro, o serviço de segurança avisa o orquestrador, que chama o serviço de pedido com a operação compensatória e depois retorna o erro para o usuário.
- O Orquestrador chama o serviço de Cobrança;
- Que chama o provedor de crédito do cliente e cria uma transação em status Pending.
- O serviço de Cobrança recebe a resposta de volta do provedor de crédito.
- Se a resposta for negativa ou erro, ele avisa o orquestrador, que reproduz os passos do número 7a.
- O serviço salva a cobrança como pending no seu banco de dados;
- E envia a resposta de volta para o Orquestrador.
- O orquestrador chama o serviço de reserva;
- Que requisita a API do evento para reservar os assentos/ingressos de fato;
- E recebe a resposta.
- Caso a resposta seja negativa (as vezes o assento já foi comprado em outra plataforma) ou erro, o serviço de reserva devolve essa resposta para o orquestrador, que avisa o pedido de cobrança para cancelar a transação pendente e o serviço de pedido para cancelar o pedido pendente, além de retornar o cancelamento para o usuário.
- A reserva é então salva no banco de dados do serviço de reserva;
- E avisada ao orquestrador;
- Que então chama novamente o serviço de cobrança, para fazer a cobrança da transação que estava pendente;
- E o serviço de Pedido, para alterá-lo de “pendente” para “efetivado”.
- O orquestrador então chama o serviço de email;
- Que envia um email para o usuário;
- E retorna a resposta para o Orquestrador, finalizando o fluxo.