Renan Montanari Blog
Voltar ao blog
ArquiteturaResiliência

Dependências Externas: Quando Um Serviço de Fora Derruba Tudo

Pagamento, e-mail, WhatsApp, login, nota fiscal: seu sistema depende de mais serviços do que você imagina. Como mapear essas dependências e garantir que a queda de uma delas não leve o produto inteiro junto.

Renan Montanari

Renan Montanari

Engenheiro de Software Full-Stack

11 min de leitura

Introdução: A Lista Mental Incômoda

Faz uma lista mental rápido.

O que seu sistema usa por fora, entre serviços, APIs e plataformas que você não controla:

  • Pagamento (Stripe, PagSeguro, maquininha)
  • Envio de e-mail (SendGrid, AWS SES, seu SMTP)
  • WhatsApp (Twilio, WhatsApp Business API)
  • Mapa (Google Maps, Mapbox)
  • Login (Google, GitHub, Facebook)
  • Armazenamento (AWS S3, Cloudinary)
  • Emissão de nota fiscal
  • SMS (Twilio, AWS SNS)
  • CRM (se integrado)
  • Analytics (Mixpanel, Amplitude, Google Analytics)

Conte quantas são.

Sete. Dez. Quinze? E você lembra de ter escolhido conscientemente duas, no máximo.

Qualquer uma delas pode fazer seu produto parar numa terça à tarde, sem aviso nenhum.

Por Que Isso Não é um Problema de Arquitetura

Primeiro, deixando claro: isso não é defeito de arquitetura. É a forma correta e moderna de construir.

Ninguém escreve do zero um sistema de pagamento. Ninguém constrói o próprio serviço de e-mail em 2026. Seria:

  • Caro demais
  • Lento demais
  • Inseguro demais
  • Desnecessário

As grandes empresas fazem porque precisam. Você não precisa.

O ponto não é "não usar serviços de fora".

O ponto é: dependência boa é dependência conhecida.

O Que Costuma Acontecer Com Quem Nunca Mapeou Suas Dependências

Cenário 1: O Serviço Fica Instável

O gateway de pagamento fica fora do ar. Só 30 minutos. Pequeno.

Mas seu sistema inteiro trava. Porque:

// ❌ Amador: sem timeout, sem fallback
app.post("/checkout", async (req, res) => {
  const payment = await stripe.charges.create({
    amount: req.body.amount,
    currency: "brl",
    // ... sem timeout, sem retry
  });

  // Se o gateway não responder, essa linha demora a retornar
  // A requisição fica pendurada
  // O usuário fica esperando
  // Conexões e workers ficam presos aguardando resposta
  // O servidor fica sem recursos
  // Começa a rejeitar outros usuários
  // Tudo cai

  res.json({ success: true });
});

O cliente sente: tudo congela. Não consegue pagar. Não consegue acessar nada, nem as telas que não têm nada a ver com pagamento.

Cenário 2: O Fornecedor Muda a Integração

O fornecedor avisa: "Vamos descontinuar a versão antiga da API. Migrem até dezembro."

O e-mail chega em fevereiro. Vai para a caixa de um ex-funcionário. Ou para o spam. Ninguém lê.

Dezembro chega. O sistema quebra. Você descobre pelo chat de um cliente.

Cenário 3: O Preço Muda

Você calculou a margem com a taxa atual do gateway: um percentual mais um valor fixo por transação.

Um dia, a tabela muda. Alguns décimos a mais no percentual, alguns centavos a mais no fixo.

Em ticket baixo e volume alto, isso é suficiente para a conta por cliente deixar de fechar.

Cenário 4: O Produto É Descontinuado

Um serviço do Google é encerrado. Uma rede social muda as permissões de login. Um provedor de SMS deixa de atender determinada região.

Aí não é ajuste. É substituição completa, com prazo que você não escolheu.

Os Três Pilares da Resiliência

Tratar isso não exige plano caro. Exige decisões antes de entrar em produção.

