Muchas veces cuando empezamos a construir una aplicación, lo primero que queremos es que funcione, no estamos pensando en dependencia inversion, ni en dominio rico, ni en si Uncle Bob estaría orgulloso de nosotros.
Estamos pensando en algo mucho más terrenal:
- que levante
- que guarde datos
- que responda un endpoint
- y que ojalá no explote en producción a la primera semana
Y ahí es donde entra una de las arquitecturas más conocidas, más enseñadas y más utilizadas por años: la arquitectura en capas.
Sí, la famosa, la que a muchos nos enseñaron en la universidad, la de:
- controller
- service
- repository
- database
La que casi todos hemos usado en algún momento, que muchos proyectos siguen usando hoy y que, seamos honestos, nos ha sacado el brete un montón de veces.
Pero también es la misma arquitectura que, cuando el sistema empieza a crecer, nos comienza a pasar factura.
Hoy quiero arrancar esta serie hablando justamente de esa arquitectura:
por qué nació, cómo funciona, qué resuelve, cuáles son sus ventajas, sus desventajas, y cómo se vería una mini demo en GitHub para entenderla bien.
El problema que intentaba resolver
Antes de que muchas aplicaciones adoptaran una estructura clara, era común encontrar sistemas donde todo estaba mezclado:
- reglas de negocio
- validaciones
- SQL
- lógica de interfaz
- manejo de requests
- acceso a datos
Todo medio revuelto en los mismos módulos, clases o archivos, normalmente cuando el sistema era pequeño, eso “funcionaba”, el problema aparecía cuando había que mantenerlo.
Porque una cosa es tener una app con 5 pantallas y 3 tablas.
Y otra muy distinta es tener una aplicación que ya empieza a crecer, donde varias personas le meten mano, y donde cada cambio empieza a tocar 14 lugares distintos aunque solo quiera cambiar una regla sencilla. (Mal recuerdo de los DTOs en Struts)
Entonces apareció una idea bastante lógica:
“Separémoslo por responsabilidades”.
Y así se volvió popular la arquitectura en capas.
Qué propone la arquitectura en capas?
La idea es sencilla: dividir la aplicación en bloques donde cada capa tenga una responsabilidad concreta.
Normalmente se ve algo así:
1. Presentation Layer
Es la capa que recibe las peticiones o interactúa con el usuario.
Ejemplo:
- controllers REST
- endpoints
- UI
- vistas
2. Business Layer
Aquí vive la lógica del negocio.
Ejemplo:
- validaciones
- reglas
- orquestación de procesos
- servicios
3. Data Access Layer
Se encarga de hablar con la base de datos.
Ejemplo:
- repositories
- DAOs
- queries
- persistencia
Y debajo de todo eso, obviamente, está la base de datos.
La idea suena bien, y de hecho sí fue una mejora importante respecto a tener todo mezclado.
Porque ahora ya no se tiene un controller haciendo SQL directamente, o una pantalla con reglas de negocio incrustadas por todo lado.
Ahora al menos había orden y eso, para la época, ya era un montón.
Cómo funciona a nivel de código
La mayoría de implementaciones en capas terminan teniendo un flujo parecido a este:
@RestController
@RequestMapping("/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping
public ResponseEntity<String> createOrder(@RequestBody CreateOrderRequest request) {
orderService.createOrder(request);
return ResponseEntity.ok("Orden creada");
}
}Luego la capa de negocio:
@Service
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public void createOrder(CreateOrderRequest request) {
if (request.getTotal() <= 0) {
throw new IllegalArgumentException("El total debe ser mayor a cero");
}
OrderEntity order = new OrderEntity();
order.setCustomerName(request.getCustomerName());
order.setTotal(request.getTotal());
orderRepository.save(order);
}
}Y finalmente la capa de acceso a datos:
@Repository
public interface OrderRepository extends JpaRepository<OrderEntity, Long> {
}Si lo vemos rápido, el flujo sería este:
Controller → Service → Repository → DB
Y listo, muy entendible, muy enseñable, muy implementable.
Por eso pegó tanto.
Qué resuelve esta arquitectura?
La arquitectura en capas resolvió varias cosas importantes:
1. Orden
- Ya no todo estaba mezclado.
- Cada cosa tenía su lugar.
2. Mantenibilidad básica
- Cuando se quería buscar lógica de negocio, solo se iba al service.
- Cuando se quería ver persistencia, solo se iba al repository.
3. Curva de aprendizaje simple
Es una arquitectura muy fácil de explicar a alguien que va empezando.
4. Buena adopción en frameworks empresariales
Spring, .NET y otros frameworks tradicionales se convirtieron en un estándar de facto, pero la arquitectura en sí no es mala. Durante años fue una solución razonable, pero el problema surge cuando muchos sistemas crecieron sobre ella sin cuestionar sus límites.
La ventaja más grande: su simplicidad
Si tuviera que resumir por qué esta arquitectura fue tan exitosa, sería por esto:
Es fácil de entender y fácil de implementar
No necesitas explicarle a alguien 14 conceptos nuevos para arrancar.
Le dices:
- aquí llegan las requests
- aquí va la lógica
- aquí se guarda en base de datos
Y listo, ya arranca.
Para equipos pequeños, sistemas internos, CRUD, backoffices o aplicaciones que no tienen una lógica de negocio muy compleja, esta arquitectura puede seguir funcionando perfectamente.
Y eso también hay que decirlo.
Porque a veces en tecnología nos encanta actuar como si todo lo anterior fuera basura solo porque salió algo nuevo… y no, tampoco así.
Es como la típica lucha entre seguir usando Oracle forms o migrar a Oracle APEX
El problema real: la dependencia siempre apunta hacia abajo
Aquí es donde empieza el asunto.
Aunque en papel la arquitectura en capas se ve ordenada, en la práctica suele traer una dependencia muy marcada:
- presentation depende de business
- business depende de data access
- data access depende de la base de datos y del framework
O sea, casi siempre la lógica de negocio termina dependiendo indirectamente de detalles técnicos.
Y ahí es donde se empieza a enredar todo.
Porque de repente tu “negocio” ya no es tan puro negocio.
Ahora está contaminado con cosas como:
- anotaciones del framework
- entidades JPA
- estructuras diseñadas por persistencia
- decisiones de base de datos
- lógica condicionada por cómo guardas los datos
Y eso provoca algo muy común:
Terminamos modelando el negocio alrededor de la base de datos, en lugar de modelar la base de datos alrededor del negocio.
Esa es una bronca grande.
Lo que normalmente empieza a pasar con el tiempo
Cuando el sistema crece, aparecen síntomas bastante conocidos:
1. Services gigantes
- Empiezan pequeños…
y terminan siendo una sodita de lógica mezclada. - Validan, transforman, consultan, persisten, llaman APIs, hacen logs, mandan correos, y probablemente también cocinan arroz.
2. Reglas de negocio anémicas
La lógica importante no vive en el dominio, sino regada en servicios.
3. Acoplamiento fuerte con infraestructura
Cambiar base de datos, framework o estrategia de persistencia ya no es tan transparente.
4. Testing más incómodo
Para probar lógica de negocio, muchas veces terminas arrastrando cosas que no deberían importar para esa prueba.
5. Dependencia mental hacia “lo de abajo”
Muchos equipos terminan pensando la aplicación desde la base de datos hacia arriba.
Y ahí es donde comenzamos a sentir que sí… que hay orden…
pero no necesariamente hay buen diseño.
Ventajas
Para resumirlo bonito y sin hacerle injusticia:
- Es simple de entender
- Es rápida de implementar
- Se adapta bien a aplicaciones CRUD
- Es ampliamente conocida por la industria
- Tiene mucho soporte en frameworks tradicionales
Desventajas
Y ahora el otro lado de la tortilla:
- La lógica de negocio suele terminar acoplada a persistencia
- La dirección de dependencias favorece a la infraestructura
- Escala mal cuando el dominio se vuelve complejo
- Los services tienden a crecer demasiado
- Puede dar una falsa sensación de buena arquitectura solo porque “está ordenada”
Y esa última duele, porque es cierta.
A veces creemos que tener paquetes llamados controller, service y repository ya significa que el sistema está bien diseñado, y no necesariamente.
A veces solo significa que el desorden está mejor acomodado.
Cuándo sí tiene sentido usarla?
Porque tampoco se trata de demonizarla.
Yo sí la usaría en casos como:
- sistemas simples
- aplicaciones internas
- APIs CRUD
- proyectos pequeños
- equipos junior que necesitan una estructura clara para arrancar
Si el problema es simple, no es necesario sacar un bazuca arquitectónica para matar una mosca.
Pero cuando el negocio empieza a tener reglas serias, múltiples integraciones, cambios frecuentes y necesidad de pruebas más limpias, ahí esta arquitectura empieza a sentirse limitada.
Demo Github
Ahora bien, les preparé una demo, explicando a detalle como funciona una arquitectura de capas simple y sencilla, solo descarguen el proyecto, lean el README file e impórtenlo en Intellij:
https://github.com/Emmax77/demo-layered-order-app
Al final, que más puedo decir? La arquitectura en capas ha sido una solución confiable que proporcionó orden y estructura en la construcción de aplicaciones empresariales; sin embargo, esta estrategia enseñó que tener capas no garantiza el desacoplamiento, ya que la lógica de negocio aún estaba demasiado conectada a la infraestructura. Esto llevó a la necesidad de considerar cómo dependían las partes del sistema entre sí.
En el próximo blog, veremos la evolución hacia la N-Tier Architecture, que busca escalar el concepto separando no solo el código, sino también el despliegue.
