Cloud & Infraestrutura

Guia Completo de Serverless Patterns: SAGA, Circuit Breaker e Idempotência

8 min de leitura

Guia Completo de Serverless Patterns: SAGA, Circuit Breaker e Idempotência

Padrão SAGA: Orquestrando Transações Distribuídas Em arquiteturas serverless, a ausência de transações ACID nativas exige uma abordagem inteligente para manter consistência em múltiplos serviços. O padrão SAGA resolve isso dividindo uma transação grande em uma sequência de transações locais, cada uma com sua própria compensação (rollback). Existem duas variações principais: coreografia (baseada em eventos) e orquestração (controlada centralmente). A orquestração é mais explícita e recomendada em ambientes serverless. Um orquestrador centralizado coordena as etapas, chamando serviços em sequência e, se algo falhar, executa as ações de compensação. No exemplo abaixo, usamos AWS Step Functions (infraestrutura como código com Terraform): Circuit Breaker: Proteção Contra Falhas em Cascata O Circuit Breaker é essencial em sistemas serverless para evitar que falhas em um serviço provoquem sobrecarga em outros. O padrão funciona como um disjuntor elétrico: monitora requisições e, após um limite de falhas, "abre o circuito" bloqueando novas tentativas até recuperação. Implementaremos usando Python com bibliotecas nativas e cache para rastrear falhas: Idempotência:

<h2>Padrão SAGA: Orquestrando Transações Distribuídas</h2>

<p>Em arquiteturas serverless, a ausência de transações ACID nativas exige uma abordagem inteligente para manter consistência em múltiplos serviços. O padrão SAGA resolve isso dividindo uma transação grande em uma sequência de transações locais, cada uma com sua própria compensação (rollback). Existem duas variações principais: coreografia (baseada em eventos) e orquestração (controlada centralmente).</p>

<p>A orquestração é mais explícita e recomendada em ambientes serverless. Um orquestrador centralizado coordena as etapas, chamando serviços em sequência e, se algo falhar, executa as ações de compensação. No exemplo abaixo, usamos AWS Step Functions (infraestrutura como código com Terraform):</p>

<pre><code class="language-hcl">resource &quot;aws_sfn_state_machine&quot; &quot;pedido_saga&quot; {

name = &quot;pedido-saga&quot;

role_arn = aws_iam_role.step_functions_role.arn

definition = jsonencode({

Comment = &quot;Saga para processamento de pedido&quot;

StartAt = &quot;ReservarInventario&quot;

States = {

ReservarInventario = {

Type = &quot;Task&quot;

Resource = &quot;arn:aws:states:::lambda:invoke&quot;

Parameters = {

FunctionName = &quot;reservar-inventario&quot;

Payload = {

pedido_id = &quot;$.pedido_id&quot;

items = &quot;$.items&quot;

}

}

Next = &quot;ProcessarPagamento&quot;

Catch = [{

ErrorEquals = [&quot;States.ALL&quot;]

Next = &quot;CompensarReserva&quot;

}]

}

ProcessarPagamento = {

Type = &quot;Task&quot;

Resource = &quot;arn:aws:states:::lambda:invoke&quot;

Parameters = {

FunctionName = &quot;processar-pagamento&quot;

Payload = {

pedido_id = &quot;$.pedido_id&quot;

valor = &quot;$.valor&quot;

}

}

Next = &quot;ConfirmarPedido&quot;

Catch = [{

ErrorEquals = [&quot;States.ALL&quot;]

Next = &quot;CompensarPagamento&quot;

}]

}

ConfirmarPedido = {

Type = &quot;Pass&quot;

Result = {

status = &quot;confirmado&quot;

}

End = true

}

CompensarReserva = {

Type = &quot;Task&quot;

Resource = &quot;arn:aws:states:::lambda:invoke&quot;

Parameters = {

FunctionName = &quot;cancelar-reserva&quot;

Payload = {

pedido_id = &quot;$.pedido_id&quot;

}

}

Next = &quot;FalhaProcessamento&quot;

}

CompensarPagamento = {

Type = &quot;Task&quot;

Resource = &quot;arn:aws:states:::lambda:invoke&quot;

Parameters = {

FunctionName = &quot;reembolsar-pagamento&quot;

Payload = {

pedido_id = &quot;$.pedido_id&quot;

}

}

Next = &quot;CompensarReserva&quot;

}

FalhaProcessamento = {

Type = &quot;Fail&quot;

Error = &quot;PedidoNaoProcessado&quot;

Cause = &quot;Falha na compensação&quot;

}

}

})

}</code></pre>

