CodeGym /Cursos /Docker for Spring /Esquema limpio de configuración

Esquema limpio de configuración

Docker for Spring
Nivel 12, Lección 4
Disponible

1. Esquema limpio de configuración: significado

Sinceramente, el caos en la configuración generalmente no surge porque la gente sea «mala», sino porque el proyecto vive. Al principio tenemos un application.yml, luego aparece PostgreSQL, después Redis, luego RabbitMQ — y de repente cada uno añade sus parámetros, «para que funcione más rápido». Al cabo de una semana se convierte en un museo: aquí está application-final.yml, al lado application-docker.yml, y en el README hay cuatro comandos de ejecución contradictorios. En ese momento, el proyecto ya se puede contenerizar, pero mantenerlo se vuelve desagradable.

Un esquema limpio de configuración es cuando puedes responder a las preguntas «¿qué pasará si activo el perfil cache?» y «¿por qué la aplicación busca la base de datos en esta dirección?» sin una investigación de nivel detectivesco. Queremos lograr un resultado simple: cada archivo tiene una responsabilidad, y el conjunto de perfiles se combina en un modo como un Lego, no como una «caja de cables que es mejor no tocar».

Es importante que lo «limpio» no se trata de la belleza por la belleza. En el mundo Docker, cualquier caos adicional en la configuración se convierte automáticamente en dos problemas. El primero: empiezas a reconstruir la imagen para diferentes casos, violando el principio same image, different runtime config. El segundo: dejas de entender qué se ejecutó exactamente en un contenedor concreto: por qué escribe archivos aquí, por qué escucha en ese puerto, por qué se conecta a Redis… y empieza el típico: «añadamos otra variable de entorno, a ver si ayuda».

En esta lección fijaremos un conjunto canónico de archivos y perfiles que sea comprensible desde el punto de vista docente, pero que se parezca mucho a cómo se hace en equipos reales: sin cientos de perfiles, sin copias del mismo YAML y sin «un perfil para cada estornudo».

2. Matriz de modos: perfiles y capas

Para que la configuración no se descontrole, primero hay que acordar honestamente: ¿qué ejes de diferenciación tiene la aplicación?. En nuestro servicio de aprendizaje no son «todas las opciones posibles de Spring Boot», sino solo unos pocos contornos de infraestructura, porque el proyecto es deliberadamente simple. Y eso es una buena noticia: cuantos menos ejes, menos posibilidades de obtener application-please-help.yml.

Imagina mentalmente este panorama: existe un modo básico en el que el servicio puede vivir solo (es standalone), y otro modo donde aparece una base de datos externa (postgres). Y sobre postgres podemos «añadir» dos dependencias adicionales: Redis (cache) y RabbitMQ (messaging). Es decir, cache y messaging no son «mundos» independientes, sino capas adicionales.

Para mayor claridad, es útil verlo una vez en forma de tabla. No es una lista de comandos ni «cómo ejecutar», sino simplemente un mapa de diferencias:

Perfil(es) Almacenamiento del catálogo PostgreSQL Redis RabbitMQ Idea del modo
standalone in-memory no no no inicio rápido sin infraestructura
postgres JPA + BD sí no no modelo de persistencia normal
postgres,cache JPA + BD sí sí no hemos añadido caché para lecturas
postgres,cache,messaging JPA + BD sí sí sí hemos añadido cableado mínimo de mensajería

Un punto metodológico clave: no intentamos soportar todas las combinaciones. Por ejemplo, standalone,cache no lo consideramos un modo normal. Teóricamente es posible, pero plantea preguntas como «¿caché de qué, si no hay base de datos?», «¿cómo van los datos de semilla?», «¿qué almacenamos en caché?». Para un curso sobre Docker, esta combinación aporta más confusión que beneficio.

Y otro punto importante: el modo es una composición de perfiles, no «el nombre de un perfil que lo resuelve todo». Por eso elegimos deliberadamente nombres cortos de perfiles según su significado (y no docker, prod2, mode_test). El perfil debe responder a la pregunta «¿qué cambia?». Si el perfil se llama postgres, no tendrás que adivinar qué hace.

3. Regla «una clave, un hogar»

Ahora viene una regla que parece aburrida, pero que salva del apocalipsis de la configuración: cada propiedad debe tener un lugar principal. Puede ser sobrescrita desde fuera (eso es normal), pero no debe estar «escrita como verdadera» simultáneamente en tres archivos de perfil. De lo contrario, obtienes el efecto «last-wins» donde no querías ningún juego de adivinanzas.

