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.
backend/src/main/resources/application.yml → mover 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.
[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).