<h2>Circuit Breaker: Proteção Contra Falhas em Cascata</h2>

<p>O Circuit Breaker é essencial em sistemas serverless para evitar que falhas em um serviço provoquem sobrecarga em outros. O padrão funciona como um disjuntor elétrico: monitora requisições e, após um limite de falhas, &quot;abre o circuito&quot; bloqueando novas tentativas até recuperação.</p>

<p>Implementaremos usando Python com bibliotecas nativas e cache para rastrear falhas:</p>

<pre><code class="language-python">import json

import time

from enum import Enum

from datetime import datetime, timedelta

class CircuitState(Enum):

CLOSED = &quot;closed&quot;

OPEN = &quot;open&quot;

HALF_OPEN = &quot;half_open&quot;

class CircuitBreaker:

def __init__(self, failure_threshold=5, recovery_timeout=60):

self.failure_threshold = failure_threshold

self.recovery_timeout = recovery_timeout

self.failure_count = 0

self.last_failure_time = None

self.state = CircuitState.CLOSED

def call(self, func, args, *kwargs):

if self.state == CircuitState.OPEN:

if self._should_attempt_reset():

self.state = CircuitState.HALF_OPEN

else:

raise Exception(&quot;Circuit breaker is OPEN&quot;)

try:

result = func(args, *kwargs)

self._on_success()

return result

except Exception as e:

self._on_failure()

raise e

def _on_success(self):

self.failure_count = 0

self.state = CircuitState.CLOSED

def _on_failure(self):

self.failure_count += 1

self.last_failure_time = datetime.now()

if self.failure_count &gt;= self.failure_threshold:

self.state = CircuitState.OPEN

def _should_attempt_reset(self):

return (datetime.now() - self.last_failure_time).seconds &gt;= self.recovery_timeout

Lambda handler com Circuit Breaker

circuit_breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=30)

def chamar_servico_externo(pedido_id):

import requests

response = requests.post(

&quot;https://api-externa.com/processar&quot;,

json={&quot;pedido_id&quot;: pedido_id},

timeout=5

)

response.raise_for_status()

return response.json()

def lambda_handler(event, context):

try:

