DRY in Configuration
When to Use
When managing application configuration, environment variables, feature flags, and settings across environments.
Decision Framework
| Configuration Type | Single Source | Distribution Method |
|---|---|---|
| Environment-specific (DB host, API keys) | .env files, secrets manager |
Environment variables, config service |
| Application settings (timeouts, limits) | Config files (YAML, JSON) | Typed config classes, validation at startup |
| Feature flags | Feature flag service | Runtime config API |
| Business rules as config | Database, admin UI | Config API, cache aggressively |
| Infrastructure as code | Terraform, CloudFormation | Code generation, state management |
| Schema definitions | JSON Schema, Protocol Buffers | Code generation for multiple languages |
The Problem: Configuration Duplication
# ANTI-PATTERN: Configuration duplicated across files
# config/development.yml
database:
pool_size: 10
timeout: 5000
retry_attempts: 3
email:
from: "dev@example.com"
smtp_host: "localhost"
# config/staging.yml
database:
pool_size: 10 # Duplicated
timeout: 5000 # Duplicated
retry_attempts: 3 # Duplicated
email:
from: "staging@example.com"
smtp_host: "smtp.staging.example.com"
# config/production.yml
database:
pool_size: 10 # Duplicated
timeout: 5000 # Duplicated
retry_attempts: 3 # Duplicated
email:
from: "noreply@example.com"
smtp_host: "smtp.example.com"
# What happens when default pool_size changes? Update 3 files!
Pattern: DRY Configuration
# config/defaults.yml (single source of truth)
database:
pool_size: 10
timeout: 5000
retry_attempts: 3
email:
from: "noreply@example.com"
smtp_host: "smtp.example.com"
# config/development.yml (only overrides)
email:
from: "dev@example.com"
smtp_host: "localhost"
# config/staging.yml (only overrides)
email:
smtp_host: "smtp.staging.example.com"
# config/production.yml (only overrides, if any)
# Inherits all defaults
# Load with merge strategy (Python example)
import yaml
from pathlib import Path
def load_config(env='development'):
defaults = yaml.safe_load(Path('config/defaults.yml').read_text())
env_config = yaml.safe_load(Path(f'config/{env}.yml').read_text())
# Deep merge env_config into defaults
return deep_merge(defaults, env_config)
config = load_config(os.getenv('APP_ENV', 'development'))
Schema-Driven Configuration
// DRY: Single schema generates types, validation, docs
import { z } from 'zod';
const ConfigSchema = z.object({
database: z.object({
poolSize: z.number().int().min(1).max(100).default(10),
timeout: z.number().int().positive().default(5000),
retryAttempts: z.number().int().min(0).max(10).default(3),
}),
email: z.object({
from: z.string().email(),
smtpHost: z.string(),
smtpPort: z.number().int().default(587),
}),
});
type Config = z.infer<typeof ConfigSchema>;
// Validation at startup
const config = ConfigSchema.parse({
database: { poolSize: 10, timeout: 5000, retryAttempts: 3 },
email: { from: 'dev@example.com', smtpHost: 'localhost' },
});
// Auto-generate documentation from schema
// Auto-generate JSON Schema for config files
Infrastructure as Code (Single Source)
# terraform/variables.tf (single source of truth)
variable "app_config" {
type = object({
pool_size = number
timeout = number
retry_attempts = number
})
default = {
pool_size = 10
timeout = 5000
retry_attempts = 3
}
}
# Generate application config file from Terraform
resource "local_file" "app_config" {
content = jsonencode({
database = var.app_config
})
filename = "${path.module}/generated/config.json"
}
# No manual duplication — config exists in one place
Common Mistakes
- Copying entire config files per environment — Massive duplication, hard to see what actually differs
- Hardcoding values in code — Configuration scattered across codebase, impossible to manage
- No validation — Invalid config only discovered at runtime, often in production
- Manual synchronization — Expecting developers to keep multiple config files in sync
- Ignoring config as code — No version control, no review process, no audit trail
- Secrets in code — API keys, passwords in source control (use secrets manager)