Entre os vários desafios que encontramos em uma arquitetura de microsserviços descentralizada, um dos desafios que mais enfrentamos como engenheiros de software é como garantir a integridade dos dados em transações que cruzam as fronteiras de múltiplos serviços. Isso porque transações distribuídas tradicionais criam bloqueios sistêmicos e gargalos de performance que eliminam as vantagens de um sistema descentralizado. Para isso usamos SAGA!
O padrão de SAGA, substituí a transação global por uma sequência de transações independentes localmente dentro de uma esteira dos microsserviços. Quando implementado através de Coreografia com um EventBus em um modelo Publish/Subscribe (Pub/Sub), o padrão atinge seu potencial máximo. Cada serviço realiza sua operação e publica um evento no barramento, permitindo que outros serviços reajam de forma assíncrona, resiliente e altamente desacoplada.

A Dinâmica do SAGA Pattern
Em arquiteturas monolíticas, uma transação que envolve várias tabelas é gerenciada diretamente pelo banco de dados, que garante propriedades ACID (atomicidade, consistência, isolamento e durabilidade). Isso permite que todas as operações sejam confirmadas ou revertidas juntas.
Já em uma arquitetura de microsserviços, cada serviço possui seu próprio banco de dados, isolado dos demais. Nesse cenário, não é possível usar uma única transação distribuída entre todos os serviços de forma simples.
Para lidar com isso, utiliza-se o SAGA Pattern. Ele resolve o problema dividindo uma grande transação de negócio em uma sequência de transações locais, cada uma executada por um microsserviço.
O funcionamento é o seguinte:
- Um serviço executa sua transação local.
- Após concluir, ele publica um evento indicando que sua etapa foi finalizada.
- Esse evento dispara a próxima etapa do fluxo em outro microsserviço.
Caso alguma etapa falhe, o SAGA não faz um rollback global como em um banco tradicional. Em vez disso, ele executa transações de compensação, que desfazem os efeitos das etapas que já haviam sido concluídas pelos serviços anteriores.
Assim, o SAGA permite manter consistência entre serviços mesmo em sistemas distribuídos, sem depender de transações ACID globais.
Coreografia em modelos Pub/Sub
Existem duas formas principais de implementar o SAGA Pattern: orquestração e coreografia. No modelo de orquestração, existe um componente central responsável por coordenar o fluxo da transação, decidindo qual serviço deve executar cada etapa. Já na coreografia, essa coordenação não é centralizada. O fluxo emerge da comunicação entre os próprios serviços. É nesse ponto que o modelo Pub/Sub com um EventBus se destaca.
Na abordagem coreografada, cada microsserviço reage aos acontecimentos do sistema. Quando um serviço conclui uma etapa do processo, ele publica um evento no barramento de eventos. Outros serviços que têm interesse nesse evento o consomem e executam suas próprias transações locais. Ao finalizar, também publicam novos eventos, que continuam propagando o fluxo de negócio. Assim, o processo completo vai sendo conduzido de forma distribuída, sem que exista um coordenador central determinando cada passo.
Esse modelo traz um alto nível de desacoplamento, já que os serviços não precisam conhecer diretamente uns aos outros — apenas o formato e o significado dos eventos que circulam no sistema. Isso facilita a evolução da arquitetura, pois novos serviços podem começar a reagir a eventos já existentes sem exigir mudanças nos serviços que os publicam.
Outro ponto importante é a resiliência. Quando se utiliza um EventBus como Kafka ou RabbitMQ, as mensagens podem ficar armazenadas até que os consumidores estejam disponíveis para processá-las. Dessa forma, se um serviço estiver temporariamente indisponível, ele ainda poderá consumir os eventos quando voltar ao ar.
Além disso, a arquitetura tende a escalar bem. Como os eventos são compartilhados em um barramento comum, novos serviços podem ser adicionados simplesmente passando a “escutar” determinados eventos, ampliando funcionalidades sem impactar diretamente os serviços já existentes.
Implementando a Lógica de Compensação
A parte mais crítica de um SAGA é lidar com o rollback em um sistema distribuído. Imagine, por exemplo, um fluxo em que o serviço de Pedidos cria um pedido e, em seguida, o serviço de Pagamento tenta processar a cobrança. Se o pagamento falhar, o sistema precisa garantir que o pedido criado anteriormente seja cancelado.
Como não existe uma transação global envolvendo todos os serviços, o rollback não acontece automaticamente como em um banco de dados tradicional. Em vez disso, ele é realizado por meio de transações de compensação. Isso significa que, quando uma etapa falha, um evento de erro é publicado no sistema para informar aos serviços anteriores que eles precisam desfazer suas ações.
Nesse cenário, o serviço de Pedidos precisa estar preparado para reagir a esse tipo de evento. Depois de criar um pedido e publicar o evento correspondente, ele também deve escutar eventos de falha relacionados ao fluxo — como uma falha no pagamento — para então executar a lógica de compensação, como marcar o pedido como cancelado.
Abaixo está um exemplo simplificado de como um serviço pode criar um pedido, publicar um evento e reagir a uma falha de pagamento para executar a compensação dentro de um fluxo SAGA coreografado.
A ideia é que o OrderService publique o evento ORDER_CREATED quando o pedido é criado e fique inscrito no EventBus para escutar eventos como PAYMENT_FAILED. Quando esse evento ocorrer, o serviço executa a transação de compensação, cancelando o pedido.
type Event = {
type: string;
payload: any;
timestamp: Date;
};
interface EventBus {
publish(event: Event): Promise<void>;
subscribe(eventType: string, handler: (event: Event) => Promise<void>): void;
}
export class OrderService {
constructor(private readonly eventBus: EventBus) {
this.registerSubscriptions();
}
private registerSubscriptions() {
this.eventBus.subscribe('PAYMENT_FAILED', this.handlePaymentFailed.bind(this));
}
async createOrder(orderData: any): Promise<void> {
try {
const order = await this.saveToDatabase(orderData);
await this.eventBus.publish({
type: 'ORDER_CREATED',
payload: order,
timestamp: new Date()
});
} catch (error) {
throw error;
}
}
async handlePaymentFailed(event: Event): Promise<void> {
const orderId = event.payload.orderId;
await this.updateOrderStatus(orderId, 'CANCELLED');
await this.eventBus.publish({
type: 'ORDER_CANCELLED',
payload: { orderId },
timestamp: new Date()
});
}
private async saveToDatabase(data: any) {
return { id: 1, ...data };
}
private async updateOrderStatus(id: number, status: string) {
console.log(`Order ${id} updated to ${status}`);
}
}
export class PaymentService {
constructor(private readonly eventBus: EventBus) {
this.registerSubscriptions();
}
private registerSubscriptions() {
this.eventBus.subscribe(
'ORDER_CREATED',
this.handleOrderCreated.bind(this)
);
}
async handleOrderCreated(event: Event): Promise<void> {
const order = event.payload;
try {
const payment = await this.processPayment(order);
await this.eventBus.publish({
type: 'PAYMENT_COMPLETED',
payload: {
orderId: order.id,
paymentId: payment.id
},
timestamp: new Date()
});
} catch (error) {
await this.eventBus.publish({
type: 'PAYMENT_FAILED',
payload: {
orderId: order.id,
reason: 'Payment processing failed'
},
timestamp: new Date()
});
}
}
private async processPayment(order: any) {
// Simulação de processamento de pagamento
const success = Math.random() > 0.5;
if (!success) {
throw new Error('Payment failed');
}
return {
id: Math.floor(Math.random() * 1000),
orderId: order.id
};
}
}
Nesse fluxo:
- O pedido é criado e persistido.
- O serviço publica
ORDER_CREATED. - Outro microsserviço (por exemplo, Pagamento) consome esse evento e tenta processar o pagamento.
- Se o pagamento falhar, ele publica
PAYMENT_FAILED. - O
OrderService, que está inscrito nesse evento, executa a compensação, cancelando o pedido e publicandoORDER_CANCELLED.
Esse padrão mantém os serviços desacoplados, permitindo que cada um reaja apenas aos eventos relevantes no barramento.
Conclusão: O Equilíbrio entre Complexidade e Escalabilidade
Implementar o SAGA Pattern usando um EventBus no modelo Pub/Sub é um passo importante na evolução arquitetural de sistemas baseados em microsserviços. Ao trocar transações ACID tradicionais por um modelo de consistência eventual, surgem alguns desafios — principalmente em observabilidade, rastreabilidade e monitoramento de eventos. Ainda assim, os ganhos em escalabilidade, resiliência e flexibilidade costumam compensar essa complexidade adicional.
Quando usamos uma abordagem orientada a eventos, os serviços ficam realmente desacoplados. Cada um passa a ser responsável apenas pelo que lhe cabe, reagindo aos eventos do sistema. Em caso de falha, entram em ação as transações de compensação, que ajudam a preservar a integridade dos dados sem depender de transações distribuídas bloqueantes — que costumam se tornar gargalos em sistemas distribuídos.
Dominar esse tipo de arquitetura acaba sendo um grande diferencial para engenheiros e arquitetos de software. Quando o SAGA Pattern é combinado com uma camada de mensageria bem estruturada, o resultado são sistemas mais robustos, resilientes e preparados para evoluir e escalar de forma sustentável.




Deixe um comentário