Type something to search...
El Arte de Conectar: Forjando un DataSource Dinámico en Spring Boot

El Arte de Conectar: Forjando un DataSource Dinámico en Spring Boot

En el vertiginoso universo del desarrollo de software, donde la seguridad es un pilar innegociable y la agilidad es la moneda de cambio, nos enfrentamos a desafíos que van más allá de la simple lógica de negocio. Uno de los más recurrentes y críticos es la gestión de las conexiones a bases de datos. ¿Cómo construimos un sistema que no solo proteja sus credenciales como si fueran las joyas de la corona, sino que también sea lo suficientemente flexible para “hablar” con distintos motores de bases de datos sin despeinarse?

La respuesta no reside en un truco de magia, sino en la elegancia de la buena arquitectura. Este artículo te llevará en un viaje a través de una solución sofisticada en Spring Boot, donde desvelaremos cómo obtener credenciales de forma segura desde un gestor de secretos y, a la vez, emplear el ingenioso patrón de diseño Strategy para crear DataSources que se adaptan dinámicamente a su entorno. Prepárate para transformar una tarea mundana en una pieza de ingeniería de software.


El Mapa de la Arquitectura

Toda gran solución comienza con un plan. Antes de sumergirnos en el código, visualicemos nuestro ecosistema. No se trata de un monolito de lógica enrevesada, sino de un conjunto de componentes especializados que colaboran en perfecta armonía, como una orquesta bien afinada.

Antes de desgranar el código, un buen mapa visual nos ayudará a navegar la solución. El siguiente diagrama de clases ilustra las relaciones y dependencias entre nuestros componentes clave. Observa cómo las fábricas (Factory) orquestan la creación de objetos, mientras que la interfaz DatabaseEngineStrategy actúa como un contrato para sus diferentes implementaciones.

image

codigo mermaid

        classDiagram
            class DatabaseConnectionPool {
                -DatabaseEngineFactory engineFactory
                +dataSourceFromSecret(String, DatabaseConnectionPropertiesFactory) DataSource
            }
            class DatabaseConnectionPropertiesFactory {
                -GenericManager secretsManager
                +getDatabaseConnectionProperties(String) DatabaseConnectionProperties
            }
            class DatabaseConnectionProperties {
                -String dbname
                -String username
                -String password
                -String engine
                ...
            }
            class DatabaseEngineFactory {
                -Map~String, DatabaseEngineStrategy~ strategies
                +getStrategy(String) DatabaseEngineStrategy
            }
            class DatabaseEngineStrategy {
                <>
                +getName() String
                +buildJdbcUrl(...) String
                +getValidationQuery() String
            }
            class PostgresqlStrategy {
                +getName() String
                ...
            }
            class MysqlStrategy {
                +getName() String
                ...
            }
            DatabaseConnectionPool ..> DatabaseConnectionPropertiesFactory : uses
            DatabaseConnectionPool ..> DatabaseEngineFactory : uses
            DatabaseConnectionPropertiesFactory ..> DatabaseConnectionProperties : creates
            DatabaseEngineFactory ..> DatabaseEngineStrategy : uses
            PostgresqlStrategy --|> DatabaseEngineStrategy : implements
            MysqlStrategy --|> DatabaseEngineStrategy : implements
  

El flujo de nuestra sinfonía es el siguiente:

  1. El director de orquesta (DatabaseConnectionPool) necesita la partitura: las propiedades de conexión. Para ello, acude a nuestro “bibliotecario” (DatabaseConnectionPropertiesFactory).
  2. El bibliotecario viaja a una bóveda segura (el gestor de secretos) para recuperar la partitura (DatabaseConnectionProperties).
  3. La partitura indica qué tipo de instrumento principal se necesita (el engine, ej. “postgres”). Con esta clave, el director consulta a un “maestro de instrumentos” (DatabaseEngineFactory).
  4. Este maestro selecciona al músico virtuoso adecuado (DatabaseEngineStrategy) para ese instrumento.
  5. El músico, con su maestría, interpreta la partitura y genera la melodía única: la URL JDBC.
  6. Finalmente, con todos los elementos en su lugar, el director da la señal y se forma la orquesta completa: un pool de conexiones HikariDataSource listo para actuar.

