Configuration

Escopo: transversal. Aplica-se a qualquer linguagem ou stack do projeto.

Configuração é tudo que varia entre ambientes sem mudar o comportamento do código: URLs de dependências, tamanhos de pool, flags de habilitação, chaves de integração. A organização certa permite promover o mesmo artefato de dev para produção sem recompilar. A errada espalha if (env === "prod") pelo código e transforma cada deploy em aposta.

Esta página cobre estrutura e precedência. Secrets (rotina de rotação, armazenamento, escopo) são tratados em security.md. Feature flags, como mecanismo de release gradual, em ci-cd.md.

Conceitos fundamentais

ConceitoO que é
Config (configuração)Valor que varia entre ambientes sem mudar o comportamento do código
Secret (segredo)Valor sensível que expõe credencial ou acesso privilegiado; nunca vai para o repositório
Layering (camadas de configuração)Estratégia de resolver configuração por níveis de especificidade, onde cada camada sobrescreve a anterior
Fail-fast (falhar rápido)Validar toda a configuração no startup e encerrar imediatamente se algo estiver ausente ou inválido
Runtime (tempo de execução)Período em que o processo está rodando, após o startup; configurações dinâmicas são avaliadas aqui

Config vs Secret

Os dois parecem iguais (uma string injetada no startup) mas têm ciclo de vida e superfície de risco distintos:

EixoConfigSecret
SensibilidadePúblico ou interno não críticoExpõe credencial ou acesso privilegiado
VersionamentoPode ir no repositórioNunca no repositório, inclusive histórico
RotaçãoRaro, acompanha releasePeriódica, independente de release
ExemplosAPI_BASE_URL, PAGE_SIZE, LOG_LEVELDATABASE_PASSWORD, JWT_SECRET, STRIPE_API_KEY

A linha divisória: um valor que vira notícia se vazar é secret. Tudo que pode ser inspecionado por qualquer pessoa sem consequência é config.

Misturar os dois no mesmo arquivo força o arquivo inteiro a seguir o regime mais restrito. Config de rotina vira difícil de alterar; secret vaza com facilidade. Separar em duas origens mantém cada um no regime certo.


Precedência em camadas

Configuração resolve-se por camadas, do menos específico ao mais específico. Cada camada sobrescreve a anterior:

1. defaults no código        → última linha de defesa
2. arquivo de config         → valores padrão por ambiente (commitado)
3. variáveis de ambiente     → overrides por host (injetadas no deploy)
4. argumentos de linha de comando / runtime → override pontual
5. secrets manager           → resolvido no startup para chaves sensíveis

A regra que sustenta o modelo: camadas mais externas ao código ganham. Um valor commitado nunca sobrescreve uma variável injetada pelo host. Inverter a ordem quebra a capacidade de promover o mesmo artefato entre ambientes.

O default no código existe para o caso de tudo mais faltar. Um servidor que sobe com PORT=8080 hardcoded e ignora PORT=80 do host tem a precedência invertida.


Layering por ambiente

Um único arquivo com if (env === "prod") cresce desordenado. Múltiplos arquivos (config.dev.json, config.staging.json, config.prod.json) duplicam valores que não mudam. O padrão sustentável é base + overrides:

config/
  base.json         <- valores válidos em todo ambiente
  dev.json          <- apenas o que muda em dev
  staging.json      <- apenas o que muda em staging
  prod.json         <- apenas o que muda em produção

No startup, o sistema lê base.json, aplica o arquivo do ambiente atual por cima e depois as variáveis de ambiente. Valores que não variam ficam em um lugar só. Alterar um default comum não exige tocar três arquivos.

Sinal de arquitetura errada: o arquivo do ambiente tem quase tudo que o base tem, com pequenas mudanças. O base está anêmico; a maioria dos valores deveria estar lá.


Tipagem e contrato

Configuração chega ao código como string ou objeto vindo de arquivo. Consumir esse valor diretamente espalha conversões (parseInt(PORT), value === "true") por toda a aplicação. Quando a chave muda de nome, o compilador não acusa; o bug aparece em produção.

A solução é um objeto tipado, construído uma vez no startup:

