Lição 5: Tratamento de erros e estratégias de retentativa
Objetivos de aprendizado:
- Diferenciar erros transitórios de erros permanentes
- Dominar estratégias de retentativa e algoritmos de backoff
- Aprender a projetar ações compensatórias e mecanismos de rollback
Pré-requisitos: Lição 4: Gerenciamento de estado e passagem de contexto | Próxima: Lição 6 >>
Erros são a norma em fluxos de trabalho
Seu fluxo de trabalho roda perfeitamente dez vezes. Na décima primeira, no passo 8, a API retorna um 503. O fluxo de trabalho quebra.
Então você adiciona um try-catch, captura o erro, registra em log e segue em frente. Na décima segunda execução, a conexão com o banco de dados expira. O fluxo de trabalho continua, mas a escrita falhou, e agora seus dados estão inconsistentes.
Tratamento de erros não é tão simples quanto “adicione um try-catch”.
Em um fluxo de trabalho, o tratamento de erros precisa responder a três perguntas:1
- Este erro é temporário ou permanente? (instabilidade de rede vs. permissões ausentes)
- Você deve repetir, pular ou abortar? (uma retentativa pode resolver vs. uma retentativa piora as coisas)
- Se abortar, como você limpa os passos que já terminaram? (reverter o banco de dados vs. enviar um aviso de cancelamento)
Se o passo que falhou é opcional (digamos, enviar uma notificação), pule-o e siga em frente. Deixar que uma falha não crítica não derrube a execução inteira se chama degradação graciosa. Mas se o passo que falhou é crítico, pulá-lo deixa um estado inconsistente, então você deveria abortar.
Sem respostas, seu fluxo de trabalho fica frágil demais (um errinho o derruba) ou perigoso demais (ele ignora erros e continua rodando, deixando estado inconsistente para trás).2
Classificando erros: transitórios vs. permanentes
Erros transitórios são temporários; uma retentativa pode ter sucesso.3
Erros transitórios comuns:
- Timeouts de rede
- Serviço temporariamente indisponível (503 Service Unavailable)
- Limitação de taxa (429 Too Many Requests)
- Pool de conexões do banco de dados esgotado
- Conflitos temporários de lock
O que eles têm em comum: costumam vir de disputa por recursos, oscilação de rede ou sobrecarga temporária, e esperar um instante antes de repetir tende a funcionar.
Erros permanentes não vão ter sucesso em uma retentativa; eles precisam de uma correção de código ou de configuração.2
Erros permanentes comuns:
- Permissões ausentes (401 Unauthorized, 403 Forbidden)
- Recurso não encontrado (404 Not Found)
- Entrada malformada (400 Bad Request)
- Erros de lógica de negócio (saldo insuficiente, estoque zerado)
- Bugs de código (ponteiro nulo, divisão por zero)
O que eles têm em comum: vêm de configuração incorreta, bugs de código ou regras de negócio violadas, e repetir só desperdiça recursos.
Como diferenciá-los:
Estratégias de retentativa
Para erros transitórios, repetir é seu primeiro movimento. Mas repetir tem mais nuance do que parece.3
Estratégia 1: retentativa com atraso fixo
O problema: se os erros vêm de um serviço sobrecarregado, todos os clientes repetindo ao mesmo tempo pioram a sobrecarga (o efeito thundering herd).
Estratégia 2: backoff exponencial
O benefício: cada retentativa dobra o intervalo, dando ao serviço mais tempo para se recuperar em vez de martelá-lo.3
Estratégia 3: backoff exponencial + jitter
O benefício: o jitter impede que vários clientes repitam exatamente no mesmo instante, distribuindo a carga.3
Esta é a estratégia recomendada para produção.4
Estratégia 4: retentativa seletiva
A ideia central: repita apenas erros transitórios. Lance erros permanentes imediatamente, para não queimar ciclos com retentativas inúteis.5
O padrão circuit breaker
O problema: se um serviço fica falhando (digamos, um banco de dados que caiu) e cada requisição repete três vezes, você queima recursos à toa e arrasta o fluxo de trabalho inteiro para baixo. E se esse serviço é dependência de outros serviços, a falha se propaga pela cadeia e vira uma falha em cascata.
Um circuit breaker: quando a taxa de erro cruza um limiar, ele para temporariamente de chamar o serviço que está falhando e falha rápido em vez disso, evitando desperdício de recursos.1
Três estados
Closed: funcionando normalmente. As requisições passam, e o breaker acompanha a taxa de erro.
Open: o serviço é considerado indisponível. As requisições falham rápido, sem chamá-lo.
Half-open: depois de um timeout, algumas requisições de teste passam. Se tiverem sucesso, o breaker volta para closed; caso contrário, ele continua open.
Implementação
Quando usar: chamadas a serviços externos, bancos de dados, sistemas de arquivos e outras dependências que podem falhar em massa.1
Ações compensatórias e rollback
O problema: o fluxo de trabalho fez três escritas (gravar no banco de dados, enviar um e-mail, atualizar o cache), e o passo 4 falhou. Como você desfaz as três primeiras?2
Padrão 1: operações transacionais
Quando serve: todas as operações vivem no mesmo banco de dados, e ele suporta transações.
O limite: não dá para atravessar sistemas (digamos, banco de dados + sistema de arquivos + chamada de API).
Padrão 2: ações compensatórias (o padrão Saga)
A ideia: defina uma ação compensatória para cada operação e, em caso de falha, execute as compensações para desfazer os passos que já terminaram.4
Pontos centrais:
- Cada passo tem um
forward (a ação) e um compensate (o desfazer).
- Em caso de falha, execute as compensações dos passos concluídos na ordem inversa.
- Uma compensação pode ela mesma falhar; registre em log e sinalize para uma pessoa.4
Padrão 3: design idempotente
Idempotente: executar N vezes tem o mesmo efeito que executar uma vez.2
O benefício: se um passo roda duas vezes por causa de um soluço de rede (a primeira tentativa expirou, mas na verdade teve sucesso), a idempotência garante que não haja efeitos colaterais duplicados.2
Camadas de tratamento de erros
Um bom fluxo de trabalho trata erros em três camadas:
Camada 1: a operação individual
Camada 2: o passo do fluxo de trabalho
Esta camada escreve cada falha em workflowState.errors com o nome do passo, a mensagem de erro e o timestamp. Esse é o seu log de erros e, quando estiver depurando, você se apoia nesse registro em vez da sua memória.
Camada 3: o fluxo de trabalho inteiro
Três camadas de proteção: retentativa na camada da operação, registro na camada do passo, recuperação e notificação na camada do fluxo de trabalho.
Próxima: Lição 6: Fluxos de trabalho do mundo real na prática — Junte tudo para construir três fluxos de trabalho de nível de produção: refatoração de código, geração de documentação e automação de testes