Saltar al contenido principal

TP5.3 - Configuración por Perfiles

Objetivos​

Al final de este TP, usted será capaz de:

  1. ✅ Crear perfiles para dev, test y prod
  2. ✅ Crear Beans condicionales según el perfil
  3. ✅ Utilizar archivos application-\{profile\}.properties
  4. ✅ Activar un perfil y probar

Duración estimada: 1h30


Prerrequisitos​

  • ✅ Capítulo 4 del Módulo 5 completado
  • ✅ TP5.1 y TP5.2 completados
  • ✅ Maven 3.9+ y Java 17+

Preparación: Nuevo Proyecto​

Crear el Proyecto​

Vía Spring Initializr:

  • Project: Maven
  • Spring Boot: 3.2.0
  • Artifact: profiles-demo
  • Dependencies: Spring Web, Spring Data JPA, H2 Database, PostgreSQL Driver

Estructura:

src/main/java/com/example/profiles/
├── ProfilesDemoApplication.java
├── service/
│ ├── EmailService.java
│ ├── FakeEmailService.java
│ └── SmtpEmailService.java
├── config/
│ ├── DevConfig.java
│ ├── ProdConfig.java
│ └── DatabaseConfig.java
└── controller/
└── TestController.java

src/main/resources/
├── application.properties
├── application-dev.properties
├── application-test.properties
└── application-prod.properties

Ejercicio 1: Perfiles para Servicios de Email​

Objetivo​

Crear 2 implementaciones de EmailService: una falsa para dev, una real para prod.

Paso 1.1: Crear la Interfaz​

Archivo: service/EmailService.java

package com.example.profiles.service;

public interface EmailService {
void send(String to, String subject, String body);
}

Paso 1.2: Implementación DEV (Fake)​

Archivo: service/FakeEmailService.java

package com.example.profiles.service;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

@Service
@Profile("dev") // ← Activo SOLO en dev
public class FakeEmailService implements EmailService {

@Override
public void send(String to, String subject, String body) {
System.out.println("========== FAKE EMAIL ==========");
System.out.println("To: " + to);
System.out.println("Subject: " + subject);
System.out.println("Body: " + body);
System.out.println("================================");
System.out.println("(Email simulado - no enviado realmente)");
}
}

Puntos clave:

  • @Profile("dev") → Bean creado solo en perfil dev
  • Email simulado → No se envía realmente

Paso 1.3: Implementación PROD (Real)​

Archivo: service/SmtpEmailService.java

package com.example.profiles.service;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

@Service
@Profile("prod") // ← Activo SOLO en prod
public class SmtpEmailService implements EmailService {

@Override
public void send(String to, String subject, String body) {
System.out.println("========== SMTP EMAIL ==========");
System.out.println("Conexión al servidor SMTP...");
System.out.println("Autenticación...");
System.out.println("Enviando a: " + to);
System.out.println("Asunto: " + subject);
System.out.println("================================");
System.out.println("(Email enviado realmente)");

// En producción, aquí usaría JavaMail:
// Session session = Session.getInstance(properties, authenticator);
// MimeMessage message = new MimeMessage(session);
// Transport.send(message);
}
}

Paso 1.4: Crear el Controller de Prueba​

Archivo: controller/TestController.java

package com.example.profiles.controller;

import com.example.profiles.service.EmailService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/test")
public class TestController {

private final EmailService emailService;

@Value("${spring.profiles.active:default}")
private String activeProfile;

public TestController(EmailService emailService) {
this.emailService = emailService;
}

@GetMapping("/email")
public String testEmail() {
emailService.send(
"user@example.com",
"Test Email",
"Este es un email de prueba"
);

return "✅ Email enviado vía perfil: " + activeProfile +
" - Ver consola";
}

@GetMapping("/profile")
public String getActiveProfile() {
return "Perfil activo: " + activeProfile;
}
}

Paso 1.5: Activar el Perfil DEV​

Archivo: src/main/resources/application.properties

# Perfil activo
spring.profiles.active=dev

# Nombre de la aplicación
spring.application.name=Profiles Demo

Paso 1.6: Probar en DEV​

Inicie:

mvn clean install -DskipTests spring-boot:run

Consola al iniciar:

The following 1 profile is active: "dev"

Pruebe:

http://localhost:8080/api/test/email

Consola:

========== FAKE EMAIL ==========
To: user@example.com
Subject: Test Email
Body: Este es un email de prueba
================================
(Email simulado - no enviado realmente)

✅ En DEV: ¡Email simulado!


Paso 1.7: Probar en PROD​

Modifique application.properties:

spring.profiles.active=prod

Reinicie y pruebe:

Consola:

The following 1 profile is active: "prod"

========== SMTP EMAIL ==========
Conexión al servidor SMTP...
Autenticación...
Enviando a: user@example.com
Asunto: Test Email
================================
(Email enviado realmente)

