Pular para o conteúdoPular para navegaçãoPular para rodapé
Voltar para o blog
integracoeswebhooksapiresilienciabackend

Integrações que Não Falham: Retries e Idempotência

Brizolla Studio
10 de fevereiro de 2024
12 minutos de leitura
integracoeswebhooksapiresilienciabackend

Integrações que Não Falham: Retries e Idempotência

Brizolla Studio
10 de fevereiro de 2024
12 minutos de leitura

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 { return new Promise(resolve => setTimeout(resolve, ms)); } }

// 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(operation: () => Promise): Promise { if (this.state === 'OPEN') { if (Date.now() - this.lastFailureTime > this.options.recoveryTimeout) { this.state = 'HALF_OPEN'; } else { throw new Error('Circuit breaker is OPEN'); } }

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.

Precisa de ajuda para implementar estas soluções?

Nossos artigos são baseados em experiência real em projetos. Vamos conversar sobre como aplicar estes conceitos no seu negócio.

Falar no WhatsApp