Marcio Cunha

Processamento de Transações Distribuídas com Two-Phase Commit e Padrões SAGA em Microsserviços Go

Descubra como garantir consistência de dados em microsserviços usando Go. Analisamos os limites do Two-Phase Commit e a resiliência assíncrona dos padrões SAGA.

Marcio Cunha•5 min
Também disponível em:EnglishEspañol
Resumo
  • Sistemas distribuídos exigem quebra de monólitos, gerando desafios complexos de sincronização de dados entre bases isoladas.
  • O protocolo Two-Phase Commit oferece consistência estrita, mas sacrifica drasticamente a disponibilidade e trava recursos em caso de falhas.
  • O padrão SAGA substitui bloqueios síncronos por uma cadeia de transações locais combinadas com ações compensatórias.
  • A linguagem Go permite implementar transações resilientes combinando goroutines e canais para gerenciar fluxos assíncronos.
  • A escolha entre consistência imediata ou eventual depende diretamente do modelo de negócio e da tolerância a latência do sistema.

O Desafio da Consistência de Dados em Microsserviços

Quando separamos um sistema monolítico grande em vários microsserviços menores, cada pedaço ganha seu próprio banco de dados isolado. Na prática, isso significa que uma simples compra no e-commerce — que antes atualizava o estoque, cobrava o cartão e gerava a nota fiscal em uma única transação de banco — agora precisa coordenar múltiplos serviços independentes na rede. Se a rede falhar na metade do caminho, o sistema fica com dados inconsistentes, como o cliente cobrado mas o produto sem estoque.

Garantir que todas as etapas aconteçam com sucesso ou que tudo seja desfeito é o grande problema das transações distribuídas. Na engenharia de software, buscamos a consistência para evitar que o dinheiro desapareça ou que pedidos fiquem perdidos no limbo digital. Para resolver isso, a indústria criou abordagens como o protocolo de dois passos e sequências de passos compensatórios, cada uma com trade-offs profundos de performance e complexidade.

Como Funciona o Two-Phase Commit na Prática

O Two-Phase Commit, conhecido como 2PC, é um protocolo clássico que tenta garantir que vários bancos de dados atualizem seus registros ao mesmo tempo. Na primeira fase, chamada de preparação, um coordenador central pergunta a todos os bancos envolvidos se eles conseguem salvar os dados. Cada banco verifica suas travas, garante que há espaço e responde com um voto positivo ou negativo. Na segunda fase, se todo mundo votou sim, o coordenador manda o comando para efetivar a gravação de vez.

A grande armadilha do Two-Phase Commit é o bloqueio. Enquanto os bancos esperam a ordem final do coordenador, eles mantêm os registros travados para evitar alterações concorrentes. Na prática, se o coordenador cai ou a rede fica lenta durante essa janela de espera, os recursos ficam indisponíveis, derrubando a performance geral do sistema. Por causa dessa fragilidade a quedas de rede, arquiteturas modernas evitam o 2PC em ambientes de alta escala.

A Alternativa do Padrão SAGA para Alta Disponibilidade

Para fugir dos travamentos rígidos do 2PC, a arquitetura moderna abraça o padrão SAGA. Em vez de uma única transação global que bloqueia tudo, a SAGA divide o processo em uma sequência de transações locais. Cada microsserviço executa sua operação no seu próprio banco e emite um evento para o próximo passo. Na prática, isso significa que a consistência deixa de ser imediata e passa a ser eventual, ou seja, o sistema caminha para o estado correto de forma assíncrona.

O grande diferencial da SAGA é a compensação. Se a terceira etapa de um fluxo de cinco falha, o sistema não pode simplesmente dar um rollback automático, pois os passos anteriores já confirmaram as alterações em bancos separados. O que a SAGA faz é disparar transações compensatórias na ordem inversa, como emitir um estorno no cartão ou devolver o item para o estoque. É como desfazer um bolo cru que já foi colocado no forno através de um processo controlado de correção.

Implementando Transações Assíncronas em Go

A linguagem Go oferece ferramentas excelentes para lidar com fluxos assíncronos e concorrência através de goroutines (pequenas tarefas leves que rodam em paralelo) e canais de comunicação. Ao construir uma SAGA em Go, modelamos cada etapa do fluxo como um comando independente que pode ser executado, monitorado e revertido se necessário. A criação de estruturas limpas ajuda a manter o código legível e fácil de testar unitariamente.

package main

import (
	"context"
	"fmt"
	"time"
)

type Step struct {
	Name         string
	Execute      func(ctx context.Context) error
	Compensate   func(ctx context.Context) error
}

func RunSaga(ctx context.Context, steps []Step) error {
	var executed []Step
	for _, step := range steps {
		fmt.Printf("Executando etapa: %s\n", step.Name)
		if err := step.Execute(ctx); err != nil {
			fmt.Printf("Erro na etapa %s. Iniciando compensações...\n", step.Name)
			reverseSteps(ctx, executed)
			return err
		}
		executed = append(executed, step)
	}
	return nil
}

func reverseSteps(ctx context.Context, steps []Step) {
	for i := len(steps) - 1; i >= 0; i-- {
		step := steps[i]
		if step.Compensate != nil {
			_ = step.Compensate(ctx)
		}
	}
}

func main() {
	ctx := context.Background()
	steps := []Step{
		{
			Name: "ReservarEstoque",
			Execute: func(c context.Context) error { return nil },
			Compensate: func(c context.Context) error { fmt.Println("Estoque devolvido."); return nil },
		},
	}
	_ = RunSaga(ctx, steps)
}

O código acima demonstra uma estrutura básica de orquestração de SAGA em Go, onde um slice armazena as etapas bem-sucedidas para garantir que a reversão ocorra na ordem correta se algo der errado. Essa abordagem garante controle total sobre o fluxo sem depender de protocolos de rede complexos e bloqueantes.

Orquestração versus Coreografia em Sistemas Distribuídos

Ao implementar o padrão SAGA, os engenheiros precisam decidir entre dois modelos de controle: orquestração ou coreografia. Na coreografia, os microsserviços conversam entre si por meio de eventos publicados em um barramento de mensagens, sem um controlador central. Cada serviço escuta o que lhe interessa, faz seu trabalho e avisa o próximo. Na prática, isso reduz o acoplamento estrutural, mas dificulta a visualização do fluxo completo conforme o sistema cresce.

Já na orquestração, existe um componente dedicado — chamado de orquestrador — cuja única responsabilidade é ditar a ordem dos passos e decidir quando chamar as compensações. Embora adicione um ponto central de dependência, o orquestrador simplifica drasticamente a depuração e o rastreamento de falhas em ambientes corporativos complexos. A escolha entre os dois caminhos depende da maturidade da equipe e da visibilidade exigida pelas regras de negócio.

Considerações Finais sobre Consistência em Arquiteturas Modernas

O processamento de transações distribuídas deixa claro que a engenharia de software é a arte de gerenciar concessões e trade-offs. Enquanto o TwoPhase Commit busca uma consistência rígida que penaliza a disponibilidade, os padrões SAGA abraçam a realidade imperfeita das redes distribuídas através de consistência eventual e ações compensatórias. Compreender essas diferenças evita arquiteturas frágeis e garante sistemas capazes de escalar com segurança.

Dominar essas ferramentas em linguagens eficientes como Go permite construir microsserviços resilientes, preparados para lidar com falhas inevitáveis de infraestrutura sem corromper os dados dos usuários. O planejamento cuidadoso dos fluxos e a clareza nas regras de compensação continuam sendo os pilares fundamentais para o sucesso de qualquer aplicação moderna em larga escala.