Três coisas:

1️⃣ Conhecimento: De Quem Seu Sistema Depende

// Mapeie explicitamente
const externalDependencies = {
  payments: {
    service: "Stripe",
    critical: true, // sem isso, o negócio para
    fallback: "PagSeguro",
    owner: "time-financeiro", // quem responde quando cai
    sla: "conferir no contrato",
    statusPage: "https://status.stripe.com",
  },
  email: {
    service: "SendGrid",
    critical: false, // usuário não sente imediatamente
    fallback: "AWS SES",
    owner: "time-produto",
  },
  whatsapp: {
    service: "Twilio",
    critical: true, // o negócio depende das notificações
    fallback: "SMS",
    owner: "time-produto",
  },
  storage: {
    service: "AWS S3",
    critical: true, // sem arquivos, o sistema não funciona
    fallback: "Google Cloud Storage",
    owner: "time-infra",
  },
};

// E, mais importante: documente isso
// Wiki, README ou planilha
// Todo mundo no time precisa saber

2️⃣ Resiliência: O Que Acontece Quando Cada Uma Falha

Essa é a que mais economiza dinheiro.

Sistema bem construído perde uma função quando um serviço de fora cai.

Sistema mal construído perde tudo, porque ninguém decidiu o que fazer quando a resposta não chega.

Padrão: Timeout + Retry + Fallback

// ✅ Profissional: com timeout, retry e fallback

interface PaymentGateway {
  charge(input: {
    amount: number;
    idempotencyKey: string;
  }): Promise<{ id: string; success: boolean }>;
}

class ResilientPaymentService {
  constructor(
    private primary: PaymentGateway,
    private fallback?: PaymentGateway,
  ) {}

  async charge(orderId: string, amount: number, maxRetries = 3) {
    // A mesma chave em todas as tentativas: se uma tentativa "falhou"
    // por timeout mas o gateway processou, ele não cobra de novo
    const idempotencyKey = `order-${orderId}`;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await this.withTimeout(
          this.primary.charge({ amount, idempotencyKey }),
          5000, // 5s
        );
      } catch (error) {
        console.warn(`Primary gateway attempt ${attempt} failed`, error);

        if (attempt < maxRetries) {
          // Espera progressiva antes de tentar de novo: 1s, 2s
          await this.delay(2 ** (attempt - 1) * 1000);
        }
      }
    }

    // Primary falhou: tenta o fallback
    if (this.fallback) {
      // Antes de cobrar em outro gateway, confirme que o primary
      // realmente não processou, senão o cliente paga duas vezes
      if (!(await this.primaryChargeExists(idempotencyKey))) {
        try {
          return await this.withTimeout(
            this.fallback.charge({ amount, idempotencyKey }),
            5000,
          );
        } catch (error) {
          console.error("Fallback also failed", error);
        }
      }
    }

    // Tudo falhou: registra para tentar depois
    await this.queueForRetry({ orderId, amount, idempotencyKey });

    throw new PaymentPendingError(
      "Pagamento pendente de confirmação. Avisaremos assim que for processado.",
    );
  }

  private withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
    return Promise.race([
      promise,
      new Promise<never>((_, reject) =>
        setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms),
      ),
    ]);
  }

  private delay(ms: number) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  private async queueForRetry(payment: object) {
    // Salva no banco; um job tenta de novo periodicamente
    await db.pendingPayments.create(payment);
  }
}

Dois cuidados que separam esse código de um problema novo:

  • Retry em pagamento sem idempotência é cobrança em dobro. Um timeout no seu lado não significa que o gateway não processou. Por isso a mesma idempotencyKey vai em todas as tentativas.
  • O usuário está esperando. Três tentativas de 5s mais as pausas dão quase 20 segundos numa tela de checkout. Em fluxos com gente do outro lado, prefira menos tentativas e uma mensagem clara de "pendente".

Circuit Breaker (Proteção Automática)

