Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.0 KiB
id, title, status, type, domain, scope, priority, depends, blocks, related, created, updated, tags
| id | title | status | type | domain | scope | priority | depends | blocks | related | created | updated | tags |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0018 | Config & Env Management | completado | feature | multi-app | media | 2026-05-17 | 2026-05-17 |
0018 — Config & Env Management
Metadata
| Campo | Valor |
|---|---|
| ID | 0018 |
| Estado | pendiente |
| Prioridad | media |
| Tipo | feature |
Dependencias
Ninguna.
Objetivo
Funciones reutilizables para cargar, validar y gestionar configuracion de apps usando struct tags en Go y dataclasses en Python. Reemplazar los os.Getenv dispersos por un patron consistente con validacion, defaults y soporte para .env.
Contexto
- Las apps cargan config ad-hoc:
os.Getenvrepetidos, sin validacion, sin defaults, sin mensajes de error descriptivos. - Cuando falta una variable de entorno, el error aparece mucho despues (conexion fallida, panic) en vez de al arrancar.
- No hay soporte para archivos
.env— cada desarrollador tiene que exportar variables manualmente. - Patron deseado: definir un struct con tags, poblarlo automaticamente, validar al arrancar, fallar rapido con errores claros.
Funciones Go (dominio infra)
| Funcion | Purity | Firma (simplificada) |
|---|---|---|
config_from_env |
impure | (dst any) error |
config_from_file |
impure | (path string, dst any) error |
config_validate |
pure | (cfg any) (ConfigValidation, error) |
config_merge |
pure | (base, override map[string]string) map[string]string |
config_dump |
pure | (cfg any) map[string]string |
env_require |
impure | (key string) (string, error) |
env_require_all |
impure | (keys []string) (map[string]string, error) |
dotenv_load |
impure | (path string) error |
Struct tags
type AppConfig struct {
Port int `env:"PORT" default:"8080" required:"true"`
DBPath string `env:"DB_PATH" default:"data.db"`
APIKey string `env:"API_KEY" required:"true" secret:"true"`
LogLevel string `env:"LOG_LEVEL" default:"info"`
}
env:"VAR"— nombre de la variable de entornodefault:"valor"— valor si la variable no existerequired:"true"— error si no tiene valor (ni env ni default)secret:"true"—config_dumpredacta el valor como***
Detalle por funcion
config_from_env — Recorre campos del struct via reflection. Para cada campo lee os.Getenv(tag_env), aplica default si vacio, convierte al tipo del campo (int, bool, string, float64, []string). Error si un campo required queda vacio.
config_from_file — Detecta formato por extension (.json, .yaml). Deserializa al struct destino. Solo JSON usa stdlib; YAML solo si ya existe dependencia en el proyecto (sino stub con error).
config_validate — Valida un struct ya poblado: campos required no vacios, rangos numericos (futuro: tag min/max), formatos basicos (URL, email via regex simple). Retorna ConfigValidation con todos los errores acumulados.
config_merge — Merge de dos maps string→string donde override gana. Util para: defaults → file → env (cada capa overridea la anterior).
config_dump — Convierte struct a map[string]string para logging. Campos con secret:"true" se muestran como ***. Util para imprimir config al arrancar sin leakear secrets.
env_require / env_require_all — Helpers ligeros para cuando no se necesita un struct completo. env_require_all acumula todos los errores en vez de fallar en el primero.
dotenv_load — Parsea un archivo .env (lineas KEY=VALUE, ignora comentarios # y lineas vacias) y hace os.Setenv para cada entrada. No sobreescribe variables que ya existen en el entorno.
Funciones Python (dominio infra)
| Funcion | Purity | Firma (simplificada) |
|---|---|---|
config_from_env |
impure | (cls: type[T]) -> T |
dotenv_load |
impure | (path: str) -> None |
config_from_env_py_infra — Recibe una dataclass con field metadata y la puebla desde os.environ. Soporta defaults de la dataclass y type hints para conversion (int, float, bool, str).
@dataclass
class AppConfig:
port: int = field(default=8080, metadata={"env": "PORT"})
db_path: str = field(default="data.db", metadata={"env": "DB_PATH"})
api_key: str = field(default="", metadata={"env": "API_KEY", "required": True})
dotenv_load_py_infra — Mismo comportamiento que la version Go: parsea .env, setea en os.environ, no sobreescribe existentes.
Tipos (dominio infra)
| Tipo | Algebraic | Campos |
|---|---|---|
ConfigError |
product | field string, message string, tag string |
ConfigValidation |
product | errors []ConfigError, is_valid bool |
ConfigError.tag indica que validacion fallo (required, format, range). Permite que el caller filtre o agrupe errores programaticamente.
Tareas
Fase 1: Tipos y funciones puras
- 1.1 Crear tipos
ConfigError,ConfigValidationenfunctions/infra/+.mdentypes/infra/ - 1.2
config_validate— validacion de struct con acumulacion de errores - 1.3
config_merge— merge de maps con prioridad - 1.4
config_dump— dump a map con redaccion de secrets - 1.5 Tests unitarios para las tres funciones puras
Fase 2: Funciones impuras Go
- 2.1
dotenv_load— parser de.env+os.Setenv - 2.2
env_requireyenv_require_all— getenv con errores descriptivos - 2.3
config_from_env— reflection sobre struct tags + conversion de tipos - 2.4
config_from_file— JSON (stdlib), YAML (stub si no hay dep) - 2.5 Tests con
t.Setenvpara aislar variables de entorno
Fase 3: Funciones Python + indexado
- 3.1
config_from_env_py_infra— dataclass +os.environ - 3.2
dotenv_load_py_infra— parser.env - 3.3
fn indexy verificar todas las funciones en registry.db - 3.4
go vet -tags fts5limpio
Ejemplo de uso
// 1. Definir config como struct con tags
type ServerConfig struct {
Port int `env:"PORT" default:"8080" required:"true"`
DBPath string `env:"DB_PATH" default:"registry.db"`
APIKey string `env:"API_KEY" required:"true" secret:"true"`
LogLevel string `env:"LOG_LEVEL" default:"info"`
}
// 2. Cargar .env si existe, poblar struct, validar
func main() {
_ = infra.DotenvLoad(".env")
var cfg ServerConfig
if err := infra.ConfigFromEnv(&cfg); err != nil {
log.Fatalf("config: %v", err)
}
validation, _ := infra.ConfigValidate(&cfg)
if !validation.IsValid {
for _, e := range validation.Errors {
log.Printf(" %s: %s (%s)", e.Field, e.Message, e.Tag)
}
os.Exit(1)
}
// 3. Log config al arrancar (secrets redactados)
for k, v := range infra.ConfigDump(&cfg) {
log.Printf(" %s=%s", k, v)
}
// Output: PORT=8080 DB_PATH=registry.db API_KEY=*** LOG_LEVEL=info
}
// Caso simple: solo necesito dos variables
dbURL, err := infra.EnvRequire("DATABASE_URL")
if err != nil {
log.Fatal(err) // "DATABASE_URL: variable de entorno requerida no definida"
}
vars, err := infra.EnvRequireAll([]string{"DB_HOST", "DB_PORT", "DB_NAME"})
if err != nil {
log.Fatal(err) // "variables no definidas: DB_HOST, DB_NAME"
}
# Python
from infra import config_from_env, dotenv_load
@dataclass
class Config:
port: int = field(default=8080, metadata={"env": "PORT"})
api_key: str = field(default="", metadata={"env": "API_KEY", "required": True})
dotenv_load(".env")
cfg = config_from_env(Config)
print(f"Listening on :{cfg.port}")
Decisiones de diseno
- Solo stdlib para Go: reflection +
os+encoding/json. Sin viper, envconfig, ni dependencias externas. YAML queda como stub hasta que el proyecto lo necesite. - Struct tags como fuente de verdad: la definicion del struct ES la documentacion de la config. Un nuevo campo = una linea de codigo.
- Fail-fast con errores acumulados:
config_validatereporta TODOS los problemas, no solo el primero. Asi el usuario los arregla todos de una vez. dotenv_loadno sobreescribe: las variables ya seteadas en el entorno tienen prioridad. Esto permite overrides en CI/deploy sin tocar el.env.secrettag para redaccion: evita leakear API keys en logs.config_dumpes seguro para imprimir al arrancar.- Python usa dataclasses nativo: sin pydantic ni attrs. Consistente con el enfoque zero-deps del registry.
Riesgos
- Reflection en Go es fragil: tipos no soportados (nested structs, maps) darian panics. Mitigado limitando v1 a tipos planos (
string,int,bool,float64,[]string) y documentando la restriccion. - Parser de
.envdemasiado simple: no soportaria valores multilinea, comillas con escape, ni interpolacion. Suficiente para el 95% de casos; documentar limitaciones. - Duplicacion Go/Python: dos implementaciones del mismo concepto. Mitigado porque son simples y el comportamiento esta bien definido en esta spec.