Ahora, conozcamos a cada uno de los protagonistas de esta obra.


1. El Molde de Nuestros Secretos: DatabaseConnectionProperties

Todo sistema necesita un lenguaje común. Antes de poder manejar nuestros secretos, debemos definir su forma. Aquí es donde entra en juego DatabaseConnectionProperties, nuestro DTO (Data Transfer Object). No es más que el plano que define qué información esperamos encontrar en esa bóveda segura. Con la ayuda de Lombok, su definición es pura simpleza y elegancia.

package com.app247.mecrblog.jpa.config.datasource;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class DatabaseConnectionProperties {

    private String dbname;
    private String schema;
    private String username;
    private String password;
    private String host;
    private Integer port;
    private String engine; // La pieza clave que define nuestra estrategia.
    private String dbClusterIdentifier;

}

Esta clase es nuestro contrato: cualquier secreto que recuperemos deberá poder amoldarse a esta estructura.


2. El Guardián de los Secretos: DatabaseConnectionPropertiesFactory

La misión de esta fábrica es simple pero crucial: aventurarse en el mundo exterior, dialogar con el gestor de secretos y volver con el botín, ya transformado en nuestro DatabaseConnectionProperties.

package com.app247.mecrblog.jpa.config.datasource;

import org.springframework.stereotype.Component;
// ... (otros imports)
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
@Component
public class DatabaseConnectionPropertiesFactory {

    private static final String DATABASE_SCHEMA = "schema";

    // Un detalle brillante: no depende de un cliente de AWS o Vault,
    // sino de nuestra propia interfaz 'GenericManager'. Pura abstracción.
    private final GenericManager secretsManager;

    public DatabaseConnectionProperties getDatabaseConnectionProperties(String key) throws SecretException {
        var props = secretsManager.getSecret(key, DatabaseConnectionProperties.class);
        props.setSchema(DATABASE_SCHEMA);

        log.info("Creando DataSource para el motor={} en host: {}...",
                   props.getEngine(), props.getHost());

        return props;
    }
}

La verdadera magia aquí es la dependencia de GenericManager. Esta interfaz es nuestro pasaporte universal, permitiéndonos cambiar de proveedor de secretos (de AWS a HashiCorp Vault, por ejemplo) con solo cambiar una implementación, sin que el resto de nuestra aplicación se inmute. Es el arte del desacoplamiento en su máxima expresión.


3. El Arte de la Poliglotía: Adaptabilidad con el Patrón Strategy

Aquí es donde la trama se pone interesante. ¿Qué sucede cuando nuestra aplicación necesita conversar fluidamente con PostgreSQL y, mañana, con MySQL? Podríamos caer en la tentación de un pantanoso bloque if-else o switch, un camino seguro hacia un código frágil y una deuda técnica creciente.

Pero nosotros elegimos un camino más elegante: el patrón Strategy.

El Contrato del Traductor: DatabaseEngineStrategy

Primero, definimos un contrato, una serie de reglas que cualquier “traductor” de dialectos de bases de datos debe seguir.

package com.app247.mecrblog.jpa.config.datasource;

public interface DatabaseEngineStrategy {
    // ¿Cómo te llamas? (ej: "postgres", "mysql")
    String getName();

    // ¿Cómo construyes una URL de conexión en tu idioma?
    String buildJdbcUrl(String host, int port, String dbname, String schema);

    // ¿Cuál es tu forma de verificar que estás vivo? (Validation Query)
    String getValidationQuery();
}

Los Especialistas en Dialectos

Con el contrato en mano, contratamos a nuestros especialistas. Cada uno es un maestro en su propio idioma y se registra como un bean de Spring (@Component).

El experto en PostgreSQL:

package com.app247.mecrblog.jpa.config.datasource.strategy;

import org.springframework.stereotype.Component;
// ...

@Component
public class PostgresqlStrategy implements DatabaseEngineStrategy {
    @Override
    public String getName() { return "postgres"; }

