Integrações que Não Falham: Retries e Idempotência
Integrações que Não Falham: Retries e Idempotência
Integrações são como promessas: fáceis de fazer, difíceis de manter. 70% dos problemas em produção envolvem integrações externas. A diferença entre um sistema robusto e um caos está em como você lida com falhas.
O Problema das Integrações
// ❌ Código ingênuo que vai falhar
async function createUserInCRM(userData: User) {
try {
await crmApi.create(userData);
await emailService.sendWelcome(userData.email);
} catch (error) {
// Perdemos o usuário no CRM e não enviamos email
console.error("Failed to create user in CRM", error);
throw error;
}
}Padrão 1: Retry com Backoff Exponencial
Nunca confie que uma requisição vai funcionar na primeira tentativa.
class RetryableHttpClient {
async request<T>(
operation: () => Promise<T>,
options: {
maxRetries?: number;
baseDelay?: number;
maxDelay?: number;
} = {}
): Promise<T> {let lastError: Error;
for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error as Error;
if (attempt === maxRetries) {
throw new Error(
Operation failed after ${maxRetries} retries. Last error: ${lastError.message}
);
}
const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay); await this.sleep(delay + Math.random() * 1000); // Jitter } }
throw lastError!; }
private sleep(ms: number): Promise
// Uso prático
const client = new RetryableHttpClient();
await client.request(() => crmApi.create(userData), {
maxRetries: 5,
baseDelay: 2000
});
`
Padrão 2: Idempotência
Idempotência significa: executar a mesma operação múltiplas vezes produz o mesmo resultado que executar uma vez.
// ❌ Não idempotente
async function processPayment(paymentData: Payment) {
const payment = await paymentRepository.create(paymentData);
await paymentGateway.charge(payment.amount);
return payment;// ✅ Idempotente com idempotency key async function processPayment(paymentData: Payment & { idempotencyKey: string }) { // Verifica se já processou const existing = await paymentRepository.findByIdempotencyKey(paymentData.idempotencyKey); if (existing) { return existing; // Retorna resultado anterior }
// Processa com transação return await db.transaction(async (tx) => { const payment = await tx.payment.create({ data: { ...paymentData, idempotencyKey: paymentData.idempotencyKey, status: 'processing' } });
try { await paymentGateway.charge(payment.amount);
return await tx.payment.update({
where: { id: payment.id },
data: { status: 'completed' }
});
} catch (error) {
await tx.payment.update({
where: { id: payment.id },
data: { status: 'failed', error: error.message }
});
throw error;
}
});
}
`
Padrão 3: Circuit Breaker
Evite cascata de falhas quando serviços externos estão indisponíveis.
class CircuitBreaker {
private failures = 0;
private lastFailureTime = 0;constructor( private options: { failureThreshold: number; recoveryTimeout: number; monitoringPeriod: number; } ) {}
async execute
try { const result = await operation(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } }
private onSuccess() { this.failures = 0; this.state = 'CLOSED'; }
private onFailure() { this.failures++; this.lastFailureTime = Date.now();
if (this.failures >= this.options.failureThreshold) { this.state = 'OPEN'; } } }
// Configuração
const circuitBreaker = new CircuitBreaker({
failureThreshold: 5,
recoveryTimeout: 60000, // 1 minuto
monitoringPeriod: 30000 // 30 segundos
});
`
Padrão 4: Queue para Processamento Assíncrono
Webhooks devem ser processados assincronamente para evitar timeouts.
// Webhook endpoint - apenas enfileira
app.post('/webhooks/stripe', async (req, res) => {// Validação básica if (!id || !type) { return res.status(400).json({ error: 'Invalid webhook' }); }
// Enfileira para processamento await webhookQueue.add('process-stripe-webhook', { id, type, data, receivedAt: new Date().toISOString() });
res.status(200).json({ received: true }); });
// Worker de processamento import { Worker } from 'bullmq';
const webhookWorker = new Worker('webhook-queue', async (job) => { const { id, type, data } = job.data;
try {
await circuitBreaker.execute(async () => {
switch (type) {
case 'payment_intent.succeeded':
await handlePaymentSucceeded(data);
break;
case 'customer.subscription.created':
await handleSubscriptionCreated(data);
break;
default:
console.log(Unhandled webhook type: ${type});
}
});
console.log(Webhook processed successfully: ${id});
} catch (error) {
console.error(Failed to process webhook ${id}:, error);
throw error; // BullMQ vai fazer retry automaticamente
}
});
`
Padrão 5: Dead Letter Queue
Para falhas que não podem ser recuperadas automaticamente.
const deadLetterQueue = new Queue('webhook-dlq');
// No worker, quando falha definitivamente webhookWorker.on('failed', async (job, error) => { if (job.attemptsMade >= job.opts.attempts) { await deadLetterQueue.add('failed-webhook', { originalJob: job.data, error: error.message, failedAt: new Date().toISOString(), attempts: job.attemptsMade });
console.log(Moved to DLQ: ${job.id});
}
});
// Dashboard para monitorar DLQ
async function getFailedWebhooks() {
return await deadLetterQueue.getJobs(['waiting', 'active']);
}
`
Conclusão
Integrações robustas não são sobre evitar falhas, mas sobre projetar para falhas. Com retries, idempotência, circuit breakers e processamento assíncrono, você cria sistemas que sobrevivem ao caos externo.
Lembre-se: a rede não é confiável. APIs externas falham. Seu sistema precisa estar preparado.
---
Precisa de ajuda para implementar integrações robustas? Fale conosco sobre uma consultoria de integrações.