Imagina que la configuración es un armario. application.yml es la ropa de todos los días: vaqueros, camiseta, chaqueta. Los perfiles son el «equipo especial»: casco para la obra (postgres), guantes para trabajar con metal (cache), walkie-talkie para negociaciones (messaging). El problema comienza cuando empiezas a guardar los vaqueros tanto en el casco como en los guantes. Eso ya no es un armario, es una búsqueda del tesoro.

¿Qué ponemos en el application.yml base? Todo lo que es igual para todos los modos y refleja la «personalidad» del servicio: el puerto (o su valor por defecto), ajustes básicos de Actuator, nuestro app.export-dir (es decir, una ruta por defecto), algunas banderas lógicas generales. Pero lo que depende de una infraestructura concreta (URL de la base de datos, host de Redis, host de RabbitMQ) debe vivir en los archivos de perfil, porque por significado no es «siempre», sino «solo si se activa este contorno».

Fijemos un matiz importante por separado: los archivos de perfil no deben ser una copia de application.yml. Su tarea es ser cortos y «precisos». Cuanto más corto sea application-postgres.yml, más rápido entenderás qué incluye exactamente el modo postgres.

Para que no sea abstracto, definamos una vez la distribución de claves por archivos. En forma de tabla se lee más rápido que una larga perorata:

Grupo de ajustes Dónde está el «hogar» Ejemplos de claves
Ajustes generales del servicio application.yml server.port, management.*, app.export-dir
Almacenamiento standalone application-standalone.yml app.storage-mode: memory (o un indicador similar)
Fuente de datos PostgreSQL application-postgres.yml spring.datasource.*, spring.jpa.*, spring.flyway.* (si es necesario)
Cableado Redis application-cache.yml spring.data.redis.*, más tu bandera específica como app.cache-enabled
Cableado RabbitMQ application-messaging.yml spring.rabbitmq.*, más app.messaging-enabled

Si notas que quieres escribir la misma propiedad en dos archivos de perfil, generalmente es una señal: o la propiedad es realmente común (entonces su lugar está en application.yml), o estás intentando que un perfil «reescriba» la responsabilidad de otro (por ejemplo, el archivo cache intenta gestionar la fuente de datos).

4. Estructura de recursos: cinco archivos

Hasta ahora hemos visto las propiedades por partes. A continuación las reunimos en un conjunto de archivos funcional; es este conjunto el que debes tener en mente como base del proyecto, no fragmentos sueltos de propiedades.

Ya hemos dicho mucho «mantengan los archivos cortos», pero en un proyecto real esto comienza con una disciplina básica: exactamente cinco archivos claros en src/main/resources, sin poesía autoral en los nombres. Cuando un estudiante (o colega) abre resources, debe ver inmediatamente un mapa de modos, no un acertijo de tres letras.

En nuestro proyecto transversal se ve así:

src/main/resources/
├── application.yml
├── application-standalone.yml
├── application-postgres.yml
├── application-cache.yml
└── application-messaging.yml

¿Por qué es importante específicamente para un curso de Docker? Porque Docker ama la reproducibilidad, y la reproducibilidad ama la previsibilidad. Si la configuración está ordenada limpiamente, puedes ejecutar la misma imagen con SPRING_PROFILES_ACTIVE=postgres,cache y estar seguro de que Redis se activará, la fuente de datos seguirá siendo la de postgres, y nada se sobrescribirá «accidentalmente».

Otra disciplina pequeña pero útil: en el marco del curso no mezclamos YAML y .properties en la misma ubicación. Técnicamente Spring Boot soporta ambos, pero para un principiante esto crea una sorpresa innecesaria: en la misma carpeta dos archivos con sentido similar, y de repente la prioridad no es la esperada. Necesitamos que «last-wins» ocurra solo donde lo permitimos conscientemente (orden de perfiles), no porque alguien haya añadido accidentalmente application.properties.

Y lo último: no colocamos spring.profiles.active dentro de application-postgres.yml y archivos similares. El perfil es algo que viene «desde fuera» al arrancar. El archivo de perfil debe describir diferencias, no intentar activarse a sí mismo (eso sería como «me activo a mí mismo» — suena bien, pero al final te confundirás o tendrás un comportamiento implícito).

5. Ejemplo de configuraciones para Catalog Service

Ahora — versiones completas de la línea base empaquetada, no fragmentos sueltos de propiedades. Las haremos de modo que sean amigables con las sobrescrituras externas mediante variables de entorno, que ya sabes pasar al contenedor.

application.yml — solo lo común

application.yml debe ser aburrido (en el buen sentido). Si parece una «enciclopedia de todos los ajustes de Spring», ya has perdido.

# src/main/resources/application.yml

spring:
  profiles:
    default: standalone # Perfil por defecto si no se especifican perfiles activos al arrancar

server:
  port: ${SERVER_PORT:8080} # El puerto se puede sobrescribir mediante variable de entorno

