Pular para o conteúdo principal

Microsserviço 6

· 5 min para ler
Leandro Andrade
Leandro Andrade
Software Developer

Trabalhando com microsserviços, utilizar vários deles colaborando entre si para implementar um processo de negócio é natural. Comunicação com banco de dados, acesso à rede e filesystem, tudo isso está contido no contexto dos microsserviços.

Especificamente sobre transações de banco de dados, temos várias ações que precisam ocorrer, mas queremos tratá-las como uma unidade. Utilizamos transações para garantir que uma ou mais mudanças de estado sejam realizadas com sucesso.

Para que essa garantia aconteça, o banco de dados fornece o modelo ACID:

  • atomicidade: ou todas as operações acontecem, ou todas falham;
  • consistência: as alterações mantêm o banco de dados em um estado válido;
  • isolamento: várias transações podem ser executadas simultaneamente sem interferirem entre si; e
  • durabilidade: após a conclusão de uma transação, os dados não serão perdidos caso haja falha no sistema.

Focando no princípio de atomicidade, ao decompor uma operação única em duas transações separadas, perdemos essa garantia. A falta de atomicidade pode causar problemas significativos, especialmente ao migrar sistemas que dependem dessa propriedade.

Para contornar essa situação, surge a transação distribuída com two-phase commit.

Two-Phase Commit

O two-phase commit é usado para permitir mudanças transacionais em sistemas distribuídos, nos quais vários processos participam da operação. Esse algoritmo é dividido em duas fases:

  • fase de votação: um coordenador consulta os workers participantes para confirmar se a mudança de estado pode ser feita. Se todos confirmarem, o algoritmo passa para a próxima fase (commit). Se algum worker negar, a operação é cancelada;
  • fase de commit: o coordenador envia a requisição de commit para todos os workers. Não é possível garantir que todos os commits ocorram no mesmo instante, pois as mensagens podem chegar em momentos diferentes para cada worker. Quanto mais workers, maior a latência do sistema e maior o período em que os recursos estarão travados (distributed lock).

Assim, evite essa abordagem sempre que possível, mantendo os estados em um único banco de dados e centralizando a gestão em um único serviço.

Sagas

As sagas são outra abordagem de transação distribuída. Entretanto, não teremos a atomicidade fornecida pelo ACID. A atomicidade existirá apenas dentro de cada transação individual dos serviços que compõem a saga.

Com sagas, é possível executar operações que envolvem vários serviços, coordenando mudanças de estado sem o uso de locking, permitindo uma execução independente.

O primeiro ponto de apoio das sagas é a necessidade de modelar explicitamente o processo de negócio. Dividimos o processo em um conjunto de chamadas que serão feitas para diferentes serviços colaborativos.

Como a saga é dividida em transações individuais executadas em serviços distintos, devemos considerar a recuperação em caso de falha. Existem duas abordagens:

  • recuperação com retrocesso (backward recovery): rollback de transações que já tenham feito commit;
  • recuperação com avanço (forward recovery): a partir de checkpoints, continuar do ponto onde ocorreu a falha. Para isso, precisamos de informações suficientes para que novas tentativas sejam realizadas.

O objetivo é recuperar falhas de negócio, e não falhas técnicas. Falhas técnicas, como um erro 500, devem ser tratadas separadamente.

Como são várias transações envolvidas em uma saga, para fazer rollback de uma transação que já teve commit, precisamos de uma transação compensatória, um rollback semântico. Por exemplo, um email enviado ao cliente informando que o pedido está a caminho. Não há como "desenviar" o email. A transação compensatória seria enviar outro email ao cliente informando, por exemplo, que houve um problema no pedido e que este foi cancelado.

Antes de chegar ao ponto de usar uma transação compensatória, vale a pena reorganizar os passos do workflow original. Às vezes, é possível simplificar o rollback ajustando o fluxo do workflow.

Implementando Sagas

Existem dois estilos de implementação de sagas:

  • sagas orquestradas: utilizam um coordenador central que controla o que acontece e quando acontece. Esse coordenador chama os serviços para executar um workflow usando request/response. A vantagem é a facilidade de compreensão, pois a análise ocorre em um único ponto. A desvantagem é o maior acoplamento, já que o coordenador precisa conhecer todos os serviços envolvidos. Além disso, o coordenador pode acabar absorvendo lógica de negócio que deveria estar nos serviços;
  • sagas coreografadas: abordagem menos acoplada, distribui a responsabilidade do workflow entre os serviços que colaboram entre si. Geralmente, faz uso intensivo de eventos entre os serviços. Eventos são gerados, e os microsserviços reagem aos eventos recebidos. Não enviamos eventos diretamente aos microsserviços; apenas os geramos, e os microsserviços interessados reagem. Vários microsserviços podem reagir a eventos se usarmos um tópico.

TL;DR:

  • sagas orquestradas: request/response;
  • sagas coreografadas: event-driven.

Correlação

Independentemente da abordagem, o uso de um ID de correlação é essencial para rastrear o workflow e saber o estado atual, especialmente em sagas coreografadas. É com esse ID que teremos toda rastreabilidade de toda a cadeia de chamadas entre os serviços. A ausência desse ID dificultará muito a observabilidade do fluxo.

Conclusão

A gestão de transações em microsserviços requer equilíbrio entre consistência, disponibilidade e complexidade. O two-phase commit oferece atomicidade, mas pode comprometer o desempenho e a escalabilidade devido ao bloqueio distribuído. Já as sagas proporcionam maior flexibilidade e resiliência, ao custo de abrir mão da atomicidade plena, exigindo compensações e um design de processo mais cuidadoso. A escolha entre uma ou outra — ou mesmo a combinação de ambas — deve considerar o domínio do negócio, os requisitos de consistência e a resiliência operacional desejada.