AWS Admin

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

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:

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):

resource "aws_sfn_state_machine" "pedido_saga" {
  name       = "pedido-saga"
  role_arn   = aws_iam_role.step_functions_role.arn
  definition = jsonencode({
    Comment = "Saga para processamento de pedido"
    StartAt = "ReservarInventario"
    States = {
      ReservarInventario = {
        Type     = "Task"
        Resource = "arn:aws:states:::lambda:invoke"
        Parameters = {
          FunctionName = "reservar-inventario"
          Payload = {
            pedido_id = "$.pedido_id"
            items     = "$.items"
          }
        }
        Next       = "ProcessarPagamento"
        Catch = [{
          ErrorEquals = ["States.ALL"]
          Next        = "CompensarReserva"
        }]
      }
      ProcessarPagamento = {
        Type     = "Task"
        Resource = "arn:aws:states:::lambda:invoke"
        Parameters = {
          FunctionName = "processar-pagamento"
          Payload = {
            pedido_id = "$.pedido_id"
            valor     = "$.valor"
          }
        }
        Next = "ConfirmarPedido"
        Catch = [{
          ErrorEquals = ["States.ALL"]
          Next        = "CompensarPagamento"
        }]
      }
      ConfirmarPedido = {
        Type = "Pass"
        Result = {
          status = "confirmado"
        }
        End = true
      }
      CompensarReserva = {
        Type     = "Task"
        Resource = "arn:aws:states:::lambda:invoke"
        Parameters = {
          FunctionName = "cancelar-reserva"
          Payload = {
            pedido_id = "$.pedido_id"
          }
        }
        Next = "FalhaProcessamento"
      }
      CompensarPagamento = {
        Type     = "Task"
        Resource = "arn:aws:states:::lambda:invoke"
        Parameters = {
          FunctionName = "reembolsar-pagamento"
          Payload = {
            pedido_id = "$.pedido_id"
          }
        }
        Next = "CompensarReserva"
      }
      FalhaProcessamento = {
        Type = "Fail"
        Error = "PedidoNaoProcessado"
        Cause = "Falha na compensação"
      }
    }
  })
}

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:

import json
import time
from enum import Enum
from datetime import datetime, timedelta

class CircuitState(Enum):
    CLOSED = "closed"
    OPEN = "open"
    HALF_OPEN = "half_open"

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("Circuit breaker is OPEN")

        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 >= self.failure_threshold:
            self.state = CircuitState.OPEN

    def _should_attempt_reset(self):
        return (datetime.now() - self.last_failure_time).seconds >= 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(
        "https://api-externa.com/processar",
        json={"pedido_id": 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['pedido_id'])
        return {
            "statusCode": 200,
            "body": json.dumps(resultado)
        }
    except Exception as e:
        return {
            "statusCode": 503,
            "body": json.dumps({"erro": str(e)})
        }

Idempotência: Garantindo Segurança em Retentativas

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.

import json
import hashlib
import boto3
from datetime import datetime, timedelta

dynamodb = boto3.resource('dynamodb')
idempotency_table = dynamodb.Table('operacoes-idempotentes')

def gerar_idempotency_key(pedido_id, operacao):
    """Gera uma chave única para a operação"""
    conteudo = f"{pedido_id}-{operacao}"
    return hashlib.sha256(conteudo.encode()).hexdigest()

def processar_com_idempotencia(pedido_id, operacao, funcao_negocio):
    """Wrapper que implementa idempotência"""
    chave = gerar_idempotency_key(pedido_id, operacao)

    # Verificar se operação já foi executada
    try:
        resposta = idempotency_table.get_item(Key={'idempotency_key': chave})
        if 'Item' in resposta:
            print(f"Operação já executada, retornando resultado anterior")
            return resposta['Item']['resultado']
    except Exception as e:
        print(f"Erro ao consultar idempotência: {e}")

    # Executar operação
    try:
        resultado = funcao_negocio(pedido_id)

        # Armazenar resultado para futuras retentativas
        idempotency_table.put_item(
            Item={
                'idempotency_key': chave,
                'pedido_id': pedido_id,
                'operacao': operacao,
                'resultado': resultado,
                'timestamp': datetime.now().isoformat(),
                'ttl': int((datetime.now() + timedelta(hours=24)).timestamp())
            }
        )
        return resultado
    except Exception as e:
        raise e

def lambda_handler(event, context):
    pedido_id = event['pedido_id']

    def processar_pagamento(pid):
        # Lógica de negócio aqui
        return {"status": "sucesso", "transacao_id": "TXN-123"}

    resultado = processar_com_idempotencia(
        pedido_id,
        "processar_pagamento",
        processar_pagamento
    )

    return {
        "statusCode": 200,
        "body": json.dumps(resultado)
    }

Conclusão

Dominar estes três padrões é fundamental para construir sistemas serverless resilientes e confiáveis. SAGA permite orquestrar transações complexas com compensações automáticas; Circuit Breaker protege seus serviços de falhas em cascata e degradação de performance; Idempotência 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.

Referências


Artigos relacionados