    @Override
    public String buildJdbcUrl(String host, int port, String dbname, String schema) {
        return String.format("jdbc:postgresql://%s:%d/%s%s", host, port, dbname,
                ((schema != null) && (!schema.isEmpty())) ? ("?currentSchema=" + schema) : "");
    }

    @Override
    public String getValidationQuery() { return "SELECT 1"; }
}

El experto en MySQL:

package com.app247.mecrblog.jpa.config.datasource.strategy;

import org.springframework.stereotype.Component;
// ...

@Component
public class MysqlStrategy implements DatabaseEngineStrategy {
    @Override
    public String getName() { return "mysql"; }

    @Override
    public String buildJdbcUrl(String host, int port, String dbname, String schema) {
        return String.format("jdbc:mysql://%s:%d/%s?useSSL=false&serverTimezone=UTC", host, port, dbname);
    }

    @Override
    public String getValidationQuery() { return "SELECT 1"; }
}

Esta estructura es liberadora. ¿Necesitamos soportar Oracle mañana? Simplemente creamos un OracleStrategy sin tocar una sola línea del código existente. Nuestro sistema ha aprendido a crecer.


4. El Maestro de Ceremonias: DatabaseEngineFactory

Ya tenemos a nuestros músicos especialistas, pero necesitamos a alguien que sepa a quién llamar en cada momento. Ese es el rol de DatabaseEngineFactory, nuestro maestro de ceremonias.

package com.app247.mecrblog.jpa.config.datasource;

import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
// ...

@Slf4j
@Service
public class DatabaseEngineFactory {

    private final Map<String, DatabaseEngineStrategy> strategies;

    // Gracias a la magia de Spring, el constructor recibe un mapa con todos
    // nuestros especialistas (beans de DatabaseEngineStrategy) disponibles.
    public DatabaseEngineFactory(Map<String, DatabaseEngineStrategy> strategiesMap) {
        this.strategies = strategiesMap.values().stream()
                .collect(Collectors.toMap(DatabaseEngineStrategy::getName, Function.identity()));
        log.info("Motores de base de datos soportados: {}", this.strategies.keySet());
    }

    // Dada una clave ("postgres"), devuelve al especialista correcto.
    public DatabaseEngineStrategy getStrategy(String engineName) {
        DatabaseEngineStrategy strategy = strategies.get(engineName.toLowerCase());
        if (strategy == null) {
            throw new IllegalArgumentException("Motor de BD no soportado: " + engineName);
        }
        return strategy;
    }
}

Esta fábrica es un ejemplo sublime de cómo el framework Spring puede simplificar nuestro código. En lugar de registrar manualmente cada estrategia, Spring las descubre y nos las entrega listas para usar. La fábrica simplemente las organiza en un mapa para un acceso instantáneo.


5. La Gran Orquesta: Sincronizando Todo en DatabaseConnectionPool

Hemos llegado al acto final. Es hora de que el director suba al podio y una todas las piezas en una sinfonía funcional. La clase DatabaseConnectionPool es nuestro @Configuration principal, el lugar donde la magia realmente ocurre.

package com.app247.mecrblog.jpa.config.datasource;

import javax.sql.DataSource;
// ...
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
// ...

@Slf4j
@Configuration
@RequiredArgsConstructor
@Profile({ "local", "qa", "dev", "pdn" }) // Actuamos solo en los escenarios indicados
public class DatabaseConnectionPool {

    // ... (Constantes de configuración de HikariCP)

    private final DatabaseEngineFactory engineFactory;

    @Bean
    public DataSource dataSourceFromSecret(
            @Value("${aws.secrets.credentials.db-write}") String secretName,
            DatabaseConnectionPropertiesFactory connectionPropertiesFactory) throws SecretException {

        // Acto 1: El bibliotecario trae la partitura.
        var props = connectionPropertiesFactory.getDatabaseConnectionProperties(secretName);

        // Acto 2: El maestro de ceremonias elige al virtuoso.
        DatabaseEngineStrategy engine = engineFactory.getStrategy(props.getEngine());

        // Acto 3: El virtuoso crea la melodía (la URL JDBC).
        String jdbcUrl = engine.buildJdbcUrl(props.getHost(), props.getPort(), props.getDbname(), props.getSchema());
        log.info("Creando DataSource con URL: {}", jdbcUrl);

        // Gran final: Se forma la orquesta (el pool de conexiones).
        return buildHikariDataSource(jdbcUrl, props.getUsername(), props.getPassword(), engine);
    }