app:
  export-dir: ${APP_EXPORT_DIR:/app/exports} # Ruta base para exportación, también sobrescribible desde fuera

Aquí se ven tres cosas: hay un perfil por defecto claro, el puerto se puede sobrescribir desde fuera, y el directorio de exportación también es sobrescribible. No decimos dónde está físicamente /app/exports — eso ya es mecánica de archivos; hoy nos centramos en el esquema de configuración.

Si se quiere añadir exposición de Actuator, se puede, pero mantenlo breve y solo si es realmente común para todos los modos. Sigue siendo el mismo application.yml:

# src/main/resources/application.yml

management:
  endpoints:
    web:
      exposure:
        # La lista de endpoints se puede sobrescribir desde fuera (útil para diferentes entornos)
        include: ${MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE:health,info,metrics}

application-standalone.yml — diferencias del modo in-memory

Standalone debe arrancar rápido y no requerir servicios externos. Por eso en el archivo de perfil describimos solo lo que diferencia a este modo.

# src/main/resources/application-standalone.yml

app:
  storage-mode: memory # En standalone usamos almacenamiento in-memory

Sí, el archivo ha quedado «graciosamente pequeño». Y ese es el mejor cumplido. Si un modo se diferencia por una sola clave, significa que has diseñado bien el esquema.

application-postgres.yml — datasource y todo lo relacionado con la BD

En el modo postgres aparece una base de datos, y por tanto un datasource. Aquí es lógico mantener el datasource como «hogar» precisamente en este archivo.

# src/main/resources/application-postgres.yml

app:
  storage-mode: postgres # Cambiamos el modo de almacenamiento a BD

spring:
  datasource:
    # Todos los parámetros clave se pueden sobrescribir mediante variables de entorno (importante para contenedores/Compose)
    url: ${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/catalog}
    username: ${SPRING_DATASOURCE_USERNAME:catalog}
    password: ${SPRING_DATASOURCE_PASSWORD:catalog}

Observa un efecto psicológico importante: incluso si por ahora no ejecutas en Compose, sino solo en un contenedor «en el vacío», el hogar del datasource sigue aquí. No traslades spring.datasource.* a application.yml «porque es más rápido». Ese «más rápido» luego se convierte en «¿por qué standalone intenta conectarse a la base de datos?».

application-cache.yml — Redis como capa adicional

El perfil cache debe ser precisamente un «añadido», no una «segunda versión de postgres». No duplicamos el datasource ni cambiamos el puerto del servidor. Simplemente añadimos el cableado de Redis y (si se desea) una pequeña bandera para nuestros propios beans.

# src/main/resources/application-cache.yml

app:
  cache-enabled: true # Activamos el contorno de caché (útil para registro condicional de beans)

spring:
  data:
    redis:
      # Parámetros de Redis esperamos desde fuera (en contenedores el host casi nunca es localhost)
      host: ${SPRING_DATA_REDIS_HOST:localhost}
      port: ${SPRING_DATA_REDIS_PORT:6379}

Incluso si aún no has implementado un escenario complejo de caché, este archivo sigue siendo útil: fija de antemano que Redis es un contorno separado que se activa con un perfil separado.

application-messaging.yml — RabbitMQ como capa adicional

Lo mismo para messaging: un añadido, no un «nuevo mundo».

# src/main/resources/application-messaging.yml

app:
  messaging-enabled: true # Activamos el contorno de mensajería (registro condicional de beans/escuchadores)

spring:
  rabbitmq:
    # Parámetros de RabbitMQ, que es conveniente pasar mediante variables de entorno/Compose
    host: ${SPRING_RABBITMQ_HOST:localhost}
    port: ${SPRING_RABBITMQ_PORT:5672}

Observa: todos los parámetros están «preparados» para sobrescrituras externas. Ese es el esquema que hace honesta la contenerización: dentro del jar hay valores predeterminados razonables, y desde fuera puedes sobrescribirlos sin reescribir el artefacto.

6. Matices útiles

Perfiles en modo: default, active y last-wins

En este esquema, spring.profiles.default: standalone vive en application.yml y proporciona un inicio de respaldo sin infraestructura externa. Si el arranque no ha dicho nada explícitamente sobre los perfiles, el servicio se levantará en modo in-memory y no empezará de repente a buscar PostgreSQL, Redis o RabbitMQ.

El modo concreto se especifica igualmente desde fuera mediante spring.profiles.active: postgres, postgres,cache o postgres,cache,messaging. Es decir, la línea base empaquetada sigue siendo la misma, y la combinación necesaria de contornos se elige ya al arrancar el contenedor o con java -jar.

