Configuración y Perfiles en Boot 4
El mismo código de VehicleValuationService debería apuntar a un endpoint de sandbox de Black Book en dev y al real en prod — sin que cambie una sola línea de código.
Cada pieza de cableado cubierta hasta ahora asumió que las clases involucradas ya conocían su propia configuración -- BlackBookClient simplemente tenía un baseUrl, VehicleRepository simplemente tenía una base de datos con la cual hablar. Esta lección trata sobre de dónde viene realmente esa configuración, y por qué la respuesta importa más de lo que parece. La respuesta equivocada, que aparece constantemente en código base real bajo presión de plazos, es el hardcoding: una clave de API de un proveedor escrita directamente en el código fuente de BlackBookClient, una cadena de conexión de Postgres horneada en el código de la aplicación, un nombre de host de Redis que está bien en una laptop y mal en todos lados más. Cada uno de esos casos es un incidente de producción esperando el día en que alguien olvide que está ahí, o el día en que el valor necesite cambiar y la única forma de cambiarlo sea un redeploy -- o el día en que esa clave de API hardcodeada termine comiteada en un repositorio público y tenga que rotarse bajo presión.
La respuesta de Spring Boot es la configuración externalizada: los valores viven en application.yml (o variables de entorno, o un gestor de secretos, todos los cuales Spring combina según un orden de precedencia bien definido) y se vinculan a tu código en lugar de escribirse dentro de él. El mecanismo de vinculación más simple es @Value("\${providers.black-book.base-url}") sobre un solo campo o parámetro de constructor, y ya lo viste usado así para el método de fábrica @Bean en la lección 2. Funciona, pero escala mal: a medida que crece la cantidad de valores configurados, las anotaciones @Value se dispersan por cada clase que necesita una, no hay un lugar único donde ver qué configuración espera la app, y un error de tipeo en la clave de la propiedad simplemente produce null en silencio (o un fallo de arranque con un stack trace que no señala directamente el error) en lugar de un error de compilación.
@ConfigurationProperties es la mejor opción por defecto para cualquier cosa más allá de uno o dos valores sueltos: una sola data class, típicamente llamada algo como ProviderProperties, cuyos campos reflejan un bloque anidado de YAML, vinculada y validada una sola vez al arrancar. Para nuestros tres proveedores modelarías una clase ProvidersProperties con una propiedad anidada por cada proveedor -- blackBook, carfax, vis -- cada una con su propio baseUrl, apiKey y timeout. El soporte de vinculación por constructor de Spring Boot 4 significa que esta clase puede ser una data class de Kotlin ordinaria con un val por propiedad y sin setters, sin constructor sin argumentos por defecto, y sin estado mutable poco amigable con la reflexión -- la misma disciplina de inmutabilidad que los beans inyectados por constructor, aplicada a la configuración.
Los perfiles son la forma en que la misma estructura de YAML produce valores distintos en distintos entornos. application.yml contiene ajustes comunes a todos los entornos; application-dev.yml, application-test.yml y application-prod.yml contienen las anulaciones específicas de cada uno, y Spring Boot elige qué archivo adicional superponer según el perfil activo (fijado mediante la propiedad spring.profiles.active, una variable de entorno, o un flag de arranque). En el escenario de este curso, dev podría apuntar providers.black-book.base-url a un sandbox provisto por el proveedor que devuelve respuestas prefabricadas y no cuesta nada llamar repetidamente; test podría apuntarlo a un servidor WireMock para que las pruebas de integración sean herméticas y rápidas; prod lo apunta al endpoint real de Black Book con una clave de API real y facturable. VehicleValuationService y BlackBookClient nunca ven nada de esta ramificación -- reciben una instancia de ProvidersProperties y usan los valores que contenga, sin saber que el valor vino de un archivo distinto según qué perfil se activó.
El mismo patrón aplica a la persistencia políglota de este escenario, y es exactamente ahí donde el incidente-esperando-a-pasar se vuelve concreto. La cadena de conexión de Postgres, el host y puerto de Redis, y el URI de MongoDB difieren entre la laptop de un desarrollador, un pipeline de CI corriendo Testcontainers, y el clúster de producción -- y todos pertenecen a YAML específico de perfil (o, para secretos genuinos como contraseñas y claves de API, a variables de entorno o a un gestor de secretos que el YAML simplemente referencia mediante un placeholder como ${DB_PASSWORD}), nunca escritos como literal en un archivo .kt. Una URL de base de datos de producción hardcodeada que silenciosamente también se usa cuando alguien corre la app localmente contra datos de prueba es exactamente el tipo de error que se ve bien en cada pull request y luego borra filas reales en la primera corrida local de alguien.
Boot 4 afina todo esto sin cambiar el modelo mental: el procesamiento de metadatos de configuración se ha movido más hacia el pipeline AOT (Ahead-of-Time), lo que significa que herramientas como el autocompletado de tu IDE para claves de propiedades en application.yml, y la validación de que los valores vinculados de una clase @ConfigurationProperties están bien formados, pueden ocurrir con menos reflexión en tiempo de ejecución que en versiones anteriores -- parte del mismo impulso hacia el tiempo de arranque y las imágenes nativas discutido en la lección 2. Desde tu lado, sigues escribiendo una data class anotada @ConfigurationProperties(prefix = "providers"), sigues habilitándola con @EnableConfigurationProperties o escaneo de componentes, y sigues escribiendo un archivo YAML por perfil; Boot 4 simplemente te lleva ahí más rápido y valida antes.
El hilo conductor de las cinco lecciones de este módulo es la misma idea vista desde cinco ángulos: mantén cada preocupación en exactamente un lugar, y deja que las piezas se encuentren solo en fronteras limpias y explícitas. Las capas evitan que HTTP, lógica de negocio y acceso a datos se enreden. La inyección por constructor mantiene visibles e intercambiables las dependencias de una clase. Suspend evita que el I/O bloquee silenciosamente un recurso compartido. El recorrido de la valuación mostró a los tres cooperando en una sola solicitud. Y la configuración externalizada y por perfiles mantiene los valores específicos de cada entorno fuera del código que no debería necesitar conocerlos. El Módulo 2 continúa desde aquí y profundiza en el cliente HTTP que hay detrás de BlackBookClient, CarfaxClient y VisClient.
// DO NOT DO THIS.class BlackBookClientBad {private val baseUrl = "https://api.blackbook.com/v2"private val apiKey = "sk_live_4f9a2b7c1e8d" // a real key, committed to git history foreverprivate val dbUrl = "jdbc:postgresql://prod-db.internal:5432/valuations"}
The anti-pattern this lesson exists to prevent: secrets and environment-specific values typed directly into source. Requires nothing to run, but is shown only as a counter-example, so it does not run in-browser.
@ConfigurationProperties(prefix = "providers")data class ProvidersProperties(val blackBook: ProviderConfig,val carfax: ProviderConfig,val vis: ProviderConfig,) {data class ProviderConfig(val baseUrl: String,val apiKey: String,val timeoutMs: Long = 5_000,)}@Configuration@EnableConfigurationProperties(ProvidersProperties::class)class ProviderConfigConfiguration
A typed, immutable @ConfigurationProperties class bound from YAML, replacing scattered @Value fields. Requires Spring Boot's configuration-binding machinery to construct, so it does not run in-browser.
# application.yml (shared across all profiles)providers:black-book:timeout-ms: 5000carfax:timeout-ms: 5000vis:timeout-ms: 5000# application-dev.yml (active when spring.profiles.active=dev)providers:black-book:base-url: https://sandbox.blackbook.example.comapi-key: ${BLACK_BOOK_DEV_KEY}# application-prod.yml (active when spring.profiles.active=prod)providers:black-book:base-url: https://api.blackbook.com/v2api-key: ${BLACK_BOOK_PROD_KEY} # resolved from a secrets manager at deploy time
The matching application.yml layers: shared defaults plus a dev override that points at a sandbox instead of the real vendor. Requires Spring Boot's YAML loading and profile activation, so it does not run in-browser.
🧠 Comprueba tu comprensión
0/1 · 0/1 answered1. A developer needs the app to call a sandbox VIS endpoint on their laptop but the real VIS endpoint in production, without maintaining two copies of BlackBookClient or VisClient. What is the correct Spring Boot mechanism, and why does it satisfy the requirement without touching provider client code?