Una implementación deficiente puede requerir reescrituras o parches que degraden el rendimiento. Una arquitectura robusta, en cambio, debería permitir agregar nuevos componentes como bloques de construcción, manteniendo la estabilidad general.
Este artículo muestra cómo enriquecer una interfaz de listado listarSolucionesPorPagina con cálculos de agregación de precios, siguiendo un enfoque de "múltiples pasos de consulta" que garantiza escalabilidad y rendimiento.
Versión 1.0: Interfaz optimizada con consulta en múltiples pasos
Antes de la extensión, la interfaz ya estaba optimizada para mostrar información de creadores (SolucionUsuario) e IDs de demandas relacionadas (RelacionSolucionDemanda) en listas paginadas de Solucion.
Se utilizaba un enfoque de tres pasos para evitar problemas N+1:
// SolucionServicio.java (V1.0)
@Transactional(readOnly = true)
public Page<SolucionDTO> listarSolucionesPorPagina(...) {
// Paso 1: Paginación de entidades principales
Page<Solucion> paginaEntidades = repositorioSolucion.findAll(spec, pageable);
List<Solucion> solucionesPagina = paginaEntidades.getContent();
List<Integer> idsSoluciones = ...;
// Paso 2: Carga por lotes de SolucionUsuario
repositorioSolucion.cargarConUsuarios(solucionesPagina);
// Paso 3: Carga por lotes de IDs de demandas relacionadas
List<RelacionSolucionDemanda> relaciones = repositorioRelacion.buscarPorIdsSoluciones(idsSoluciones);
Map<Integer, Set<Integer>> mapaSolucionDemandaIds = ...;
// Ensamblar DTOs en memoria...
}
Nuevo requisito: Incorporar cálculos de precios
El requisito adicional es mostrar precio por unidad y monto total para cada Solucion, calculados como la suma de (cantidad * precioGrupoCompraApp) de todos sus SolucionItem.
Enfoque incorrecto: Realizar cálculos individuales dentro del bucle de mapeo generaría un nuevo problema N+1.
Versión 2.0: Expansión modular mediante un cuarto paso
La solución preserva la arquitectura existente y añade el cálculo de precios como un componente adicional.
1. Nuevo método de repositorio para cálculo por lotes
// RepositorioSolucion.java
/**
* Calcula precios por unidad para un lote de IDs de soluciones.
*/
@Query("SELECT s.id, COALESCE(SUM(sp.precioGrupoCompraApp * si.cantidad), 0.0) " +
"FROM Solucion s " +
"LEFT JOIN s.items si " +
"LEFT JOIN si.producto sp " +
"WHERE s.id IN :idsSoluciones " +
"GROUP BY s.id")
List<Object[]> calcularPreciosPorLotes(@Param("idsSoluciones") List<Integer> idsSoluciones);
Detalles técnicos:
WHERE s.id IN :idsSoluciones: Calcula solo para las soluciones de la página actualGROUP BY s.id: Permite cálculos independientes por soluciónCOALESCE(..., 0.0): Maneja casos sin productos retornando 0
2. Integración en la capa de servicio
// SolucionServicio.java (V2.0)
@Transactional(readOnly = true)
public Page<SolucionDTO> listarSolucionesPorPagina(...) {
// Pasos 1-3: Inalterados...
// ==================== NUEVO PASO ====================
// Cálculo por lotes de precios para todas las soluciones
List<Object[]> resultadosPrecios = repositorioSolucion.calcularPreciosPorLotes(idsSoluciones);
Map<Integer, BigDecimal> mapaPrecios = resultadosPrecios.stream()
.collect(Collectors.toMap(
fila -> (Integer) fila[0],
fila -> (BigDecimal) fila[1]
));
// ====================================================
// Ensamblado extendido de DTOs
List<SolucionDTO> listaDTOs = solucionesPagina.stream()
.map(solucion -> {
SolucionDTO dto = convertirADTO(solucion);
// Lógica extendida
BigDecimal precioUnitario = mapaPrecios.getOrDefault(
solucion.getId(), BigDecimal.ZERO);
dto.setPrecioUnitario(precioUnitario);
dto.setMontoTotal(precioUnitario.multiply(
new BigDecimal(solucion.getCantidadConjuntos())));
return dto;
})
.collect(Collectors.toList());
return new PageImpl<>(listaDTOs, ...);
}
Resultado: Consultas SQL optimizadas y predecibles
El registro SQL muestra claramente el enfoque de cuatro pasos:
-- 1. Paginación principal
SELECT ... FROM solucion WHERE ... LIMIT ?
-- 2. Carga por lotes de usuarios
SELECT ... FROM solucion ... LEFT JOIN solucion_usuario ... WHERE solucion0_.id IN (...)
-- 3. Carga por lotes de relaciones de demanda
SELECT ... FROM relacion_solucion_demanda ... WHERE relacion.solucion_id IN (...)
-- 4. Cálculo de agregación de precios
SELECT s.id, COALESCE(SUM(...), 0.0) FROM solucion s ... WHERE s.id IN (...) GROUP BY s.id
Características clave:
- Interacciones controladas: De 3 a 4 consultas fijas, independientes del tamaño de página
- Responsabilidad única: Cada consulta maneja una tarea específica
- Rendimiento garantizado: Todas las consultas son por lotes (cláusula
IN)
Principios de arquitectura extensible
Esta evolución demuestra la eficacia del enfoque de múltiples pasos:
- Escalabliidad: Nuevos requisitos se integran como pasos adicionales sin reestructurar el código existente
- Alta cohesión y bajo acoplamiento: Cada paso se enfoca en una tarea específica, comunicándose mediante identificadores simples
- Rendimiento sostenible: La adición de pasos mantiene el número de interacciones con la base de datos constante y predecible