Se um serviço está falhando muito, insistir só piora: cada requisição fica presa esperando o timeout. O circuit breaker para de tentar por um tempo e falha rápido.

class CircuitBreaker {
  private failureCount = 0;
  private lastFailureTime = 0;
  private state: "CLOSED" | "OPEN" | "HALF_OPEN" = "CLOSED";

  constructor(
    private name: string,
    private failureThreshold = 5,
    private resetTimeMs = 60_000, // 1 minuto
  ) {}

  async call<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === "OPEN") {
      if (Date.now() - this.lastFailureTime > this.resetTimeMs) {
        // Já esperou o bastante: deixa uma tentativa passar
        this.state = "HALF_OPEN";
      } else {
        // Aberto: falha na hora, sem nem chamar o serviço
        throw new Error(`Circuit breaker OPEN for ${this.name}`);
      }
    }

    try {
      const result = await fn();
      // Funcionou: fecha o circuito e zera a contagem
      this.state = "CLOSED";
      this.failureCount = 0;
      return result;
    } catch (error) {
      this.failureCount++;
      this.lastFailureTime = Date.now();

      if (
        this.state === "HALF_OPEN" ||
        this.failureCount >= this.failureThreshold
      ) {
        this.state = "OPEN";
        console.error(
          `Circuit breaker OPEN for ${this.name}. Retrying in ${this.resetTimeMs}ms`,
        );
      }

      throw error;
    }
  }
}

// Uso
const stripeCircuit = new CircuitBreaker("Stripe", 5, 60_000);

try {
  const charge = await stripeCircuit.call(() =>
    stripe.charges.create({ amount: 1000, currency: "brl" }),
  );
} catch (error) {
  console.log("Stripe indisponível, usando fallback");
  // redireciona para o gateway alternativo
}

3️⃣ Alternativas: Qual Delas Não Tem Substituto

// Identifique quais são críticas e quanto custa cada hora sem elas

const dependencyRisk = {
  stripe: {
    critical: true, // sem pagamento, o negócio para
    alternatives: ["PagSeguro", "maquininha"],
    costOfDowntime: "R$ 1.000/hora", // vendas que não acontecem
  },
  email: {
    critical: false, // e-mail atrasado não é o fim do mundo
    alternatives: ["AWS SES", "SMTP próprio"],
    costOfDowntime: "R$ 100/hora", // atrito e reputação
  },
  whatsapp: {
    critical: true, // notificações são o centro do produto
    alternatives: ["SMS", "push notification"],
    costOfDowntime: "R$ 5.000/hora", // usuário não consegue fazer nada
  },
};

// Para cada crítica, tenha um plano B
// E teste regularmente se ele funciona

O número exato importa menos do que a ordem: ele diz onde vale gastar esforço primeiro.

Implementação Prática: Monitoramento de Dependências

Verificação de Status

// Mede se cada dependência responde, e em quanto tempo
const timed = async (check: () => Promise<unknown>) => {
  const start = Date.now();
  try {
    await check();
    return { status: "UP", responseTime: Date.now() - start };
  } catch (error) {
    return { status: "DOWN", error: (error as Error).message };
  }
};

const dependencyMonitor = {
  checkStripe: () => timed(() => stripe.accounts.retrieve()),

  // Muitos provedores publicam uma página de status com API própria
  checkSendGrid: () =>
    timed(async () => {
      const res = await fetch("https://status.sendgrid.com/api/v2/status.json");
      const body = await res.json();
      if (body.status.indicator !== "none") {
        throw new Error(body.status.description);
      }
    }),

  async checkAll() {
    return {
      timestamp: new Date(),
      services: {
        stripe: await this.checkStripe(),
        sendgrid: await this.checkSendGrid(),
        // ... outros
      },
    };
  },
};