Si hay varios perfiles activos, los archivos se superponen en su orden, y el perfil indicado después puede sobrescribir al anterior. Precisamente por eso hemos distribuido las claves por roles: postgres se encarga del datasource, cache de Redis, messaging de RabbitMQ. Entonces last-wins sigue siendo un caso raro y consciente, no una ruleta diaria.

Sobrescrituras externas y línea base empaquetada

Con este esquema de cinco archivos, el servicio ya debería arrancar correctamente sin archivos externos: la línea base empaquetada dentro del jar es autosuficiente. El archivo de configuración externo aquí no es una «segunda verdad», sino una capa de sobrescritura fina que solo se necesita cuando una máquina concreta o una ejecución concreta requiere otro valor.

Si fuera hay archivos application.yml / application-{perfil}.yml ordinarios en la ubicación estándar, Spring Boot puede tomarlos por sí mismo. Si eliges un nombre arbitrario como runtime.yml o una ruta no estándar, dicho archivo se conecta explícitamente mediante spring.config.import, spring.config.additional-location o spring.config.location. Lo importante no es la mecánica por la mecánica, sino la regla: cambia la capa de ejecución, no el jar ni la imagen.

Comprobación del modo activo sin endpoints

Después de ensamblar el esquema, es útil verificar no «parece que funciona», sino dos señales concretas al arrancar: qué perfiles están activos y qué valor final han obtenido una o dos claves características como app.export-dir. Esta comprobación rápida muestra si los valores predeterminados empaquetados, la sobrescritura externa y las variables de entorno han coincidido exactamente en el orden esperado.

Esto se puede sacar al registro de inicio de cualquier manera simple: mediante un ApplicationRunner, un logger ordinario, incluso un System.out.println temporal en un proyecto de aprendizaje. El sentido no está en la belleza de la implementación, sino en no tener que adivinar si has arrancado en modo standalone o ya en postgres,cache, y si estás arrastrando accidentalmente el valor antiguo de app.export-dir.

7. Errores típicos en el esquema final de configuración

En este tema, los errores casi siempre se ven igual: la aplicación «en teoría debería ser así», pero se ejecuta «en teoría de otra manera». Y casi siempre la causa no es Docker ni Spring Boot como «magia negra», sino que la configuración ha perdido su estructura. A continuación, los rastrillos más comunes que vale la pena reconocer.

Error nº1: application-{perfil}.yml se convierte en una copia de application.yml.
Parece «más seguro»: como si cada perfil fuera autosuficiente. En la práctica obtienes cinco archivos casi idénticos, donde las diferencias son difíciles de notar y los cambios hay que hacerlos en cinco sitios a la vez. Al final, alguien se olvida de cambiar un lugar y empieza el «¿por qué solo en modo postgres el puerto es diferente?».

Error nº2: la misma clave vive en varios archivos «simplemente porque salió así».
Por ejemplo, spring.datasource.url aparece tanto en application.yml como en application-postgres.yml. Esto es peligroso porque dejas de entender el «hogar» de la propiedad. En algún momento cambias un valor, pero la aplicación sigue tomando otro, y empiezas a sospechar de misticismo. En realidad, es simplemente un conflicto de fuentes.

Error nº3: el perfil cache o messaging empieza a duplicar los ajustes de postgres.
Los perfiles adicionales deben añadir diferencias, no ser una «segunda versión del modo postgres». Si application-cache.yml de repente empieza a contener un datasource, ya has perdido la idea de composición: activar cache debería afectar solo al contorno de caché, no reescribir el modo base de trabajo con datos.

Error nº4: intentar «activar un perfil desde dentro del perfil».
A veces se quiere escribir spring.profiles.active: postgres dentro de application-postgres.yml. Parece lógico, pero en la práctica rompe la previsibilidad: el perfil debe venir desde fuera al arrancar, de lo contrario obtienes cascadas extrañas y no puedes entender con los ojos por qué se activó lo que se activó.

Error nº5: el archivo de configuración externo se vuelve obligatorio y sin él el servicio no arranca.
Cuando el archivo externo no está marcado como optional: (o usas spring.config.location de modo que las ubicaciones estándar de búsqueda quedan desactivadas), fácilmente obtienes la situación «en mi máquina funciona, en la tuya no». Para el curso y para la plantilla, es importante que la ruta base empaquetada siga siendo funcional: descargaste el proyecto, lo ejecutaste — funciona.

1
Tarea
Docker for Spring, nivel 12, lección 4
Bloqueada
Override dependiente de la máquina sobre un esquema limpio
Override dependiente de la máquina sobre un esquema limpio
1
Cuestionario/control
Spring Data, nivel 12, lección 4
No disponible
Spring Data
Perfiles y configuración externa
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION