Tratamento de Erros e Retentativas
Um workflow robusto deve ser capaz de lidar com falhas transitórias, como problemas de rede ou indisponibilidade temporária de uma API. A engine Refluxo fornece uma RetryPolicy (Política de Retentativa) poderosa e declarativa que você pode anexar a qualquer NodeDefinition para controlar seu comportamento em caso de falha.
A Política de Retentativa
A RetryPolicy é um objeto que especifica quantas vezes um nó com falha deve ser repetido e quanto tempo esperar entre as tentativas.
export interface RetryPolicy {
maxAttempts: string | number;
interval: string | number;
backoff: "fixed" | "exponential";
}maxAttempts: O número máximo de vezes para tentar a execução (incluindo a primeira tentativa).interval: O tempo de espera base em milissegundos.backoff: A estratégia para aumentar o tempo de espera em retentativas subsequentes."fixed": O tempo de espera é sempre igual aointerval."exponential": O tempo de espera dobra a cada tentativa (interval * 2 ^ (tentativa - 1)).
Exemplo: Um Nó com Política de Retentativa
Vamos adicionar uma RetryPolicy a um nó que pode falhar.
const fallibleNode: NodeDefinition = {
input: { type: "object" },
output: { type: "object" },
retryPolicy: {
maxAttempts: 3,
interval: 1000, // 1 segundo
backoff: "exponential",
},
executor: async (data) => {
// Simula uma chamada de API que falha
if (Math.random() > 0.3) { // 70% de chance de falha
throw new Error("API está indisponível no momento");
}
return { data: { success: true } };
},
};O Ciclo de Tratamento de Erro
Quando o executor do fallibleNode lança um erro:
- O
WorkflowEnginecaptura o erro. - Ele verifica se a
NodeDefinitionpossui umaretryPolicy. - Ele determina se o número de
maxAttemptsfoi excedido. - Se uma retentativa for possível:
- A engine calcula o
delay(atraso) com base na estratégia debackoff. - Ela retorna um
Snapshotcomstatus: "error". - O
Snapshotinclui um objetoretryStatecomnodeId,attempts, enextRetryAt(um timestamp indicando quando a próxima tentativa deve ocorrer).
- A engine calcula o
- Se não houver mais retentativas (ou não houver política):
- A engine retorna um
Snapshotcomstatus: "failed". - O erro é registrado no
Contextpara aquele nó.
- A engine retorna um
O Papel do Executor (Runner)
A engine Refluxo não espera. Ela é stateless. Ela simplesmente retorna um snapshot indicando que uma retentativa é necessária e quando deve acontecer.
É responsabilidade do ambiente de execução (o "runner") respeitar o timestamp nextRetryAt.
- Em um processo de longa duração, você pode usar
setTimeout. - Em um ambiente serverless como o AWS Lambda, você pode usar uma fila de mensagens (como SQS) com uma propriedade
DelaySeconds. - Em um runtime como o Trigger.dev, você pode usar
await io.sleep(), que lida com esse agendamento para você.
Quando o atraso passar, o runner simplesmente chama engine.execute({ snapshot }) com o snapshot que tem o status "error". A engine irá então reativá-lo automaticamente и tentar novamente o nó que falhou.
Tratamento de Erros Avançado
Retentativas Condicionais
Nem todos os erros são iguais. Um erro 401 Unauthorized de uma API é uma falha permanente que não deve ser repetida, enquanto um 503 Service Unavailable é um erro transitório e um candidato perfeito para uma retentativa.
A RetryPolicy da engine só é acionada quando um executor lança um erro. Você pode usar isso para criar uma lógica de tratamento de erros sofisticada. Em vez de lançar um erro, você pode capturá-lo e redirecionar o workflow por um caminho diferente usando um nextHandle.
O Padrão:
- Para erros que permitem retentativa (ex: códigos de status 5xx, timeouts de rede), lance um erro do seu executor para acionar a
RetryPolicy. - Para erros lógicos e não recuperáveis (ex: códigos de status 4xx), capture o erro e retorne um
nextHandleespecífico para ramificar o workflow.
Exemplo: Lidando com Status HTTP
Aqui está um nó de requisição HTTP que tenta novamente em caso de erros de servidor, mas segue um caminho separado para erros de autenticação.
const httpRequestNode: NodeDefinition = {
// ... schemas de input, output ...
retryPolicy: {
maxAttempts: 3,
backoff: "exponential",
interval: 2000,
},
executor: async (data) => {
try {
const response = await fetch(data.url);
if (response.status === 401 || response.status === 403) {
// Não lance um erro, este é um caminho lógico
return {
data: { error: "Falha na autenticação" },
nextHandle: "auth-error",
};
}
if (!response.ok) {
// Para outros erros (como 5xx), lance para acionar uma retentativa
throw new Error(`Requisição falhou com status ${response.status}`);
}
return { data: await response.json() };
} catch (error) {
// Relance erros de rede ou erros 5xx para acionar a política de retentativa
throw error;
}
},
};Seu workflow então teria uma aresta separada para o handle auth-error:
const workflow: WorkflowDefinition = {
nodes: [
{ id: "request", type: "http-request-node", data: { ... } },
{ id: "handle_success", type: "process-data", data: { ... } },
{ id: "handle_auth_error", type: "notify-admin", data: { ... } },
],
edges: [
// Caminho de sucesso (handle padrão)
{ source: "request", target: "handle_success" },
// Caminho de erro de autenticação
{ source: "request", target: "handle_auth_error", sourceHandle: "auth-error" },
],
};Retentativas Configuráveis pelo Usuário
Você pode tornar esse comportamento ainda mais flexível, permitindo que o usuário da sua plataforma defina o que constitui um erro que permite retentativa. Isso pode ser feito passando a configuração como parte dos data do nó.
// O schema de input agora inclui um campo para códigos de status que permitem retentativa
const inputSchema = object({
url: string([url()]),
retriableCodes: optional(array(number()), [500, 502, 503, 504]),
});
// Lógica do Executor
const executor = async (data) => {
const { url, retriableCodes } = data;
const response = await fetch(url);
if (!response.ok) {
// Verifica se o usuário quer tentar novamente para este código de status
if (retriableCodes.includes(response.status)) {
// Lance para acionar a retentativa
throw new Error(`Erro recuperável: ${response.status}`);
} else {
// Caso contrário, é um caminho de falha lógica
return {
data: { status: response.status, body: await response.text() },
nextHandle: 'fail'
};
}
}
//...
}Isso capacita o usuário final da sua plataforma a customizar a resiliência do workflow sem precisar alterar o código principal do nó.