    private DataSource buildHikariDataSource(String jdbcUrl, String username, String password,
                                             DatabaseEngineStrategy engine) {
        var config = new HikariConfig();
        config.setJdbcUrl(jdbcUrl);
        config.setUsername(username);
        config.setPassword(password);
        config.setPoolName("jpa-" + engine.getName() + "-hikari-pool");
        config.setConnectionTestQuery(engine.getValidationQuery()); // Usamos la frase del especialista
        // ... (resto de la configuración del pool)

        return new HikariDataSource(config);
    }
}

El método dataSourceFromSecret es el corazón palpitante de nuestra aplicación. Orquesta la secuencia de llamadas de una manera tan limpia y declarativa que su lógica se lee casi como prosa.


Telón Final y Futuras Funciones

Lo que hemos creado es más que un simple configurador de DataSource. Es un testimonio de cómo los buenos principios de diseño pueden dar como resultado un sistema que respira:

  • Seguro: Las credenciales viven en su fortaleza, lejos de miradas indiscretas.
  • Adaptable: Es un políglota de bases de datos, listo para aprender nuevos dialectos en cualquier momento.
  • Robusto y Mantenible: Cada componente tiene su lugar y su propósito, haciendo que el sistema sea un placer de mantener y extender.

¿Y qué nos depara el futuro? Esta arquitectura no es un final, sino un punto de partida para nuevas aventuras:

  1. Mundos Paralelos: Extender la lógica para manejar réplicas de lectura, creando un DataSource para escritura y otro para lectura.
  2. Nuevos Talentos: Incorporar estrategias para Oracle, SQL Server o incluso bases de datos NoSQL con drivers JDBC.
  3. Inteligencia Dinámica: Hacer que la selección del schema sea tan dinámica como el resto de la configuración.

Hemos transformado un requisito técnico en una solución elegante, demostrando que el código, en sus mejores momentos, se acerca más al arte que a la ciencia.

Related Posts

Diseñando un Wrapper de Respuesta en Java con Funcionalidades de Optional y Gestión de Estado

Diseñando un Wrapper de Respuesta en Java con Funcionalidades de Optional y Gestión de Estado

En el desarrollo de aplicaciones Java, el manejo de respuestas a solicitudes —especialmente aquellas que involucran operaciones asincrónicas, procesamiento de datos o comunicación con servicios extern

Leer más
Una Propuesta para Estandarizar la Seguridad en APIs REST con Arquitectura Hexagonal y Spring Security

Una Propuesta para Estandarizar la Seguridad en APIs REST con Arquitectura Hexagonal y Spring Security

En el desarrollo de aplicaciones empresariales modernas, la seguridad es un pilar fundamental. Sin embargo, lograr una arquitectura de seguridad que sea reutilizable, desacoplada y, al mismo t

Leer más
Manejando el Caos: Una Guía Definitiva sobre Excepciones de Dominio 🎯

Manejando el Caos: Una Guía Definitiva sobre Excepciones de Dominio 🎯

En el desarrollo de software moderno, escribir código que funciona perfectamente en escenarios ideales es solo el primer paso. La verdadera fortaleza de un sistema se manifiesta cuando debe enfrentar

Leer más
El Arte del Contexto: Diseño Flexible en Arquitecturas DDD con Java y Spring Boot

El Arte del Contexto: Diseño Flexible en Arquitecturas DDD con Java y Spring Boot

En el universo del desarrollo de software empresarial, nos enfrentamos a un dilema constante: cómo manejar información transversal —ese rastro de datos vitales como IDs de correlación, información del

Leer más
El Arte de la Consistencia: Creando Respuestas API Predecibles en Spring MVC

El Arte de la Consistencia: Creando Respuestas API Predecibles en Spring MVC

El Arte de la Consistencia: Creando Respuestas API Predecibles en Spring MVC Imagina a un desarrollador frontend consumiendo tu API. En un endpoint, recibe un objeto JSON. En otro, una simple lista. S