✅ En PROD: ¡Email real!


Ejercicio 2: Configuración de Base de Datos por Perfil​

Objetivo​

Utilizar H2 en dev/test, PostgreSQL en prod.

Paso 2.1: Archivo application.properties (Común)​

Archivo: src/main/resources/application.properties

# Perfil activo (cambie según necesidad)
spring.profiles.active=dev

# Configuración común
spring.application.name=Profiles Demo
server.port=8080

# JPA común
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

Paso 2.2: Configuración DEV (H2)​

Archivo: src/main/resources/application-dev.properties

# Base de datos H2 en memoria
spring.datasource.url=jdbc:h2:mem:devdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

# Consola H2
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

# Hibernate
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

# Logs
logging.level.root=DEBUG
logging.level.com.example.profiles=DEBUG

Paso 2.3: Configuración TEST (H2 con scripts)​

Archivo: src/main/resources/application-test.properties

# Base de datos H2 en memoria para pruebas
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

# Hibernate
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

# Logs mínimos para pruebas
logging.level.root=WARN
logging.level.com.example.profiles=INFO

Paso 2.4: Configuración PROD (PostgreSQL)​

Archivo: src/main/resources/application-prod.properties

# Base de datos PostgreSQL
spring.datasource.url=jdbc:postgresql://localhost:5432/proddb
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=admin
spring.datasource.password=admin123

# Hibernate
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect

# Logs mínimos
logging.level.root=WARN
logging.level.com.example.profiles=INFO

# Seguridad (en producción, use variables de entorno!)
# spring.datasource.username=${DB_USERNAME}
# spring.datasource.password=${DB_PASSWORD}

Paso 2.5: Crear una Entidad de Prueba​

Archivo: model/User.java

package com.example.profiles.model;

import jakarta.persistence.*;

@Entity
@Table(name = "users")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private String email;

// Constructores
public User() {}

public User(String name, String email) {
this.name = name;
this.email = email;
}

// Getters y Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }

public String getName() { return name; }
public void setName(String name) { this.name = name; }

public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}

Paso 2.6: Probar las Diferentes BDs​

En DEV (H2):

spring.profiles.active=dev

Inicie y verifique la consola:

H2 console available at '/h2-console'
Hibernate: create table users (...)
Database: H2

Acceda a la consola H2:

http://localhost:8080/h2-console
JDBC URL: jdbc:h2:mem:devdb
Username: sa
Password: (vacío)

En PROD (PostgreSQL):

  1. Cree primero la base:
CREATE DATABASE proddb;
  1. Modifique el perfil:
spring.profiles.active=prod
  1. Inicie:
Database: PostgreSQL
Hibernate: validate schema

Ejercicio 3: Beans de Configuración por Perfil​

Objetivo​

Crear Beans diferentes según el entorno.

Paso 3.1: Configuración DEV​

Archivo: config/DevConfig.java

package com.example.profiles.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
@Profile("dev") // ← Toda la clase activa en dev
public class DevConfig {

@Bean
public String environmentName() {
return "DEVELOPMENT";
}

@Bean
public boolean debugMode() {
return true;
}

@Bean
public String apiUrl() {
return "http://localhost:8080/api";
}
}

Paso 3.2: Configuración PROD​

Archivo: config/ProdConfig.java

package com.example.profiles.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
@Profile("prod") // ← Toda la clase activa en prod
public class ProdConfig {

@Bean
public String environmentName() {
return "PRODUCTION";
}

@Bean
public boolean debugMode() {
return false;
}

@Bean
public String apiUrl() {
return "https://api.monsite.com/api";
}
}

Paso 3.3: Probar​

Agregue en TestController.java:

@RestController
@RequestMapping("/api/test")
public class TestController {

private final EmailService emailService;
private final String environmentName;
private final boolean debugMode;
private final String apiUrl;

@Value("${spring.profiles.active:default}")
private String activeProfile;

public TestController(
EmailService emailService,
String environmentName,
boolean debugMode,
String apiUrl
) {
this.emailService = emailService;
this.environmentName = environmentName;
this.debugMode = debugMode;
this.apiUrl = apiUrl;
}

// ... otros métodos ...

@GetMapping("/config")
public String getConfig() {
return String.format(
"Entorno: %s<br>" +
"Perfil activo: %s<br>" +
"Modo debug: %s<br>" +
"API URL: %s",
environmentName,
activeProfile,
debugMode,
apiUrl
);
}
}

En DEV:

Entorno: DEVELOPMENT
Perfil activo: dev
Modo debug: true
API URL: http://localhost:8080/api

En PROD:

Entorno: PRODUCTION
Perfil activo: prod
Modo debug: false
API URL: https://api.monsite.com/api

Ejercicio 4: Perfiles Múltiples y @Profile Compuesto​

Objetivo​

Activar múltiples perfiles y crear condiciones complejas.

