🏛️ Capítulo 19: Padrões Enterprise: Resiliência e Microsserviços
Módulo 10: Paradigmas de Programação & Padrões de Projeto • Bloco 5: Arquitetura e Enterprise Patterns
🌐 Sistemas que Falham Parcialmente: Em um monólito, uma chamada de método ou funciona ou lança exceção. Em microsserviços, uma chamada de rede pode demorar indefinidamente, falhar depois de ter executado ou executar duas vezes. As Oito Falácias da Computação Distribuída (Deutsch, 1994) — “a rede é confiável”, “a latência é zero”… — derrubam quem as ignora. Neste capítulo, estudamos os padrões que tornam sistemas distribuídos resilientes: Retry com Backoff, Circuit Breaker, Saga, Transactional Outbox e CQRS.
🗺️ Mapa Conceitual do Capítulo
flowchart TD
ENT_DIST["Padrões Enterprise Distribuídos"]
ENT_DIST --> RES["1. Resiliência de Chamadas<br/>• Timeout, Retry com Backoff + Jitter<br/>• Circuit Breaker: Closed, Open, Half-Open<br/>• Bulkhead e Fallback"]
ENT_DIST --> SAGA["2. Saga (Transações Distribuídas)<br/>• Sequência de transações locais<br/>• Compensações em ordem inversa<br/>• Orquestração vs. Coreografia"]
ENT_DIST --> DADOS["3. Consistência de Dados e Eventos<br/>• Transactional Outbox + Relay<br/>• Idempotência e entrega at-least-once<br/>• CQRS: escrita e leitura separadas"]
style ENT_DIST fill:#ede7f6,stroke:#512da8,stroke-width:2px
style RES fill:#e8f5e9,stroke:#4caf50,stroke-width:1.5px
style SAGA fill:#fff3e0,stroke:#ff9800,stroke-width:1.5px
style DADOS fill:#e1f5fe,stroke:#03a9f4,stroke-width:1.5px
🔁 1. Timeout e Retry com Exponential Backoff e Jitter
A primeira linha de defesa é nunca esperar para sempre: toda chamada remota precisa de um timeout. A segunda é tentar de novo falhas transitórias (oscilação de rede, 503, deadlock de banco):
- Backoff exponencial: espera 100 ms, 200 ms, 400 ms, 800 ms… para dar tempo ao serviço de se recuperar.
- Jitter (aleatoriedade): sem ele, mil clientes que falharam juntos tentam de novo no mesmo instante, criando picos sincronizados (thundering herd). O full jitter sorteia a espera entre zero e o teto exponencial.
espera = random.uniform(0, min(teto_s, base_s * 2 ** tentativa)) # full jitter
⚠️ Só repita operações idempotentes. Repetir um
POST /pagamentosque já foi processado (mas cuja resposta se perdeu) cobra o cliente duas vezes. Use chaves de idempotência (Idempotency-Key) para que o servidor reconheça e descarte duplicatas.
⚡ 2. O Padrão Circuit Breaker (Disjuntor)
Descrito por Michael Nygard em Release It! (2007) e popularizado pelo Netflix Hystrix, Resilience4j (Java) e Polly (.NET).
2.1 O Problema: Falhas em Cascata
Se o serviço de frete trava, cada requisição de checkout prende uma thread esperando o timeout. Em segundos, o pool de threads do checkout se esgota — e agora o checkout está fora do ar, derrubando quem depende dele. Retry, nesse cenário, piora o problema.
2.2 A Máquina de Estados
stateDiagram-v2
[*] --> Closed
Closed --> Closed: sucesso (zera contador)
Closed --> Open: N falhas seguidas (ex. 3)
Open --> Open: chamadas rejeitadas com fast-fail + fallback
Open --> HalfOpen: cooldown expirou (ex. 30s)
HalfOpen --> Closed: chamada de teste com sucesso
HalfOpen --> Open: chamada de teste falhou
- CLOSED (fechado): operação normal; as falhas são contadas.
- OPEN (aberto): o disjuntor rejeita imediatamente (fast-fail), sem chamar o serviço, e aciona um fallback (valor em cache, resposta padrão, fila para depois).
- HALF-OPEN (semiaberto): após o cooldown, deixa passar uma chamada de teste para decidir se volta a fechar.
2.3 Padrões Companheiros
- Bulkhead (anteparas): pools de threads/conexões separados por dependência, como compartimentos estanques de um navio — a pane do frete não afunda o pagamento.
- Fallback: a resposta degradada precisa ser pensada com o negócio (mostrar “frete a calcular” é melhor que um erro 500).
- Rate Limiter: protege o seu serviço de clientes abusivos, complementando o disjuntor, que protege você de dependências instáveis.
📜 3. O Padrão Saga (Transações Distribuídas)
3.1 Por que Não Usar uma Transação ACID Global?
Com um banco por serviço (Database-per-Service), não existe um único BEGIN ... COMMIT. O protocolo 2PC (Two-Phase Commit) até resolve, mas bloqueia recursos enquanto espera todos os participantes, não escala e não é suportado por brokers como o Kafka.
3.2 Transações Locais + Compensações
Uma Saga (Garcia-Molina & Salem, 1987) divide a operação em transações locais. Se o passo k falhar, executam-se as transações compensatórias dos passos k−1 … 1, em ordem inversa:
| Passo | Ação (transação local) | Compensação |
|---|---|---|
| 1 | Criar pedido PENDENTE |
Marcar pedido CANCELADO |
| 2 | Pré-autorizar R$ 240,00 no cartão | Liberar a pré-autorização |
| 3 | Reservar 2 unidades no estoque | Devolver as unidades |
| 4 | Confirmar pedido | — (último passo, não precisa) |
💡 Compensar não é apagar: o pedido cancelado continua existindo, com histórico. Sagas oferecem consistência eventual (propriedades ACD, sem o “I” de isolamento), então telas e relatórios precisam lidar com estados intermediários como
PENDENTE.
3.3 Orquestração vs. Coreografia
flowchart LR
subgraph Orq["Orquestração"]
O["Orquestrador da Saga"] --> P1["Pedidos"]
O --> P2["Pagamentos"]
O --> P3["Estoque"]
end
subgraph Cor["Coreografia"]
C1["Pedidos"] -- "PedidoCriado" --> C2["Pagamentos"]
C2 -- "PagamentoAutorizado" --> C3["Estoque"]
C3 -- "EstoqueEsgotado" --> C2
end
| Critério | Orquestração | Coreografia |
|---|---|---|
| Controle do fluxo | Centralizado no orquestrador | Distribuído em eventos |
| Visibilidade | Fluxo legível em um só lugar | Fluxo espalhado entre serviços |
| Acoplamento | Orquestrador conhece todos | Serviços só conhecem eventos |
| Ferramentas | Temporal, Camunda, AWS Step Functions | Kafka, RabbitMQ, EventBridge |
📦 4. Transactional Outbox e CQRS
4.1 O Problema da Escrita Dupla (Dual Write)
Salvar o pedido no banco e publicar PedidoCriado no Kafka são duas operações em sistemas diferentes. Se o processo cair entre elas, o banco diz uma coisa e o resto do sistema nunca fica sabendo.
4.2 A Solução: Outbox na Mesma Transação
sequenceDiagram
participant S as Serviço de Pedidos
participant DB as Banco (pedidos + outbox)
participant R as Relay / CDC (Debezium)
participant K as Broker (Kafka)
S->>DB: BEGIN, UPDATE pedidos, INSERT outbox, COMMIT
Note over S,DB: dado e evento gravados atomicamente
R->>DB: lê eventos com publicado = 0
R->>K: publica eventos
R->>DB: marca publicado = 1
O relay garante entrega pelo menos uma vez (at-least-once): se cair depois de publicar e antes de marcar, publicará de novo. Por isso os consumidores precisam ser idempotentes (registrando os IDs de eventos já processados).
4.3 CQRS (Command Query Responsibility Segregation)
Proposto por Greg Young a partir do princípio CQS de Bertrand Meyer: separar o modelo de escrita (normalizado, com as regras de negócio) do modelo de leitura (desnormalizado, otimizado para as telas). Os eventos da outbox alimentam as projeções de leitura (Elasticsearch, Redis, tabelas materializadas). Use com parcimônia: CQRS adiciona consistência eventual e infraestrutura extra, e só compensa quando os padrões de leitura e escrita são muito diferentes.
📊 5. Matriz Comparativa Sistemática
| Padrão | Problema que resolve | Custo principal | Ferramentas de mercado |
|---|---|---|---|
| Retry + Backoff + Jitter | Falhas transitórias | Duplicidade se a operação não for idempotente | Tenacity, Polly, Resilience4j |
| Circuit Breaker | Falhas em cascata | Definir fallback e limites adequados | Resilience4j, Polly, Istio |
| Bulkhead | Esgotamento de recursos compartilhados | Mais pools para configurar | Resilience4j, pools dedicados |
| Saga | Transações entre bancos distintos | Consistência eventual e compensações | Temporal, Camunda, Step Functions |
| Transactional Outbox | Escrita dupla banco + broker | Relay/CDC e consumidores idempotentes | Debezium, Kafka Connect |
| CQRS | Leitura e escrita com necessidades opostas | Projeções e atraso de sincronização | Axon, EventStoreDB, MediatR |