Adoptar una arquitectura en capas ofrece beneficios que se amplifican a medida que el sistema crece. En esta sección exploramos cómo la separación de responsabilidades, la facilidad de mantenimiento, la reutilización de componentes y la escalabilidad se materializan en prácticas concretas dentro de la plataforma de reservas de coworking iniciada en los temas anteriores.
Dividir el sistema en capas evita que las decisiones de presentación, negocio y persistencia se mezclen. Cada equipo puede enfocarse en su ámbito sin afectar el trabajo de los demás.
package com.example.coworking.api.controller;
import com.example.coworking.application.usecase.CancelarReserva;
public class CancelacionController {
private final CancelarReserva cancelarReserva;
public CancelacionController(CancelarReserva cancelarReserva) {
this.cancelarReserva = cancelarReserva;
}
public void cancelar(String idReserva) {
cancelarReserva.ejecutar(idReserva);
}
}
El controlador se limita a orquestar el caso de uso, mientras que las reglas de cancelación residen en la capa de aplicación.
Una vez aisladas las responsabilidades, cada capa puede someterse a estrategias de prueba específicas. Esto reduce el tiempo de diagnóstico y permite que las mejoras evolucionen sin romper el sistema completo.
var repositorio = new ReservaRepositorioEnMemoria();
var calendario = new CalendarioSalaStub();
var notificaciones = new ServicioNotificacionMock();
var reservarSala = new ReservarSala(repositorio, calendario, notificaciones);
Reserva reserva = reservarSala.ejecutar(command);
assertTrue(repositorio.existe(reserva.id()));
verify(notificaciones).enviarConfirmacion(reserva);
Las pruebas se concentran en cada capa, lo que acelera regresiones y otorga confianza al refactorizar.
Los puertos del dominio y los adaptadores desacoplados permiten reutilizar piezas en nuevos contextos. El mismo caso de uso puede alimentar diversas interfaces, mientras que los adaptadores se combinan según el despliegue.
public interface ServicioNotificacion {
void enviarConfirmacion(Reserva reserva);
}
public class ServicioNotificacionEmail implements ServicioNotificacion {
// Implementación con SMTP corporativo
}
public class ServicioNotificacionSlack implements ServicioNotificacion {
// Implementación con webhook de Slack
}
El caso de uso no cambia cuando se agrega un nuevo canal de notificación, solo se registra otra implementación.
Al mantener límites limpios, cada capa puede escalar o reemplazarse con mínimo impacto. La infraestructura puede migrar de JDBC a un servicio administrado sin modificar la capa de aplicación.
public class ReservaRepositorioDynamo implements ReservaRepositorio {
private final DynamoDbClient client;
public ReservaRepositorioDynamo(DynamoDbClient client) {
this.client = client;
}
@Override
public void guardar(Reserva reserva) {
client.putItem(mapper.toItem(reserva));
}
}
El cambio de motor de persistencia no afecta al caso de uso ni a la interfaz de usuario, solo a la implementación del puerto.
Las ventajas de la arquitectura en capas se materializan cuando los equipos las adoptan de forma disciplinada. En los siguientes temas estudiaremos las limitaciones y cómo evolucionar hacia estilos complementarios para resolver los retos que surgen en arquitecturas distribuidas.