Structum Configuration Philosophy¶
Versione: 3.0
Ultima Revisione: 2025-01-11
Stato: Alpha
Indice¶
1. Principio Fondamentale¶
Structum adotta un approccio layered configuration per separare le responsabilità tra:
Developers → Definiscono la struttura e i default
DevOps/SRE → Configurano l’infrastruttura e i secrets
End Users → Personalizzano l’esperienza runtime
1.1 Core Philosophy: “Configuration as Code”¶
La configurazione in Structum segue gli stessi principi del codice:
Versionata (defaults in Git)
Validata (Pydantic schemas)
Testabile (mock providers per unit tests)
Overridable (layer di override senza modificare i sorgenti)
1.2 Separation of Concerns¶
Il codice applicativo non sa da dove arriva la configurazione.
# ✅ Codice business decoupled
config = get_config()
db_host = config.get("database.host")
# Può venire da: TOML, ENV, Secrets, Runtime, ...
# ❌ Anti-pattern: accoppiamento
db_host = os.getenv("DB_HOST", "localhost")
# Hard-coded al meccanismo di override
Questo permette di cambiare il backend (file → Redis → Vault) senza toccare una riga di business logic.
2. Il Modello a 5 Layer¶
Structum usa una gerarchia a priorità per risolvere ogni configurazione:
┌──────────────────────────────────────────────────┐
│ Layer 1: RUNTIME (In-Memory) │ ← MASSIMA PRIORITÀ
│ • Modifiche via config.set() non persistite │
│ • Valido solo per il processo corrente │
├──────────────────────────────────────────────────┤
│ Layer 2: USER PERSISTENT (Runtime Saved) │
│ • File: ~/.structum/{namespace}_saved.json │
│ • Modifiche persistite via config.save() │
│ • Specifico per utente/istanza │
├──────────────────────────────────────────────────┤
│ Layer 3: ENVIRONMENT VARIABLES │
│ • Formato: STRUCTUM_{NAMESPACE}__{KEY} │
│ • Configurazione infrastrutturale (Docker/K8s) │
│ • Impostato da DevOps/deployment scripts │
├──────────────────────────────────────────────────┤
│ Layer 4: SECRETS │
│ • File: config/.secrets.toml (git-ignored) │
│ • Contiene password, API keys, tokens │
│ • Mergiato automaticamente con Layer 5 │
├──────────────────────────────────────────────────┤
│ Layer 5: APPLICATION DEFAULTS │ ← MINIMA PRIORITÀ
│ • File: config/app/{namespace}.toml │
│ • Defaults dichiarati dallo sviluppatore │
│ • Versionato in Git, baseline per tutti │
└──────────────────────────────────────────────────┘
2.1 Risoluzione: “First Found Wins”¶
Quando richiedi config.get("database.host"):
# Pseudo-codice del resolver
def resolve_value(key):
if key in runtime_layer:
return runtime_layer[key] # Layer 1
if key in user_persistent_layer:
return user_persistent_layer[key] # Layer 2
if key in environment_variables:
return environment_variables[key] # Layer 3
if key in secrets_layer:
return secrets_layer[key] # Layer 4
if key in application_defaults:
return application_defaults[key] # Layer 5
return default_value # Se nessun layer ha il valore
Il primo valore trovato vince. Layer superiori sovrascrivono quelli inferiori.
3. Separazione delle Responsabilità¶
3.1 Layer 5: Developer Layer (Application Defaults)¶
Chi: Software Engineer, Library Author
Formato: TOML
Location: config/app/{namespace}.toml (nel repository, versionato)
Scopo: Definire struttura, chiavi valide, valori di default sicuri
Esempio:
# config/app/database.toml
[default]
host = "localhost"
port = 5432
pool_size = 10
timeout = 30
[default.auth]
user = "app_user"
# ⚠️ NON mettere password qui
[production]
host = "db.prod.example.com"
pool_size = 50
Caratteristiche:
Versionato in Git (parte del codice)
Contiene solo valori sicuri (no secrets)
Supporta ambienti multipli (
[default],[production],[development])Validato da Pydantic schema (opzionale ma raccomandato)
Accesso:
config.get("database.host") # "localhost" (da [default])
config.get("database.pool_size") # 10
3.2 Layer 4: Secrets Layer¶
Chi: DevOps, Security Team
Formato: TOML (locale) o Secret Manager (produzione)
Location: config/.secrets.toml (git-ignored) o Vault/K8s Secrets
Scopo: Isolare credenziali sensibili dal codice
Esempio:
# config/.secrets.toml (NON versionare!)
[database.auth]
password = "SuperSecretPassword123"
[api_client]
api_key = "sk-proj-abc123xyz"
client_secret = "very-secret-value"
⚠️ CRITICO: Security Checklist
[ ] Aggiunto a
.gitignore[ ] Permessi file:
chmod 600 config/.secrets.toml[ ] In produzione: usa Vault/K8s Secrets invece di file
[ ] Rotazione periodica delle credenziali
Merge Automatico: Il plugin mergia automaticamente secrets con i namespace corrispondenti:
# Merged in-memory:
config.get("database.auth.user") # "app_user" (da database.toml)
config.get("database.auth.password") # "SuperSecret..." (da .secrets.toml)
3.3 Layer 3: Environment Variables (Infrastructure)¶
Chi: DevOps, SRE, CI/CD Pipeline
Formato: Environment Variables
Location: Shell, Docker Compose, K8s ConfigMaps
Scopo: Override per ambiente specifico senza modificare file
Convenzione:
{ENV_PREFIX}__{NAMESPACE}__{PATH__TO__KEY}
Esempio:
# Override singolo valore
export STRUCTUM__DATABASE__HOST=prod-db.example.com
export STRUCTUM__DATABASE__PORT=3306
# Override valore nested
export STRUCTUM__DATABASE__POOL__MAX_SIZE=100
# Liste (comma-separated)
export STRUCTUM__DATABASE__HOSTS=db1.com,db2.com,db3.com
Uso Tipico: Docker Compose
# docker-compose.yml
version: '3.8'
services:
app:
image: myapp:latest
environment:
- STRUCTUM__DATABASE__HOST=postgres
- STRUCTUM__DATABASE__PORT=5432
- STRUCTUM__LOG_LEVEL=INFO
Uso Tipico: Kubernetes
# k8s-deployment.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
data:
STRUCTUM__DATABASE__HOST: "prod-db.svc.cluster.local"
STRUCTUM__DATABASE__PORT: "5432"
---
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: myapp
envFrom:
- configMapRef:
name: myapp-config
3.4 Layer 2: User Persistent (Runtime Saved)¶
Chi: Instance Operator, End User, Application Runtime
Formato: JSON
Location: ~/.structum/{namespace}_saved.json (user home)
Scopo: Salvare preferenze utente tra riavvii
Caratteristiche:
Creato automaticamente al primo
config.save()Contiene solo chiavi modificate (non duplica i default)
Specifico per utente/istanza (non condiviso)
Esempio:
# Modifica runtime
config = get_config()
config.set("ui.theme", "dark_mode")
config.set("ui.font_size", 14)
# Persiste su disco
config.save()
# File creato: ~/.structum/ui_saved.json
# {
# "theme": "dark_mode",
# "font_size": 14
# }
Al riavvio:
config = get_config()
theme = config.get("ui.theme") # "dark_mode" (dal file saved)
# Anche se config/app/ui.toml dice theme = "light"
Casi d’Uso:
Preferenze UI (tema, font size, layout)
Ultimi file aperti
Dimensioni finestre
Cache persistente
3.5 Layer 1: Runtime (In-Memory)¶
Chi: Application Runtime
Formato: In-Memory Dict
Location: RAM (volatile)
Scopo: Modifiche temporanee senza persistenza
Caratteristiche:
Vive solo durante l’esecuzione del processo
Non persiste dopo
exit()(a meno di chiamaresave())Utile per test, debugging, feature flags temporanei
Esempio:
config = get_config()
# Modifica temporanea (solo in-memory)
config.set("debug.verbose", True)
print(config.get("debug.verbose")) # True
# NON chiamiamo config.save()
# Riavvio applicazione
config = get_config()
print(config.get("debug.verbose")) # False (default dal TOML)
Casi d’Uso:
Feature flags temporanei
Debug mode attivato da CLI flag
Test A/B runtime
Override per sessione specifica
4. Flusso di Risoluzione¶
4.1 Esempio Completo: Risoluzione Multi-Layer¶
Setup:
# config/app/database.toml
[default]
host = "localhost"
port = 5432
pool_size = 10
# config/.secrets.toml
[database.auth]
password = "secret123"
# Environment
export STRUCTUM__DATABASE__HOST=staging-db.com
export STRUCTUM__DATABASE__PORT=3306
// ~/.structum/database_saved.json
{
"host": "my-custom-db.local"
}
# Runtime
config.set("database.pool_size", 50)
Risoluzione:
config = get_config()
config.get("database.host")
# Layer 1 (Runtime): ❌ Non impostato
# Layer 2 (User): ✅ "my-custom-db.local" ← VINCE
config.get("database.port")
# Layer 1 (Runtime): ❌ Non impostato
# Layer 2 (User): ❌ Non nel file saved
# Layer 3 (ENV): ✅ 3306 ← VINCE
config.get("database.pool_size")
# Layer 1 (Runtime): ✅ 50 ← VINCE
config.get("database.auth.password")
# Layer 1-3: ❌ Non impostato
# Layer 4 (Secrets): ✅ "secret123" ← VINCE
4.2 Diagramma di Flusso¶
┌─────────────────────┐
│ config.get("key") │
└──────────┬──────────┘
│
▼
┌──────────────────────┐
│ Layer 1: Runtime? │
│ (in-memory via set()) │
└──────┬───────────────┘
│
NO ┌┴┐ YES
┌─────┤ ├─────┐
│ └─┘ │
▼ ▼
┌──────────────────┐ [Return Value]
│ Layer 2: User? │
│ (*_saved.json) │
└────┬─────────────┘
│
NO ┌┴┐ YES
┌─────┤ ├─────┐
│ └─┘ │
▼ ▼
┌──────────────┐ [Return Value]
│ Layer 3: ENV?│
│ (STRUCTUM__) │
└────┬─────────┘
│
NO ┌┴┐ YES
┌─┤ ├──┐
│ └─┘ │
▼ ▼
┌─────────────┐ [Return Value]
│ Layer 4: │
│ Secrets? │
└──┬──────────┘
│
NO ┌┴┐ YES
┌─┤ ├──┐
│ └─┘ │
▼ ▼
┌──────────────┐ [Return Value]
│ Layer 5: │
│ Defaults? │
│ (.toml) │
└──┬───────────┘
│
NO ┌┴┐ YES
┌─┤ ├──┐
│ └─┘ │
▼ ▼
[Return [Return Value]
Default
Arg]
5. Diagrammi e Visualizzazioni¶
5.1 Architettura Completa¶
┌────────────────────────────────────────────────────────────────┐
│ APPLICATION CODE │
│ config.get("db.host") │
└────────────────────────┬───────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ CONFIG FACADE (Singleton) │
│ • Gestisce layer priority │
│ • Risolve dot-notation (a.b.c → nested dict) │
│ • Cache in-memory per performance │
└────────────────────────┬───────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ DYNACONF CONFIG PROVIDER (Strategy) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Namespace │ │ Namespace │ │ Namespace │ │
│ │ "database" │ │ "api_client" │ │ "ui" │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ LAYER RESOLVER (Priority Stack) │ │
│ ├─────────────────────────────────────────────────┤ │
│ │ 1. Runtime (volatile) │ │
│ │ 2. User Persistent (~/.structum/*.json) │ │
│ │ 3. Environment Variables (STRUCTUM__*) │ │
│ │ 4. Secrets (.secrets.toml) │ │
│ │ 5. Application Defaults (.toml) │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ PYDANTIC VALIDATOR (Optional) │ │
│ │ • Strong typing │ │
│ │ • Field validation │ │
│ │ • Custom rules │ │
│ └─────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
5.2 Data Flow (Mermaid)¶
graph TB
subgraph "Configuration Layers (Priority ↓)"
L1[Layer 1: Runtime In-Memory]
L2[Layer 2: User Persistent JSON]
L3[Layer 3: Environment Variables]
L4[Layer 4: Secrets TOML]
L5[Layer 5: Application Defaults]
end
subgraph "Validation & Merging"
V[Pydantic Validator]
M[Layer Merger]
end
subgraph "Application Access"
APP[config.get - key -]
FACADE[Config Facade]
end
L5 -->|Load| M
L4 -->|Load & Merge| M
L3 -->|Load & Override| M
L2 -->|Load & Override| M
L1 -->|Override| M
M -->|Per-Layer| V
V -->|Validated Data| FACADE
APP -->|Request| FACADE
FACADE -->|Resolve| L1
FACADE -->|Fallback| L2
FACADE -->|Fallback| L3
FACADE -->|Fallback| L4
FACADE -->|Fallback| L5
6. Scenari d’Uso¶
6.1 Scenario: Sviluppo Locale¶
Attori: Developer
Setup:
config/app/database.toml→ defaults (versionato)Nessuna env var
Nessun file saved
Comportamento:
config.get("database.host") # "localhost" (da TOML)
config.get("database.port") # 5432 (da TOML)
6.2 Scenario: Staging Environment¶
Attori: CI/CD Pipeline, DevOps
Setup:
config/app/database.toml→ defaultsENV vars impostate dal deployment:
STRUCTUM__DATABASE__HOST=staging-db.internal STRUCTUM__DATABASE__PORT=5432
Comportamento:
config.get("database.host") # "staging-db.internal" (ENV vince)
config.get("database.port") # 5432 (ENV, anche se = default)
6.3 Scenario: Production con Secrets¶
Attori: SRE, Security Team
Setup:
config/app/database.toml→ defaultsconfig/.secrets.toml→ password (git-ignored)ENV vars da K8s ConfigMap + Secrets
Comportamento:
config.get("database.host") # "prod-db.cluster.local" (da ENV)
config.get("database.auth.user") # "prod_user" (da ENV)
config.get("database.auth.password") # "..." (da .secrets.toml)
6.4 Scenario: End User Customization¶
Attori: End User
Setup:
App installata con defaults
User modifica tema UI
Preferenza salvata in
~/.structum/ui_saved.json
Comportamento:
# Prima modifica
config.set("ui.theme", "dark")
config.save() # Scrive ~/.structum/ui_saved.json
# Dopo riavvio
config.get("ui.theme") # "dark" (da saved.json, non da TOML)
7. Best Practices¶
7.1 Cosa Va in Ogni Layer¶
Layer |
Contenuto |
Esempi |
|---|---|---|
Defaults (TOML) |
Valori sicuri, struttura |
|
Secrets |
Credenziali sensibili |
|
Environment |
Config infrastrutturale |
|
User Persistent |
Preferenze utente |
|
Runtime |
Override temporanei |
|
7.2 Anti-Patterns da Evitare¶
❌ Secrets in TOML versionato
# config/app/database.toml
[default.auth]
password = "admin123" # ❌ Esposto in Git!
❌ Hardcoded paths in codice
# ❌ Accoppiamento
with open("/etc/myapp/config.toml") as f:
config = toml.load(f)
# ✅ Usa il framework
config = get_config()
❌ Magic values senza default
# ❌ Fallisce se chiave manca
db_host = config.get("database.host") # KeyError!
# ✅ Sempre fornisci default
db_host = config.get("database.host", default="localhost")
7.3 Security Checklist¶
Production Deployment:
[ ]
.secrets.tomlin.gitignore[ ] Permessi file secrets:
chmod 600[ ] Env vars per credenziali (no file in prod)
[ ] Vault/K8s Secrets per produzione
[ ] Rotazione periodica passwords
[ ] Audit logging delle modifiche config
[ ] Validazione Pydantic abilitata
7.4 Performance Tips¶
Cache aggressiva: Il facade cacheggia valori risolti
Lazy loading: I namespace vengono caricati solo se acceduti
Hot reload: Usa con cautela (overhead di watchdog)
Validazione: Valida all’init, non ad ogni
get()
Changelog¶
v3.0.0 (2025-01-11)¶
📝 Revisione completa della filosofia
✨ Chiarito modello a 5 layer (prima erano 4)
📊 Aggiunti diagrammi Mermaid e ASCII
🎯 Separazione chiara: Runtime vs User Persistent
v2.0.0 (2024-09-01)¶
✨ Introdotto layer Secrets separato
🔒 Best practices di security
v0.1.0 (Alpha)¶
🎉 Versione iniziale
Riferimenti¶
Maintainer: Structum Core Team
Feedback: https://github.com/structum-lab/structum/discussions