Configuración externa y gestión de entornos en microservicios con Spring Boot

Capítulo 8

Tiempo estimado de lectura: 9 minutos

+ Ejercicio

Conceptos clave: configuración externa en Spring Boot

En microservicios, la configuración debe poder cambiar entre entornos (local, dev, qa, prod) sin recompilar ni redeployar artefactos. Spring Boot ofrece un modelo de configuración basado en Property Sources (fuentes de propiedades), perfiles y una jerarquía de precedencia que permite combinar archivos, variables de entorno y parámetros de arranque.

Property Sources y jerarquía de precedencia

Spring Boot agrega propiedades desde múltiples fuentes y, cuando una clave se repite, gana la de mayor precedencia. En la práctica, esto permite definir valores por defecto en archivos y sobreescribirlos en despliegue con variables de entorno o argumentos.

  • Argumentos de línea de comandos: --server.port=8081
  • Variables de entorno: SERVER_PORT=8081
  • Propiedades del sistema: -Dserver.port=8081
  • Archivos de configuración: application.yml, application-{profile}.yml
  • Valores por defecto en el código (por ejemplo, en @Value o valores iniciales de un POJO)

Regla práctica: usa archivos para defaults y estructura; usa variables de entorno para sobreescrituras por entorno; usa secretos fuera del repositorio.

Perfiles (profiles) para segmentar por entorno

Los perfiles activan bloques de configuración específicos. Puedes activarlos con:

  • Variable de entorno: SPRING_PROFILES_ACTIVE=dev
  • Argumento: --spring.profiles.active=dev
  • En archivo: spring.profiles.active: dev (menos recomendable para despliegue)

Convención recomendada: local, dev, qa, prod. Evita perfiles “por persona” y prioriza perfiles “por capacidad” cuando aplique (por ejemplo, kafka, redis), combinables con el entorno: SPRING_PROFILES_ACTIVE=prod,kafka.

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

Estructura de archivos por entorno (YAML) sin duplicación excesiva

Patrón recomendado: define en application.yml los valores base (comunes) y en application-{profile}.yml solo lo que cambia. Ejemplo:

# application.yml (base, común a todos los entornos) spring:  application:    name: orders-service server:  port: 8080 app:  http:    connect-timeout: 2s    read-timeout: 3s  concurrency:    max-in-flight: 200  features:    enable-new-checkout: false datasource:  url: jdbc:postgresql://localhost:5432/orders  username: orders  password: ${DB_PASSWORD:changeme}
# application-dev.yml (solo diferencias) app:  features:    enable-new-checkout: true datasource:  url: jdbc:postgresql://dev-db:5432/orders
# application-prod.yml (solo diferencias) server:  port: 8080 app:  concurrency:    max-in-flight: 500 datasource:  url: jdbc:postgresql://prod-db:5432/orders

Observa el uso de placeholders: ${DB_PASSWORD:changeme} toma el valor de la variable de entorno DB_PASSWORD y, si no existe, usa un default (útil en local; en prod normalmente se exige que exista).

Importación de configuración (config tree / archivos externos)

Para mantener consistencia y evitar duplicación, es común centralizar “base” en un archivo externo montado en el contenedor/VM y que cada servicio lo importe. En Spring Boot puedes usar spring.config.import:

# application.yml spring:  config:    import: optional:file:/etc/mycompany/common.yml

Esto permite compartir convenciones (timeouts, límites, toggles) sin copiar/pegar entre repositorios. Mantén el archivo común libre de secretos.

Guía práctica paso a paso: parametrizar conexiones, timeouts, concurrencia y feature flags

Paso 1: define un namespace propio (evita mezclar con propiedades de Spring)

Usa un prefijo como app.* o company.* para propiedades de negocio/infra propias. Esto reduce colisiones y mejora la legibilidad.

Paso 2: modela la configuración con @ConfigurationProperties

En lugar de dispersar @Value, agrupa propiedades en clases tipadas. Ejemplo:

