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
Engenheiro de Software Full-Stack
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 saber2️⃣ 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
idempotencyKeyvai 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 funcionaO 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:
- Nome do serviço
- Por que usa?
- É crítico ou não?
- Qual é a alternativa?
- Qual é o SLA?
- 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
Falar comigo