O Problema das Dependências Invisíveis
- Provavelmente você não vai atingir os quatro noves 🤷🏾♂️
- Muitas dependências se tornam invisíveis ao longo do tempo, aumentando o risco de falhas
- SLAs de serviços externos reduzem a disponibilidade sem você perceber
- A complexidade inerente à forma como construímos aplicações hoje em dia tornou a alta disponibilidade uma luta principalmente estatística
- Todo sistema tem 100% de uptime até você começar a medir. E pior ainda… Mesmo quando você mede, provavelmente está ignorando metade das coisas que podem derrubar seu serviço.
Devs adoram falar sobre os "quatro noves" (99,99%) de disponibilidade. Os olhos dos clientes brilham, e os stakeholders adoram. O que poucos mencionam é que isso significa apenas 52 minutos e 35 segundos de downtime por ano. Se seu parceiro de pagamentos ficar fora do ar por uma hora, esse limite já foi pro beleléu.
Mas o verdadeiro problema não está no que você pode ver e planejar. Está nas dependências que você nem sabe que existem, o que eu chamo de "dependências invisíveis".
Dev vs Probabilidade… Fight!
Vamos começar pelas dependências que você pode ver.
Se seu serviço depende de três componentes, cada um com 99,9% de uptime, sua disponibilidade máxima teórica não é 99,9%. É 99,9% × 99,9% × 99,9% = 99,7%. Você pode até contestar: "Mas são só três dependências!"
Vamos listar algumas então:
- Seu load balancer
- Seu servidor de aplicação
- Seu banco de dados
- Seu cache com Redis
- Seu serviço de autenticação
- Seu CDN para assets
- Seu provedor de DNS
- Seu serviço de monitoramento
- Seu sistema de logs
- Seu serviço de mensageria
Já são dez. Com 99,9% cada: 99,9%^10 = 99,0%. Perdeu um nove inteiro. E isso assumindo que todas têm três noves, o que é bem otimista para dizer o mínimo.
"Cada dependência multiplica suas chances de falha."
E quanto às dependências invisíveis
O que torna tudo pior é que as dependências que você vê são apenas a ponta do iceberg. Para cada serviço que você conscientemente adiciona, existem dezenas de dependências invisíveis esperando para te lascar.
Dependências invisíveis incluem dependências transitivas (dependências das suas dependências) e outras dependências não-óbvias como CDNs de terceiros, serviços de DNS compartilhados, ou bibliotecas que fazem chamadas externas silenciosamente.
Considere um caso real que vivenciei quando trabalhei com e-commerce há alguns anos atrás: usuários não conseguiam clicar no botão "Finalizar Compra" às 3 da manhã de uma terça-feira. Tudo isso por conta de uma indisponibilidade de um CDN que hospedava ícones usados por uma biblioteca JavaScript da interface de pagamentos.
Ninguém sabia que dependíamos daquele CDN. Não estava em nenhum diagrama de arquitetura. Não aparecia em nenhum runbook. Era invisível até o momento em que se tornou crítica.
A timeline do incidente
- 14:00 - Time de frontend atualiza uma biblioteca de componentes para corrigir um bug visual na página de pagamento
- 14:15 - Deploy passa lindo em todos os testes, vai para produção
- 14:30 - Tudo certo, teste em produção para garantir, métricas normais, nenhum alerta
- 14:31 - A nova biblioteca introduz uma dependência silenciosa: ícones carregados de um CDN terceirizado
- 02:45 - CDN terceirizada entra em janela de manutenção programada (fuso horário diferente) e tem problema de certificado SSL
- 02:46 - Ícones da interface não carregam, botão "Finalizar Compra" fica visualmente quebrado
- 02:55 - Usuários não conseguem completar checkout, vendas param
- 03:15 - Alerta de on-call acorda a gente
- 05:30 - Após 2 horas de investigação, alguém descobre a dependência invisível
O pior é que dependências não nascem invisíveis. A entropia vence e faz com que elas se tornam invisíveis gradualmente, a medida que caem no esquecimento.
Não é possível vencer completamente a entropia, mas podemos reduzi-la. O paradoxo é real: para monitorar todas as suas dependências, você precisa primeiro saber que elas existem. Mas o objetivo não é conhecimento perfeito, é reduzir nossa cegueira gradualmente.
Investimos milhões em ferramentas de observabilidade que nos mostram em detalhes impressionantes o comportamento de dependências que já conhecemos. Enquanto isso, muitas dependências que podem derrubar o sistema permanecem no nosso ponto cego porque não sabemos que devemos procurá-las.
É como ter o melhor sistema de radar do mundo, apontado na direção errada.
Como detectar essas dependências?
A gente precisa procurar alguns sintomas:
- Aumento de latência "do nada": Seu serviço às vezes demora 5x mais para responder, mas nenhuma métrica interna explica o porquê.
- Falhas correlacionadas: Dois serviços "independentes" sempre caem juntos. Há uma dependência compartilhada que ninguém mapeou.
- Degradação gradual: Performance piora X% por semana. Alguma dependência está acumulando estado ou vazando recursos.
- Erros sazonais: Falhas que acontecem sempre no mesmo horário/dia. Dependência de algum processo batch ou rotina de manutenção.
Como sobreviver no mundo real?
Depois de anos apanhando de dependências invisíveis, desenvolvi algumas estratégias de sobrevivência. Não são perfeitas, mas reduzem a dor.
1. Assuma que tudo vai falhar eventualmente
"Design for failure" não é pessimismo, é simplesmente ser realista. Se uma dependência pode falhar, ela vai falhar. A questão não é "se", mas "quando" e "como seu sistema vai reagir".
suspend fun fetchUserPreferences(userId: String): UserPreferences {
return try {
// Tenta buscar do serviço principal
preferenceService.get(userId)
} catch (e: Exception) {
when (e) {
is TimeoutException, is ConnectException -> {
try {
// Fallback para cache local
localCache.get("prefs:$userId")
} catch (cacheError: Exception) {
// Fallback para padrões
getDefaultPreferences()
}
}
else -> throw e
}
}
}
2. Timeouts agressivos em tudo
A maior causa de falhas em cascata são timeouts mal configurados ou inexistentes. Um serviço lento é ruim. Um serviço que trava esperando resposta é catastrófico.
# Ruim
redis:
timeout: 30s # 30 segundos de thread travada
# Melhor
redis:
connect_timeout: 100ms
read_timeout: 250ms
max_retries: 2
circuit_breaker:
failure_threshold: 5
recovery_timeout: 30s
3. Mapeamento ativo de dependências
Sim, há uma ironia aqui: estou recomendando mais ferramentas para resolver problemas causados por... ter muitas ferramentas. Mas existe diferença entre complexidade necessária e acidental.
Não confie em documentação ou diagramas. Use ferramentas que descobrem dependências automaticamente analisando tráfego real:
- Service mesh com distributed tracing
- Análise de tráfego de rede
- Dependency scanning em build time
- Chaos engineering para revelar dependências ocultas
O objetivo é trocar complexidade acidental (dependências desconhecidas) por complexidade necessária (ferramentas de observabilidade).
4. Degradação graciosa por padrão
Precisamos que nossas aplicações lidem graciosamente com a parte invisível do iceberg. Quando dependências submersas falham, o sistema deve continuar funcionando sem que os usuários percebam que algo deu errado embaixo d'água.
Seu serviço deve ter níveis de funcionalidade, não ser binário (funciona/não funciona). Quando dependências falham, degrade funcionalidades não-essenciais primeiro.
class RecommendationService(
private val socialService: SocialService,
private val browsingService: BrowsingService,
private val locationService: LocationService
) {
suspend fun getRecommendations(userId: String): Recommendations = coroutineScope {
// Busca dados essenciais
val essentials = getEssentialData(userId)
// Tenta enriquecer em paralelo, mas não bloqueia se falhar
val enrichments = listOf(
async { runCatching { getSocialSignals(userId) } },
async { runCatching { getBrowsingHistory(userId) } },
async { runCatching { getLocationData(userId) } }
).map { deferred ->
withTimeoutOrNull(500) { deferred.await() }
}.mapNotNull { result ->
result?.getOrNull()
}
// Usa o que conseguiu, ignora o que falhou
computeRecommendations(essentials, enrichments)
}
}
5. Feature flags como circuit breaker de negócio
A estratégia mais subestimada: transformar dependências obrigatórias em opcionais através de feature flags. Quando a API de câmbio do nosso exemplo falha, você pode instantaneamente desabilitar conversão de moeda sem deploy.
class PricingService(
private val currencyService: CurrencyService,
private val featureFlags: FeatureFlags
) {
suspend fun getPrice(productId: String, userCurrency: String): Price {
val basePrice = getBasePriceFromDB(productId)
return if (featureFlags.isEnabled("currency_conversion")) {
try {
val rate = currencyService.getExchangeRate("USD", userCurrency)
basePrice.convert(rate, userCurrency)
} catch (e: Exception) {
// Fallback para moeda padrão quando conversão falha
basePrice.asDefaultCurrency()
}
} else {
// Feature desabilitada = zero dependência da API externa
basePrice.asDefaultCurrency()
}
}
}
Feature flags permitem que você tenha um "botão de emergência" para qualquer funcionalidade problemática. API de recomendações lenta? Desabilite e mostre produtos populares. Serviço de avaliações instável? Esconda as reviews temporariamente.
O melhor: você transforma dependências invisíveis em decisões de produto conscientes.
No nosso exemplo do endpoint de preços, para fins didáticos, se considerarmos que uma feature flag permite desabilitar completamente a dependência da API de câmbio, teríamos apenas as dependências internas (RDS + microserviço), resultando em 99,85% de disponibilidade - uma redução de 2,4 para 0,5 dias de possível indisponibilidade por ano.
É claro que na prática, se usássemos cache para armazenar as taxas, teríamos outra dependência (Redis, DynamoDB, etc.). Mas o conceito é poderoso: um "botão de emergência" que transforma uma dependência obrigatória em opcional.
Mas e os quatro noves?
Voltando aos famosos 99,99%: na prática, é quase impossível alcançar com arquiteturas modernas distribuídas. Não porque somos incompetentes (e a maioria é, infelizmente), mas porque a matemática está contra nós.
Veja um endpoint real "simples" de um e-commerce (GET /api/products/{id}/price):
- RDS PostgreSQL (99,95%)
- Microserviço interno de pricing (99,9%)
- API externa de câmbio (99,5% - para conversão de moeda)
Disponibilidade combinada do endpoint: 99,35%. Isso são 2,4 dias que este endpoint específico poderá ficar indisponível por ano. Bem longe dos 52 minutos prometidos.
Mas se implementarmos cache com fallback para taxas de câmbio (usando última taxa conhecida quando a API falha), conseguimos elevar a disponibilidade da consulta de câmbio para 99,9%. Nova disponibilidade: 99,75% - reduzindo para 0,9 dias de possível indisponibilidade.
E isso assumindo que você está usando o RDS corretamente, com multi-AZ e todas as melhores práticas. Provavelmente não conta erros de configuração, deploys mal feitos, ou aquele desenvolvedor que esqueceu de configurar timeout no cliente HTTP.
A solução? Aceitar a realidade e projetar adequadamente:
- Redundância ativa: Não apenas backups, mas múltiplos caminhos ativos
- Isolamento de falhas: Blast radius limitado quando algo falha
- Cache agressivo: Melhor servir dados levemente desatualizados que nada
- Monitoramento de negócio: O cliente consegue comprar?
Cada vez que adicionamos uma nova ferramenta moderna, uma nova biblioteca, um novo serviço, estamos apostando disponibilidade em troca de funcionalidade ou produtividade. Às vezes vale a pena, mas frequentemente não vale.
Antes: monolito Kotlin/Spring Boot e PostgreSQL
- 2 pontos de falha
- Debug simples
- Deploy atômico
Depois: microserviços com Kubernetes
- Muitos pontos de falha (facilmente 20-30+)
- Debug requer expertise em sistemas distribuídos
- Deploy pode deixar sistema em estado inconsistente
Não estou defendendo voltar ao passado. Mas precisamos ser honestos sobre os trade-offs. Complexidade tem custo, e esse custo geralmente é pago em disponibilidade.
Se não tem jeito, então se joga
O segredo não é eliminar todas as dependências invisíveis. Isso é impossível. O real segredo está em construir sistemas que sobrevivam a elas. Que degradem graciosamente. Que falhem rápido e se recuperem mais rápido ainda. Que assumam que o impossível vai acontecer, porque vai.
Porque no mundo real, a diferença entre três noves e quatro noves não está na perfeição técnica. Está em entender que o que você vê é apenas a ponta do iceberg. A massa submersa de dependências invisíveis é sempre maior do que imaginamos.
Inscreva-se na newsletter para receber links, insights e análises sobre engenharia de software, arquitetura e liderança técnica diretamente no seu e-mail.