// A cada 5 minutos
setInterval(async () => {
  const status = await dependencyMonitor.checkAll();
  await db.dependencyStatus.create(status);

  // Alerta se algo caiu
  for (const [name, result] of Object.entries(status.services)) {
    if (result.status === "DOWN") {
      await slack.notify(`🚨 ${name} is DOWN`);
    }
  }
}, 5 * 60 * 1000);

Um aviso: health check periódico é o mínimo. O sinal mais confiável é a taxa de erro das chamadas reais, que é exatamente o que falamos no post sobre monitoramento.

Log de Incidentes

// Registre cada falha de dependência externa

interface ExternalDependencyIncident {
  timestamp: Date;
  service: string;
  status: "DOWN" | "DEGRADED" | "RECOVERED";
  duration: number; // em minutos
  impact: string; // "Pagamentos bloqueados", "E-mails atrasados"...
  errorMessage: string;
}

// Quando o gateway cai
await db.incidents.create({
  timestamp: new Date(),
  service: "Stripe",
  status: "DOWN",
  impact: "Clientes não conseguem finalizar a compra",
  errorMessage: "Connection timeout after 5s",
});

// No fim do dia, um resumo
const dailyReport = async () => {
  const incidents = await db.incidents.find({
    timestamp: { $gte: yesterday, $lte: today },
  });

  await slack.notify(
    [
      "📊 Relatório diário de dependências",
      ...incidents.map(
        (i) => `- ${i.service}: ${i.status} (${i.duration}min): ${i.impact}`,
      ),
    ].join("\n"),
  );
};

Depois de alguns meses, esse log responde uma pergunta valiosa: qual fornecedor mais te deixou na mão e se vale a pena trocar.

Checklist: Você Conhece Suas Dependências?

  • Fez a lista de todos os serviços externos que usa?
  • Documentou quais são críticos (sem eles, o negócio para)?
  • Sabe qual é o SLA de cada um?
  • Tem alternativa para os críticos?
  • Seus timeouts são razoáveis (não infinitos)?
  • Implementou retry automático, com idempotência onde envolve dinheiro?
  • Tem circuit breaker para evitar falha em cascata?
  • Monitora o status de cada dependência?
  • O time sabe o que fazer quando uma cai?
  • Já testou o fallback de verdade?

Menos de 7 marcados = você está em risco.

O Exercício de Hoje

Quantos serviços de fora o seu sistema usa?

Quase sempre são mais do que a pessoa imaginava.

Abra uma planilha agora e liste:

  1. Nome do serviço
  2. Por que usa?
  3. É crítico ou não?
  4. Qual é a alternativa?
  5. Qual é o SLA?
  6. Quando foi a última vez que caiu?

Você vai descobrir dependências que nem lembrava que existiam.

O Medo Real (Que Ninguém Comenta)

Quando você começa a mapear dependências, aparece um medo:

"E se eu descobrir que dependemos completamente de um serviço que não tem alternativa?"

Exemplo comum: uma integração profunda com a API do Gmail. Um dia, o Google muda as regras de permissão, e o que funcionou por cinco anos deixa de funcionar.

É por isso que se mapeia hoje, quando nada está quebrado.

Porque quando quebra, é tarde para improviso.

Próximos Passos

Se você deixou mais de 3 itens do checklist em branco:

Você está exposto a uma falha em cascata.

Uma tarde mapeando dependências e configurando timeouts economiza semanas de investigação caótica quando algo quebra.

E evita aquele susto de descobrir que o seu negócio depende inteiramente de um serviço que você não controla.

Quer Aprofundar?

Se você está escalando seu sistema e quer garantir que nenhuma falha de terceiro derrube tudo, posso ajudar com:

  • Auditoria de Dependências: mapeamento, riscos e alternativas
  • Resiliência Implementada: timeouts, retries, circuit breakers e fallbacks
  • Monitoramento Proativo: painel de status e alertas automáticos
  • Plano de Contingência: o que fazer quando cada peça cai

Vamos mapear as dependências do seu sistema →

Gostou? Compartilhe