Leer más
Convención de Nombres en Clases Java: Mejora de Legibilidad y Mantenibilidad

Convención de Nombres en Clases Java: Mejora de Legibilidad y Mantenibilidad

En proyectos de gran escala, identificar rápidamente el tipo de una clase facilita la comprensión del código, la colaboración y la depuración. Sin un esquema de nombres estandarizado, es común que los

Leer más
Estructuración de Carpetas en Proyectos de Software

Estructuración de Carpetas en Proyectos de Software

En proyectos de software de gran escala, la falta de una estructura de carpetas bien definida puede generar desorden, dificultando la mantenibilidad, escalabilidad y comprensión del código. Muchas vec

Leer más
Desarrollo de Software Implementando Gitflow

Desarrollo de Software Implementando Gitflow

Introducción El desarrollo de software requiere metodologías y flujos de trabajo que permitan un control eficiente del código fuente. Uno de los enfoques más utilizados para la gestión de versiones

Leer más
Transformando Colecciones con Java Streams: 15 Métodos Esenciales

Transformando Colecciones con Java Streams: 15 Métodos Esenciales

Introducción En el mundo de Java, trabajar con colecciones de datos solía ser sinónimo de bucles interminables, condicionales anidados y código repetitivo. Pero con la llegada de Java Streams

Leer más
Optimizando el Acceso a Datos: La Importancia de las Proyecciones JPA en Spring Boot

Optimizando el Acceso a Datos: La Importancia de las Proyecciones JPA en Spring Boot

El Costo Oculto de Traer Demasiada Información En el desarrollo de aplicaciones que interactúan con bases de datos, una tarea fundamental es la recuperación de datos. Al usar Object-Relational Map

Leer más
Implementando Trunk Based Development con Ramas de Vida Corta en Tu Equipo: Una Guía Completa

Implementando Trunk Based Development con Ramas de Vida Corta en Tu Equipo: Una Guía Completa

En el ámbito del desarrollo de software, la elección de una estrategia de versionamiento eficiente y estable es fundamental para el éxito de un equipo. El Trunk Based Development (TBD) tradicional, do

Leer más
Desmitificando Gradle: El Primer Paso para Automatizar tu Mundo Java

Desmitificando Gradle: El Primer Paso para Automatizar tu Mundo Java

En el vertiginoso universo del desarrollo de software, la eficiencia no es un lujo, es una necesidad. Dedicar tiempo a tareas repetitivas como compilar código, ejecutar pruebas, empaquetar la aplicaci

Leer más
Estándar de Codificación y Gestión de Errores en Arquitecturas de Microservicios

Estándar de Codificación y Gestión de Errores en Arquitecturas de Microservicios

En el ecosistema de aplicaciones web modernas, la arquitectura de microservicios distribuidos se ha consolidado como un paradigma dominante. Su flexibilidad, escalabilidad y resiliencia son innegables

Leer más
Del Dicho al Hecho: Generando Proyectos Java con Plantillas y FreeMarker

Del Dicho al Hecho: Generando Proyectos Java con Plantillas y FreeMarker

En el artículo anterior, alcanzamos un hito crucial: construimos un plugin binario funcional en Java, completo con su propia configuración y tarea. Nuestro plugin "saludador" demostró que dominamos la

Leer más
De Consumidor a Creador: Construyendo tu Primer Plugin Binario de Gradle

De Consumidor a Creador: Construyendo tu Primer Plugin Binario de Gradle

En nuestro artículo anterior, desmitificamos Gradle y sentamos las bases para entender su funcionamiento. Aprendimos a crear proyectos, ejecutar tareas y comprendimos el rol fundamental de los plugins

Leer más
Arquitectura DDD y Hexagonal: Construyendo Software para el Futuro

Arquitectura DDD y Hexagonal: Construyendo Software para el Futuro

En el dinámico mundo del desarrollo de software, la complejidad es el enemigo silencioso. Las aplicaciones crecen, los requisitos cambian y, sin una guía clara, el código puede convertirse rápidamente

Leer más