package com.example.orders.config; import java.time.Duration; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "app") public class AppProperties {   private Http http = new Http();   private Concurrency concurrency = new Concurrency();   private Features features = new Features();   public Http getHttp() { return http; }   public void setHttp(Http http) { this.http = http; }   public Concurrency getConcurrency() { return concurrency; }   public void setConcurrency(Concurrency concurrency) { this.concurrency = concurrency; }   public Features getFeatures() { return features; }   public void setFeatures(Features features) { this.features = features; }   public static class Http {     private Duration connectTimeout = Duration.ofSeconds(2);     private Duration readTimeout = Duration.ofSeconds(3);     public Duration getConnectTimeout() { return connectTimeout; }     public void setConnectTimeout(Duration connectTimeout) { this.connectTimeout = connectTimeout; }     public Duration getReadTimeout() { return readTimeout; }     public void setReadTimeout(Duration readTimeout) { this.readTimeout = readTimeout; }   }   public static class Concurrency {     private int maxInFlight = 200;     public int getMaxInFlight() { return maxInFlight; }     public void setMaxInFlight(int maxInFlight) { this.maxInFlight = maxInFlight; }   }   public static class Features {     private boolean enableNewCheckout = false;     public boolean isEnableNewCheckout() { return enableNewCheckout; }     public void setEnableNewCheckout(boolean enableNewCheckout) { this.enableNewCheckout = enableNewCheckout; }   } }

Registra el binding (según tu versión):

package com.example.orders; import com.example.orders.config.AppProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; @SpringBootApplication @EnableConfigurationProperties(AppProperties.class) public class OrdersApplication {   public static void main(String[] args) {     SpringApplication.run(OrdersApplication.class, args);   } }

Paso 3: aplica la configuración en componentes (ejemplos típicos)

Timeouts HTTP (ejemplo con RestTemplateBuilder):

import com.example.orders.config.AppProperties; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class HttpClientConfig {   @Bean   RestTemplate restTemplate(RestTemplateBuilder builder, AppProperties props) {     return builder       .setConnectTimeout(props.getHttp().getConnectTimeout())       .setReadTimeout(props.getHttp().getReadTimeout())       .build();   } }

Límites de concurrencia (ejemplo con un Semaphore para “in-flight requests”):

import java.util.concurrent.Semaphore; import com.example.orders.config.AppProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ConcurrencyConfig {   @Bean   Semaphore inFlightSemaphore(AppProperties props) {     return new Semaphore(props.getConcurrency().getMaxInFlight());   } }

Feature flags (ejemplo simple):

import com.example.orders.config.AppProperties; import org.springframework.stereotype.Service; @Service public class CheckoutService {   private final AppProperties props;   public CheckoutService(AppProperties props) { this.props = props; }   public String checkout() {     if (props.getFeatures().isEnableNewCheckout()) {       return "NEW";     }     return "LEGACY";   } }

Patrón recomendado para flags: nombres positivos (enableX), defaults seguros (false) y cambios por entorno mediante variables o archivos de perfil.

Variables de entorno: convenciones y mapeo

En contenedores y plataformas de orquestación, las variables de entorno son el mecanismo más común. Spring Boot mapea variables a propiedades usando equivalencias como:

  • APP_HTTP_CONNECT_TIMEOUT=2sapp.http.connect-timeout
  • APP_CONCURRENCY_MAX_IN_FLIGHT=300app.concurrency.max-in-flight
  • SPRING_DATASOURCE_URL=...spring.datasource.url

Recomendación: estandariza nombres en mayúsculas con guiones bajos para despliegue, pero mantén en YAML el formato con puntos y kebab-case.

Validación de configuración al arranque (fail fast)

En microservicios, es preferible fallar al inicio si la configuración es inválida (por ejemplo, timeouts negativos, límites de concurrencia en cero, URLs vacías). Esto evita comportamientos erráticos en runtime.

Opción A: Bean Validation en @ConfigurationProperties

Agrega validaciones con anotaciones y marca la clase con @Validated. Ejemplo:

import jakarta.validation.Valid; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.validation.annotation.Validated; import java.time.Duration; @Validated @ConfigurationProperties(prefix = "app") public class AppProperties {   @Valid   private Http http = new Http();   @Valid   private Concurrency concurrency = new Concurrency();   public Http getHttp() { return http; }   public void setHttp(Http http) { this.http = http; }   public Concurrency getConcurrency() { return concurrency; }   public void setConcurrency(Concurrency concurrency) { this.concurrency = concurrency; }   public static class Http {     @NotNull     private Duration connectTimeout;     @NotNull     private Duration readTimeout;     public Duration getConnectTimeout() { return connectTimeout; }     public void setConnectTimeout(Duration connectTimeout) { this.connectTimeout = connectTimeout; }     public Duration getReadTimeout() { return readTimeout; }     public void setReadTimeout(Duration readTimeout) { this.readTimeout = readTimeout; }   }   public static class Concurrency {     @Min(1)     private int maxInFlight;     public int getMaxInFlight() { return maxInFlight; }     public void setMaxInFlight(int maxInFlight) { this.maxInFlight = maxInFlight; }   } }