Paso 4.1: Bean para Varios Perfiles​

Agregue en config/DevConfig.java:

@Bean
@Profile({"dev", "test"}) // ← Activo en dev O test
public String sharedDevTestBean() {
return "Bean compartido entre dev y test";
}

Paso 4.2: Bean Condicional Complejo​

@Bean
@Profile("!prod") // ← Activo en TODOS los perfiles EXCEPTO prod
public String nonProdBean() {
return "Bean para no-producción (dev, test, etc.)";
}

Paso 4.3: Activar Múltiples Perfiles​

Método 1: application.properties

spring.profiles.active=dev,mock-data,debug

Método 2: Línea de comandos

mvn spring-boot:run -Dspring-boot.run.profiles=dev,mock-data

Método 3: Variable de entorno

export SPRING_PROFILES_ACTIVE=prod,monitoring
mvn spring-boot:run

Ejercicio 5: Configuración Completa Multi-Entornos​

Objetivo​

Crear una configuración lista para producción.

Paso 5.1: Variables de Entorno en PROD​

Archivo: application-prod.properties

# Base de datos (desde variables de entorno)
spring.datasource.url=${DATABASE_URL:jdbc:postgresql://localhost:5432/proddb}
spring.datasource.username=${DB_USERNAME:admin}
spring.datasource.password=${DB_PASSWORD}

# SMTP (desde variables de entorno)
spring.mail.host=${SMTP_HOST:smtp.gmail.com}
spring.mail.port=${SMTP_PORT:587}
spring.mail.username=${SMTP_USERNAME}
spring.mail.password=${SMTP_PASSWORD}

# API Keys
app.external-api.key=${EXTERNAL_API_KEY}
app.external-api.url=${EXTERNAL_API_URL:https://api.service.com}

Sintaxis:

  • $\{VAR_NAME\} → Variable obligatoria (error si falta)
  • ${VAR_NAME:default} → Variable con valor por defecto

Paso 5.2: Desplegar con Variables​

Linux/Mac:

export DATABASE_URL="jdbc:postgresql://prod-server:5432/myapp"
export DB_USERNAME="app_user"
export DB_PASSWORD="secret123"
export SMTP_USERNAME="noreply@myapp.com"
export SMTP_PASSWORD="smtp_secret"

mvn spring-boot:run

Windows:

$env:DATABASE_URL="jdbc:postgresql://prod-server:5432/myapp"
$env:DB_USERNAME="app_user"
# ...

mvn spring-boot:run

Resumen​

Estructura de Archivos​

application.properties          ← Configuración común
application-dev.properties ← Específico dev
application-test.properties ← Específico test
application-prod.properties ← Específico prod

Orden de prioridad:

1. application-{profile}.properties (específico)
2. application.properties (común)
3. Variables de entorno
4. Argumentos de línea de comandos

Beans por Perfil​

// Bean para UN perfil
@Profile("dev")

// Bean para VARIOS perfiles
@Profile({"dev", "test"})

// Bean para TODOS excepto uno
@Profile("!prod")

// Clase entera
@Configuration
@Profile("dev")
public class DevConfig { }

Activar un Perfil​

1. application.properties:

spring.profiles.active=dev

2. Línea de comandos:

--spring.profiles.active=prod

3. Variable de entorno:

export SPRING_PROFILES_ACTIVE=prod

4. IDE (VS Code):

"spring.profiles.active": "dev"

Autoevaluación​

  1. ¿Cuál es la diferencia entre application.properties y application-dev.properties?
  2. ¿Cómo crear un Bean activo solo en dev?
  3. ¿Cómo activar varios perfiles simultáneamente?
  4. ¿Por qué usar variables de entorno en producción?
  5. ¿Cuál es la sintaxis para una variable con valor por defecto?

Challenge Bonus 🎯​

Cree una configuración completa para una aplicación real:

  1. 3 perfiles: dev, test, prod

  2. Dev:

    • H2 en memoria
    • Email fake
    • Logs DEBUG
    • Sin seguridad
  3. Test:

    • H2 con scripts
    • Email mock
    • Logs INFO
  4. Prod:

    • PostgreSQL
    • Email SMTP real
    • Logs WARN
    • Seguridad activada
    • Variables de entorno

Bonus: Agregue un perfil docker para Docker Compose


¡Felicitaciones! 🎉​

¡Ha completado el Módulo 5!

Ahora domina:

  • ✅ Inversión de Control (IoC)
  • ✅ Inyección de Dependencias (DI)
  • ✅ Beans y su ciclo de vida
  • ✅ Ámbitos (Singleton, Prototype)
  • ✅ Configuración por perfiles
  • ✅ Multi-entornos (dev/test/prod)

¡Está listo para conceptos avanzados!


⬅️ TP anterior: Beans y Ámbitos

⬅️ Volver al Módulo 5

➡️ Módulo siguiente: Spring Data JPA Avanzado