AppConfig {
  port: number
  database: {
    host: string
    maxPoolSize: number
  }
  features: {
    allowsMultiTenant: boolean
  }
}

O resto do código recebe AppConfig como dependência, não lê variáveis de ambiente diretamente. Mudanças de nome de chave viram erro de compilação, não surpresa em runtime. Testar um cenário com config específica é construir um AppConfig de teste, sem monkey-patching em process.env.

Em linguagens com forte tipagem (TypeScript, C#, VB.NET), o objeto de config é uma classe ou record com propriedades não-nulas. Em linguagens dinâmicas, validar a forma na carga (schema de JSON (JavaScript Object Notation, Notação de Objetos JavaScript), dataclass com validação) cobre o mesmo papel.


Validação fail-fast no startup

Um serviço que sobe com DATABASE_URL ausente e só descobre no primeiro request trava o primeiro usuário. Um que sobe com MAX_POOL_SIZE="abc" só percebe quando o parser tenta converter, minutos depois.

A regra é: validar tudo no startup, antes de aceitar tráfego. O binding do AppConfig roda cedo e falha alto:

startup → loadConfig() → validar → [válido] server.start() → aceita tráfego
[inválido] log fatal + exit non-zero
on startup:
  config = loadConfig()
  if not isValid(config):
    log fatal + exit non-zero
  server.start(config)

Três verificações cobrem a maioria dos casos:

VerificaçãoExemploAção na falha
PresençaDATABASE_URL definidoExit imediato, log da chave faltante
FormatoPORT é número, FEATURE_X é booleanoExit imediato, log do valor recebido
ConsistênciaMAX > MIN, URL responde a pingExit imediato, log da regra violada

Um fail-fast no startup é barato: o orquestrador (Kubernetes, systemd, PM2) marca o container como não-saudável e não encaminha tráfego. Um fail no N-ésimo request já contaminou dados e derrubou sessões.


Acesso à configuração no código

Configuração é dependência, não fato global. A forma como o código acessa o valor determina o acoplamento.

FormaAcoplamentoTestabilidade
process.env.X direto no meio da lógicaAlto, globalBaixa, exige monkey-patch
Objeto de config global importadoMédioMédia, requer reset entre testes
Objeto de config injetado via parâmetroBaixoAlta, passa fixture pronta

A injeção explícita é a forma preferida. O caller do módulo decide qual config usar. Em testes, é trivial passar uma config específica para cada cenário. Em produção, o container de injeção resolve o valor uma vez no startup e distribui.

Ver também code-style.md seção Explicit Dependencies e data-access.md para a mesma regra aplicada a conexões.


Mudanças em runtime

A maioria da configuração é estática: lida no startup, não muda até o próximo deploy. Alguns valores, porém, precisam mudar sem reiniciar:

  • Feature flags (granularidade: mudar por usuário, percentual ou geografia, ver ci-cd.md)
  • Limites operacionais (rate limit, tamanho de batch) ajustados durante incidente
  • Níveis de log temporariamente elevados para debugging em produção

Esses valores precisam de um mecanismo explícito (serviço de configuração dinâmico, feature flag service, endpoint admin autenticado) com três garantias:

  • Auditoria: quem mudou, quando, de que valor para qual.
  • Reversão rápida: voltar ao estado anterior em um clique.
  • Escopo restrito: apenas valores marcados como dinâmicos, nunca config estrutural (connection strings, chaves de criptografia).

Config dinâmica é uma faca afiada. Reservar para o que realmente precisa muda em runtime; o resto continua estático e previsível.


Referência rápida

DecisãoRegra
Valor sensível que vaza e vira notíciaSecret, ver security.md
Valor que varia entre ambientesConfig em camadas, base + override por ambiente
Ordem de precedênciaCódigo < arquivo < ambiente < CLI / runtime < secrets manager
Forma de consumir no códigoObjeto tipado injetado, não leitura direta de env
ValidaçãoFail-fast no startup, antes de aceitar tráfego
Mudança sem restartApenas para flags e limites operacionais, com auditoria
Feature flags como release gradualVer ci-cd.md, seção Feature Flags

Desenvolvido por @thiagocajadev · Fork baseado no repositório pmndrs/docs · Poimandres.