Saltar a contenido

ADR-029 — Configuración operativa completa FUERA del jar (fail-fast en todo perfil)

  • Status: accepted (2026-08-24; pedido explícito del operador: "siempre el yaml de configuración por completo tiene que estar fuera del .jar, nada de sorpresas con yaml adentro del big fat jar")
  • Context: Hoy backend/src/main/resources/application.yml viaja DENTRO del fat jar (junto con application-e2e.properties y application-load.properties). Cambiar un valor operativo en un despliegue (puerto, TLS, rutas de bases/branding, flags por cliente como sp.reconocimiento.habilitado de ADR-027, logging, URL de DB) obliga a reempaquetar el jar — o peor: el sistema arranca en silencio con defaults embebidos que el operador no ve. Los SECRETOS ya viven afuera (env vars SP_DB_PASSWORD, SP_MASTER_KEY, SP_GESTOR_PASSWORD, …); lo que falta externalizar es el resto de la configuración. Se conecta con el despliegue single-tenant por supermercado (branding + flags por cliente) y con M6 (Windows Service / jpackage: jar + carpeta config/ es el layout natural).
  • Alternatives:
  • Statu quo (yml embebido): cambiar config = reempaquetar; defaults invisibles. Rechazado (es exactamente la "sorpresa" que el operador quiere matar).
  • Split contrato/operativa: el jar conserva un yml mínimo con el contrato del código (Jackson snake_case, JPA validate, Flyway) y solo lo operativo va afuera. Menos frágil ante un yaml externo incompleto, pero deja config adentro del jar. Descartado por el operador — eligió lo literal; la fragilidad se mitiga con el chequeo de contrato (abajo).
  • Fail-fast solo en prod: dev/CI seguirían con config embebida. Descartado por el operador — eligió uniformidad total; la fricción se mitiga versionando la referencia.
  • Decision:
  • El jar NO embebe ningún archivo de configuración. src/main/resources queda sin application*.yml / application*.properties (los archivos de src/test/resources no viajan en el jar y no cambian). Los perfiles de harness (e2e, load) también se mudan a backend/config/.
  • La config vive en config/ junto al jar (backend/config/application.yml en el repo). Se usa la búsqueda estándar de Spring Boot (./config/ relativo al working dir) — cero código de carga custom; solo el chequeo de arranque es nuestro.
  • Referencia versionada, sin secretos: backend/config/application.yml se versiona en git como referencia documentada (resuelve dev y CI sin fricción: ambos corren desde backend/ y Spring la encuentra sola). Los secretos siguen SOLO en env vars — el archivo versionado referencia ${SP_*} pero jamás contiene un valor secreto (CONSTITUTION §Security). En prod, el deploy copia jar + config/ y el operador edita SU copia.
  • Fail-fast en TODO perfil: sin config/application.yml el arranque falla rápido con un mensaje claro que dice qué archivo falta y dónde ponerlo. Nunca arrancar en silencio con defaults. Mecanismo: la config externa lleva una propiedad centinela (sp.config.externa: true) que solo existe en el archivo; un chequeo temprano de arranque la exige.
  • Chequeo de contrato al arranque: además del centinela, el arranque valida que las claves críticas del contrato del código estén presentes y con el valor esperado — spring.jackson.property-naming-strategy: SNAKE_CASE (si falta, TODA la API cambia de formato y la landing muere), spring.jpa.hibernate.ddl-auto: validate, config de Flyway. Si falta una → no arranca y nombra la clave faltante. Esto cierra el riesgo de la opción literal ("borrar una línea de más rompe la API en silencio").
  • Consequences:
  • Cambiar cualquier valor operativo = editar un archivo y reiniciar. Cero reempaquetado.
  • Ningún default invisible: lo que el sistema usa es lo que el operador ve en config/.
  • La config queda documentada y con historia git (la referencia versionada).
  • Layout natural para M6 (Windows Service / jpackage: jar + config/).
  • Un despliegue MAL copiado (sin config/) no arranca — a propósito: mejor no arrancar que arrancar distinto a lo esperado.
  • El yaml externo carga también el contrato técnico; el chequeo de contrato es obligatorio para que un archivo incompleto no pueda producir un sistema "andando pero roto".
  • CI y dev dependen del archivo versionado (si alguien lo borra del repo, nada arranca — el fail-fast lo hace evidente al instante).
  • Un IDE test runner cuyo directorio de trabajo sea la RAÍZ DEL REPO (p.ej. el default del VS Code Java Test Runner) rompe todo @SpringBootTest con el mensaje centinela — Spring busca ./config/ relativo a ese working directory, no lo encuentra ahí. Se soluciona configurando el working directory del runner a backend/ (el default de módulo de IntelliJ ya funciona sin tocar nada). Trade-off de developer experience aceptado y documentado acá para que no sea una sorpresa silenciosa.

Implementation Plan

  • Affected paths:
  • backend/src/main/resources/application.ymlmover a backend/config/application.yml (versionado; secretos siguen como ${SP_*}).
  • backend/src/main/resources/application-e2e.properties, application-load.properties → mover a backend/config/.
  • NEW chequeo de arranque (centinela + claves de contrato): EnvironmentPostProcessor o ApplicationListener<ApplicationEnvironmentPreparedEvent> en soporte/ — falla con mensaje claro (IllegalStateException) nombrando archivo/clave faltante. Registrado en META-INF/spring.factories o equivalente Boot 4.
  • .github/workflows/ci.yml: sin cambios esperados (los jobs corren con working-directory: backend → Spring encuentra backend/config/); verificar en verde.
  • Scripts/docs de arranque (dev-env, M6): documentar que el jar exige config/ al lado.
  • Patterns: búsqueda estándar ./config/ de Spring Boot (sin custom loaders); fail-fast con mensaje sin secretos (mismo criterio que GestorSecurityConfig F7).
  • Tests: arranque sin config externa → falla con el mensaje esperado; config sin la clave centinela → falla; config sin snake_case → falla nombrándola; jar empaquetado no contiene application*.yml|properties; flag operativo cambiado en el archivo externo → comportamiento cambia tras reinicio sin reempaquetar.

Verification

  • [x] AC-63 — El jar empaquetado NO contiene ningún application*.yml ni application*.properties (test que inspecciona el jar); src/main/resources queda sin archivos de configuración. Evidencia: ConfigExternaEmpaquetadoTest 2/2 (recursivo).
  • [x] AC-64 — Fail-fast: arrancar (cualquier perfil) sin config/application.yml → el proceso NO arranca y el error nombra el archivo esperado, el working directory real y dónde ponerlo; nunca arranca con defaults embebidos. Evidencia: ConfigExternaEnvironmentPostProcessorTest 3/3.
  • [x] AC-65 — Chequeo de contrato: config externa sin spring.jackson.property-naming-strategy= SNAKE_CASE (o sin ddl-auto=validate) → el arranque falla nombrando la clave faltante. Evidencia: ConfigExternaEnvironmentPostProcessorTest (ac65 x2).
  • [x] AC-66 — Operable sin reempaquetar: cambiar un valor operativo (p.ej. sp.reconocimiento.habilitado) SOLO en config/application.yml y reiniciar cambia el comportamiento observable; el archivo versionado de referencia no contiene ningún secreto (escaneo del archivo).