La arquitectura en capas ofrece una estructura sólida, pero no está exenta de riesgos. Mal aplicada puede generar cuellos de botella, acoplamientos complejos y dificultades al escalar en entornos distribuidos. En esta sección revisamos los errores más frecuentes y las señales que indican que conviene evolucionar hacia estilos más flexibles.
Uno de los problemas recurrentes es la dependencia cruzada entre capas que deberían ser independientes. Cuando la capa de presentación accede directamente a la base de datos o la capa de persistencia conoce detalles de la interfaz, el modelo pierde coherencia.
Un indicador claro es la presencia de imports cíclicos o paquetes que dependen mutuamente. La siguiente situación muestra el problema y su refactor:
// Problema: la capa de persistencia importa clases de la API
package com.example.coworking.infrastructure.persistence;
import com.example.coworking.api.dto.ReservaRequest; // Dependencia indeseada
public class ReservaRepositorioJdbc {
public void guardar(ReservaRequest request) { /* ... */ }
}
// Corrección: utilizar el modelo de dominio
package com.example.coworking.infrastructure.persistence;
import com.example.coworking.domain.model.Reserva;
public class ReservaRepositorioJdbc {
public void guardar(Reserva reserva) { /* ... */ }
}
Agregar capas sin un propósito claro puede introducir latencias innecesarias. Cada paso adicional implica conversión de datos, validaciones y potenciales viajes a la red.
Es recomendable medir tiempos con herramientas de profiling y simplificar los pasos que no agregan valor. En algunos casos una capa puede volverse un simple adaptador fino:
public class ConsultaDisponibilidadService {
private final CalendarioSala calendario;
public ConsultaDisponibilidadService(CalendarioSala calendario) {
this.calendario = calendario;
}
public DisponibilidadDTO verificar(String salaId, LocalDateTime desde, LocalDateTime hasta) {
// Delegación directa: no agrega reglas, solo transforma datos.
// Si la conversión es trivial, conviene absorberla en la capa superior.
return DisponibilidadDTO.from(calendario.comprobar(salaId, desde, hasta));
}
}
En entornos distribuidos, mantener sincronizadas las capas se vuelve más complejo. Los casos de uso pueden necesitar datos de múltiples microservicios y la latencia en red agrava el problema.
Una solución común es introducir un service mesh y centralizar la trazabilidad, pero también conviene revisar si la división de capas sigue aportando valor en ese contexto.
La arquitectura en capas es un excelente punto de partida, pero hay escenarios donde conviene adoptar estilos como la arquitectura hexagonal o los microservicios:
La migración no debe ser abrupta. Muchos equipos adoptan estrategias evolutivas:
public interface ReservaFacade {
ReservaDTO crear(ReservaCommand command);
}
// Adaptador interno inicial (monolito con capas)
public class ReservaFacadeMonolite implements ReservaFacade {
private final ReservarSala reservarSala;
// ...
}
// Adaptador remoto posterior (microservicio)
public class ReservaFacadeClient implements ReservaFacade {
private final HttpClient httpClient;
// ...
}
Arquitecturas como la arquitectura hexagonal permiten mantener el dominio estable mientras los adaptadores evolucionan.
Ser conscientes de las limitaciones ayuda a aplicar controles y planear estrategias de evolución. En el siguiente tema revisaremos buenas prácticas y patrones que refuerzan la arquitectura en capas, mitigando varios de los riesgos expuestos aquí.