resultado = circuit_breaker.call(chamar_servico_externo, event[&#039;pedido_id&#039;])

return {

&quot;statusCode&quot;: 200,

&quot;body&quot;: json.dumps(resultado)

}

except Exception as e:

return {

&quot;statusCode&quot;: 503,

&quot;body&quot;: json.dumps({&quot;erro&quot;: str(e)})

}</code></pre>

<h2>Idempotência: Garantindo Segurança em Retentativas</h2>

<p>Idempotência significa que executar a mesma operação múltiplas vezes produz o mesmo resultado de uma única execução. Em serverless, onde timeouts e retentativas são comuns, este padrão é crítico. A solução envolve gerar e rastrear IDs únicos de requisição usando DynamoDB como armazenamento de estado.</p>

<pre><code class="language-python">import json

import hashlib

import boto3

from datetime import datetime, timedelta

dynamodb = boto3.resource(&#039;dynamodb&#039;)

idempotency_table = dynamodb.Table(&#039;operacoes-idempotentes&#039;)

def gerar_idempotency_key(pedido_id, operacao):

&quot;&quot;&quot;Gera uma chave única para a operação&quot;&quot;&quot;

conteudo = f&quot;{pedido_id}-{operacao}&quot;

return hashlib.sha256(conteudo.encode()).hexdigest()

def processar_com_idempotencia(pedido_id, operacao, funcao_negocio):

&quot;&quot;&quot;Wrapper que implementa idempotência&quot;&quot;&quot;

chave = gerar_idempotency_key(pedido_id, operacao)

Verificar se operação já foi executada

try:

resposta = idempotency_table.get_item(Key={&#039;idempotency_key&#039;: chave})

if &#039;Item&#039; in resposta:

print(f&quot;Operação já executada, retornando resultado anterior&quot;)

return resposta[&#039;Item&#039;][&#039;resultado&#039;]

except Exception as e:

print(f&quot;Erro ao consultar idempotência: {e}&quot;)

Executar operação

try:

resultado = funcao_negocio(pedido_id)

Armazenar resultado para futuras retentativas

idempotency_table.put_item(

Item={

&#039;idempotency_key&#039;: chave,

&#039;pedido_id&#039;: pedido_id,

&#039;operacao&#039;: operacao,

&#039;resultado&#039;: resultado,

&#039;timestamp&#039;: datetime.now().isoformat(),

&#039;ttl&#039;: int((datetime.now() + timedelta(hours=24)).timestamp())

}

)

return resultado

except Exception as e:

raise e

def lambda_handler(event, context):

pedido_id = event[&#039;pedido_id&#039;]

def processar_pagamento(pid):

Lógica de negócio aqui

return {&quot;status&quot;: &quot;sucesso&quot;, &quot;transacao_id&quot;: &quot;TXN-123&quot;}

resultado = processar_com_idempotencia(

pedido_id,

&quot;processar_pagamento&quot;,

processar_pagamento

)

return {

&quot;statusCode&quot;: 200,

&quot;body&quot;: json.dumps(resultado)

}</code></pre>

<h2>Conclusão</h2>

<p>Dominar estes três padrões é fundamental para construir sistemas serverless resilientes e confiáveis. <strong>SAGA</strong> permite orquestrar transações complexas com compensações automáticas; <strong>Circuit Breaker</strong> protege seus serviços de falhas em cascata e degradação de performance; <strong>Idempotência</strong> garante que retentativas automáticas nunca causem duplicação de dados ou efeitos colaterais indesejados. Juntos, eles formam a base de uma arquitetura serverless pronta para produção.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://docs.aws.amazon.com/step-functions/" target="_blank" rel="noopener noreferrer">AWS Step Functions Documentation</a></li>

<li><a href="https://chrisrichardson.net/post/sagas/2019/01/02/sagas-part-1.html" target="_blank" rel="noopener noreferrer">The Saga Pattern - Chris Richardson</a></li>

<li><a href="https://martinfowler.com/bliki/CircuitBreaker.html" target="_blank" rel="noopener noreferrer">Circuit Breaker Pattern - Martin Fowler</a></li>

<li><a href="https://docs.aws.amazon.com/lambda/latest/dg/idempotent-functions.html" target="_blank" rel="noopener noreferrer">Idempotency in AWS Lambda - Official Guide</a></li>

<li><a href="https://www.oreilly.com/library/view/building-microservices/9781492025269/" target="_blank" rel="noopener noreferrer">Building Microservices with Serverless - O&#039;Reilly</a></li>

</ul>

Comentários

Mais em Cloud & Infraestrutura

Route 53: DNS, Health Checks e Roteamento de Tráfego Global na Prática
Route 53: DNS, Health Checks e Roteamento de Tráfego Global na Prática

Fundamentos do Route 53: DNS na AWS O Route 53 é o serviço de DNS gerenciado...

IAM Avançado: SCP, Permission Boundaries e Attribute-Based Access: Do Básico ao Avançado
IAM Avançado: SCP, Permission Boundaries e Attribute-Based Access: Do Básico ao Avançado

IAM Avançado: SCP, Permission Boundaries e Attribute-Based Access O que é IAM...

Dominando Kinesis: Data Streams, Firehose e Analytics para Dados em Tempo Real em Projetos Reais
Dominando Kinesis: Data Streams, Firehose e Analytics para Dados em Tempo Real em Projetos Reais

Entendendo os Três Pilares do Kinesis O AWS Kinesis é um serviço gerenciado p...