Para que falle temprano, asegúrate de tener el starter de validación en dependencias (spring-boot-starter-validation). Si una propiedad requerida falta o viola una restricción, el contexto no levantará.

Opción B: validación programática (reglas de negocio)

Para reglas más complejas (por ejemplo, readTimeout >= connectTimeout), valida en un bean que se ejecute al inicio:

import com.example.orders.config.AppProperties; import org.springframework.boot.ApplicationRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class StartupValidationConfig {   @Bean   ApplicationRunner validateConfig(AppProperties props) {     return args -> {       if (props.getHttp().getReadTimeout().compareTo(props.getHttp().getConnectTimeout()) < 0) {         throw new IllegalStateException("app.http.read-timeout debe ser >= app.http.connect-timeout");       }     };   } }

Patrón: lanza excepciones claras y accionables, indicando la clave exacta que debe corregirse.

Separación: configuración no sensible vs secretos

Qué considerar “secreto”

  • Contraseñas, tokens, API keys, client secrets
  • Certificados privados, claves de cifrado
  • URLs con credenciales embebidas

Patrones recomendados

  • No sensibles en repositorio: timeouts, tamaños de pool, límites, flags, endpoints sin credenciales.
  • Secretos fuera del repositorio: inyectados como variables de entorno, archivos montados (volúmenes) o proveedores de secretos.
  • Evita defaults inseguros en prod: para secretos, prefiere “sin default” y falla al arranque si faltan.

Ejemplo: exigir contraseña en entornos no locales:

# application.yml datasource:  password: ${DB_PASSWORD:} app:  env: ${APP_ENV:local}
import org.springframework.boot.ApplicationRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SecretsValidation {   @Bean   ApplicationRunner validateSecrets(org.springframework.core.env.Environment env) {     return args -> {       String appEnv = env.getProperty("app.env", "local");       String dbPassword = env.getProperty("DB_PASSWORD", "");       if (!"local".equals(appEnv) && (dbPassword == null || dbPassword.isBlank())) {         throw new IllegalStateException("Falta DB_PASSWORD para entorno " + appEnv);       }     };   } }

Consistencia entre entornos sin duplicación: patrones operativos

Matriz de configuración: base + overlays

Define una matriz clara de qué se configura dónde:

TipoUbicación recomendadaEjemplos
Defaults comunesapplication.ymltimeouts base, límites base, flags por defecto
Diferencias por entornoapplication-dev.yml, application-prod.ymlURLs de servicios, tamaños de concurrencia
Overwrites por despliegueVariables de entorno / argspuertos, toggles temporales, endpoints específicos
SecretosProveedor de secretos / env / archivos montadospasswords, tokens, keys

Convenciones de nombres y documentación mínima

  • Prefijo único: app.*
  • Unidades explícitas: usa Duration con sufijos (200ms, 2s) y tamaños con unidades cuando aplique.
  • Documenta en el repositorio un “catálogo” de propiedades (tabla) con: clave, tipo, default, rango válido, si es secreto.

Evitar “drift” entre entornos

  • Minimiza diferencias: cambia solo lo necesario (capacidad, endpoints, credenciales).
  • Reutiliza el mismo artefacto: el jar/imagen debe ser idéntico entre entornos; solo cambia la configuración.
  • Valida al arranque: si falta una propiedad crítica o está fuera de rango, el servicio no debe iniciar.

Ejemplo de catálogo de propiedades (plantilla)

ClaveTipoDefaultRango/ReglaSecreto
app.http.connect-timeoutDuration2s> 0No
app.http.read-timeoutDuration3s>= connect-timeoutNo
app.concurrency.max-in-flightint200>= 1No
app.features.enable-new-checkoutbooleanfalsen/aNo
DB_PASSWORDstring(vacío)requerido en no-local

Ahora responde el ejercicio sobre el contenido:

En un microservicio con Spring Boot, ¿qué práctica ayuda a reutilizar el mismo artefacto entre entornos (dev/qa/prod) sin recompilar ni redeployar, cambiando solo la configuración?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

Se recomienda un jar/imagen idéntico para todos los entornos y variar solo la configuración: application.yml para base, application-{profile}.yml para diferencias y variables/args para sobreescrituras por su mayor precedencia.

Siguiente capítulo

Pruebas automatizadas para microservicios con Spring Boot

Arrow Right Icon
Portada de libro electrónico gratuitaMicroservicios con Spring Boot desde Cero
67%

Microservicios con Spring Boot desde Cero

Nuevo curso

12 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.