Arquitectura de software
Arquitectura en capas y n-tier: niveles, capas y la regla de dependencias
Principiante11 min de lecturaActualizado
Esta página se tradujo del inglés con ayuda de IA y se cotejó con el original. Leer la versión en inglés
Dividir en capas es la primera decisión de arquitectura que toman casi todos los sistemas, y una de las peor entendidas. Al terminar esta guía sabrás distinguir los niveles físicos de las capas lógicas, elegir una estructura de capas sensata para una aplicación de negocio, mantener las dependencias apuntando en la dirección correcta y reconocer los antipatrones que convierten un código en capas en un laberinto.
Lo que aprenderás
- Explicar la diferencia entre un nivel físico (tier) y una capa lógica, y por qué se confunden tan a menudo
- Describir en qué se diferencian el cliente-servidor de dos niveles, el de tres niveles y el n-tier, y qué problema resolvió cada paso
- Estructurar las capas de presentación, aplicación, dominio e infraestructura con las dependencias apuntando hacia dentro
- Documentar una arquitectura de aplicaciones con una vista de componentes y una matriz de flujos
- Identificar antipatrones de capas como entidades que se filtran, servicios anémicos y lógica de negocio en los controladores
Casi todas las aplicaciones de negocio que vas a abrir en tu carrera están organizadas en capas, y la mayoría lo están mal. Introducir capas sale barato y arreglarlas sale caro, porque una estructura que deja que las reglas de negocio se cuelen en los controladores o que el SQL se cuele en el dominio se extiende por miles de clases antes de que nadie se dé cuenta. Hacerlo bien exige entender tres cosas que se confunden continuamente: los niveles físicos, las capas lógicas y la dirección en la que se permite que apunten las dependencias.
Los niveles son físicos, las capas son lógicas
Un nivel (tier) es una frontera de despliegue: un proceso, una máquina o un clúster que se comunica con otro a través de la red. Una capa es una frontera en el código: un conjunto de paquetes con una responsabilidad definida y reglas sobre a qué puede llamar. Ambas cosas son independientes. Una aplicación de escritorio de un solo proceso puede tener cinco capas bien limpias; un sistema distribuido en seis niveles puede no tener ninguna organización en capas que merezca ese nombre.
La confusión surge porque el despliegue clásico de tres niveles — un navegador, un servidor de aplicaciones y una base de datos — coincide con tres capas evidentes: presentación, lógica de negocio y acceso a datos. Esa coincidencia hizo que «tres niveles» y «tres capas» se usaran indistintamente en el día a día, y oculta una verdad importante: mover código a otro nivel es una decisión de red y de operaciones; moverlo a otra capa es una decisión de diseño. Puedes cambiar una sin tocar la otra.
Cómo evolucionaron los niveles
Cliente-servidor de dos niveles
En el modelo de dos niveles, un cliente pesado contiene la interfaz de usuario y toda la lógica de negocio, y habla directamente con una base de datos compartida mediante un protocolo de base de datos. Las reglas de negocio viven en el programa cliente y, a veces, en procedimientos almacenados. El modelo es sencillo y rápido de construir, por eso dominó los años noventa, pero tiene problemas estructurales:
- Cada cliente necesita una conexión a la base de datos, que se convierte en el límite de escalabilidad.
- Cambiar una regla obliga a redesplegar todas las instalaciones del cliente.
- El esquema de la base de datos se convierte en el contrato de integración entre aplicaciones, así que nadie se atreve a cambiarlo.
- La seguridad se aplica a nivel de usuario de base de datos, de grano demasiado grueso para la autorización de negocio.
Tres niveles
Los despliegues de tres niveles insertaron un servidor de aplicaciones entre los clientes y la base de datos. El cliente pasó a ser ligero (con el tiempo, un navegador), la lógica de negocio se trasladó al nivel intermedio y la base de datos quedó oculta detrás de él. Ese cambio resolvió el problema de las conexiones (el nivel intermedio las agrupa en un pool), el del despliegue (un único servidor que actualizar) y el de la integración (las demás aplicaciones llaman a la interfaz del nivel intermedio en lugar de ir a las tablas). También trajo un nuevo conjunto de preocupaciones — sesiones, pools de conexiones, invocación remota — para las que se diseñaron las plataformas de servidores de aplicaciones empresariales de los años 2000.
N niveles
«N-tier» es lo que ocurre cuando el propio nivel intermedio se divide: un servidor web que sirve los recursos estáticos y termina el TLS, un servidor de aplicaciones, una capa de integración separada que envuelve sistemas legacy o APIs de socios, un nivel de caché, un broker de mensajes, una o varias bases de datos. Cada nivel adicional aporta aislamiento, escalado independiente o una frontera de seguridad, y cuesta un salto de red, un modo de fallo más y otra pieza que operar.
| Modelo | Dónde vive la lógica de negocio | Qué resolvió | Qué cuesta |
|---|---|---|---|
| Dos niveles | Programa cliente, procedimientos almacenados | Simplicidad, acceso directo a los datos | Escalabilidad, despliegue, acoplamiento al esquema |
| Tres niveles | Servidor de aplicaciones | Pool de conexiones, reglas centralizadas, esquema oculto | Sesiones, llamadas remotas, un servidor que mantener en marcha |
| N niveles | Niveles de aplicación e integración | Zonas de seguridad, escalado independiente, aislamiento del legacy | Latencia en cada salto, más modos de fallo, carga operativa |
Los cuatro niveles de una descripción de arquitectura
Cuando describen un sistema, los arquitectos suelen trabajar a cuatro niveles, y cada uno responde a una pregunta distinta. Ponerles nombre evita esas reuniones en las que una persona discute de servidores mientras otra discute de paquetes.
- Arquitectura funcional — qué capacidades de negocio ofrece el sistema y cómo se agrupan, con un vocabulario que el negocio entiende. Aquí no aparece ninguna tecnología.
- Arquitectura de aplicaciones — qué aplicaciones y componentes existen, cuál es dueño de qué datos y cómo se llaman entre sí. Aquí es donde se dibujan el diagrama de componentes y la matriz de flujos.
- Arquitectura técnica (de infraestructura) — qué servidores, zonas, redes, balanceadores de carga y bases de datos alojan esos componentes, con sus políticas de disponibilidad y seguridad.
- Arquitectura de software — cómo se organiza el código dentro de un componente: frameworks, módulos, capas, reglas de codificación.
Las capas pertenecen al cuarto nivel; los niveles físicos, al tercero. La matriz de flujos del segundo nivel es el artefacto que los une, y merece la pena elaborarla para cualquier sistema con más de un puñado de componentes:
| Ref. | Protocolo | Llamante | Llamado | Seguridad | Trazado |
|---|---|---|---|---|---|
| F1 | HTTPS | Navegador del cliente | Front-end web | TLS, cookie de sesión | sí |
| F2 | HTTP/JSON | Front-end web | Servicio de pedidos | mTLS dentro de la zona | sí |
| F3 | JDBC | Servicio de pedidos | Base de datos de pedidos | Credenciales de BD desde el vault | no |
| F4 | HTTPS | Servicio de pedidos | Proveedor de pagos | OAuth 2.0 client credentials | sí |
Cada fila se convierte en una regla de firewall, una sonda de monitorización y un posible timeout. Escribirlas pronto marca la diferencia entre una arquitectura y un dibujo.
Capas lógicas dentro de un servicio
Dentro de un componente desplegable, la estructura convencional tiene cuatro capas. Los nombres varían; las responsabilidades, no.
- Presentación (o interfaz) — recibe las peticiones, valida su forma, las convierte en llamadas a la capa inferior y genera las respuestas. Aquí viven los controladores HTTP, los listeners de mensajes, los comandos de CLI y las tareas programadas. Conoce los códigos de estado HTTP y el JSON; nada por debajo de ella los conoce.
- Aplicación — orquesta un caso de uso: carga los objetos que necesita, invoca el comportamiento del dominio, gestiona la transacción y publica eventos. No contiene reglas de negocio propias, solo la secuencia de pasos.
- Dominio — los conceptos de negocio y las reglas que los rigen: qué es un pedido válido, cuándo se aplica un descuento, qué pasa cuando se agota el stock. Es la capa que debería sobrevivir a un cambio de framework, de base de datos o de canal de entrega.
- Infraestructura — habla con el mundo exterior: acceso a la base de datos, clientes HTTP, brokers de mensajes, sistemas de archivos, correo electrónico. Implementa interfaces declaradas por la capa de dominio o de aplicación.
// Presentation: knows HTTP, knows nothing about pricing rules
@RestController
@RequestMapping("/orders")
class OrderController {
private final PlaceOrder placeOrder;
OrderController(PlaceOrder placeOrder) { this.placeOrder = placeOrder; }
@PostMapping
ResponseEntity<OrderResponse> create(@Valid @RequestBody OrderRequest body) {
OrderId id = placeOrder.handle(body.customerId(), body.lines());
return ResponseEntity.created(URI.create("/orders/" + id.value()))
.body(new OrderResponse(id.value()));
}
}
// Application: one use case, one transaction, no rules of its own
@Service
class PlaceOrder {
private final OrderRepository orders; // interface owned by the domain
private final PricingPolicy pricing; // domain service
PlaceOrder(OrderRepository orders, PricingPolicy pricing) {
this.orders = orders; this.pricing = pricing;
}
@Transactional
OrderId handle(CustomerId customer, List<OrderLine> lines) {
Order order = Order.draft(customer, lines); // domain enforces invariants
order.price(pricing); // domain rule, not controller code
orders.save(order);
return order.id();
}
}
// Infrastructure: implements the domain's interface with JPA
@Repository
class JpaOrderRepository implements OrderRepository {
// mapping between Order and the persistence entity lives here, not in the domain
}
El detalle importante es la dirección de la flecha. PlaceOrder depende de OrderRepository, una interfaz declarada junto a Order en el paquete de dominio. JpaOrderRepository, en el paquete de infraestructura, la implementa. Por tanto, el dominio no importa JPA, ni Spring, ni nada que no sea Java puro, y puedes hacer tests unitarios de Order y PricingPolicy sin base de datos.
Capas estrictas frente a capas relajadas
Con capas estrictas, cada capa solo puede llamar a la que tiene inmediatamente debajo. Con capas relajadas, una capa puede llamar a cualquiera de las inferiores, de modo que un controlador puede ir directamente a un repositorio para una lectura trivial. Las capas estrictas producen métodos de paso que no hacen más que delegar; las relajadas invitan a que los controladores acumulen lógica de negocio. Un término medio defendible: la presentación solo llama a la aplicación; la aplicación llama al dominio y a las interfaces de infraestructura; nadie llama a la presentación.
Puertos y adaptadores: la misma idea, dibujada de otra forma
La arquitectura hexagonal, también llamada de puertos y adaptadores, y las variantes «clean» y «onion» que la siguieron mantienen las mismas responsabilidades pero cambian el dibujo. En lugar de una pila con la base de datos abajo del todo, las capas de dominio y aplicación ocupan el centro, y todo lo que habla con el mundo exterior — HTTP, base de datos, broker, interfaz de usuario — es un adaptador conectado a un puerto (una interfaz) en el borde del núcleo. La base de datos ya no está «debajo» del dominio; es un adaptador más, al mismo nivel que el controlador web.
¿Por qué importa redibujarlo? Porque en la pila tradicional la capa de persistencia está abajo del todo y todo parece depender de ella, así que los equipos diseñan el dominio en torno a las tablas. Poner el dominio en el centro hace explícita la regla de dependencias: las dependencias del código fuente apuntan hacia dentro, hacia el dominio, nunca hacia fuera. La interfaz OrderRepository de antes es un puerto; JpaOrderRepository es un adaptador. Si ya sigues esa regla, ya tienes una arquitectura hexagonal, sea cual sea el aspecto del diagrama.
Datos que cruzan las fronteras entre capas
Cada frontera plantea la pregunta de qué objetos la atraviesan.
- Entre presentación y aplicación: usa objetos de petición y respuesta específicos de la API (los records funcionan muy bien en Java 21). Nunca expongas directamente las entidades de persistencia; el contrato de la API y el esquema de las tablas cambian por motivos distintos, y serializar entidades arrastra a las respuestas relaciones con carga lazy y campos internos.
- Entre aplicación y dominio: pasa objetos de dominio. Un mapeo aquí suele ser un desperdicio.
- Entre dominio e infraestructura: la interfaz del repositorio habla en términos del dominio (
Order,OrderId) y el adaptador hace el mapeo a lo que necesite el almacenamiento.
La regla práctica: un DTO (data transfer object) se gana su sitio cuando los dos lados evolucionan de forma independiente o cruzan la red; dentro de un mismo proceso, entre dos capas que siempre cambian a la vez, es puro trámite.
Errores comunes
Lógica de negocio en el controlador. Un controlador que comprueba el stock, calcula un descuento y decide si avisar al almacén no puede reutilizarse desde un proceso batch ni desde un listener de mensajes, y no se puede probar sin HTTP. Solución: el controlador traduce y delega; cada if relacionado con el negocio se va al dominio.
Dominio anémico, servicio obeso. Clases de dominio con solo getters y setters mientras una clase *Service concentra todas las reglas. Las reglas acaban duplicadas entre servicios y es imposible garantizarlas como invariantes. Solución: pon el comportamiento en los objetos dueños de los datos — quien valida es order.addLine(...), no OrderService.validateLine(...).
Entidades que se filtran a la API. Devolver entidades JPA desde un endpoint REST acopla el formato de transmisión al esquema, provoca excepciones de inicialización lazy y expone campos que nunca quisiste publicar. Solución: records de respuesta dedicados y un mapper explícito.
Capas por tecnología, paquetes solo por capa. Un código con paquetes de primer nivel controllers, services y repositories, cada uno con cientos de clases, es difícil de recorrer y permite que cualquier servicio llame a cualquier repositorio. Solución: organiza primero por funcionalidad (orders, billing), luego por capa dentro de cada funcionalidad, y haz package-private lo interno de cada funcionalidad.
Niveles añadidos por moda. Un «nivel de integración» separado para un sistema con un único socio externo añade un salto y un despliegue a cambio de nada. Solución: añade un nivel cuando sepas nombrar la propiedad de aislamiento, escalado o seguridad que te aporta.
Aspectos transversales implementados en cada capa. Logging, autenticación y gestión de transacciones copiados en cada clase. Solución: resuélvelos una sola vez — filtros o interceptores en el borde, transacciones declarativas en la capa de aplicación, aspectos donde convenga.
Buenas prácticas
- Escribe la matriz de flujos antes del primer diagrama de despliegue; te obliga a plantearte las preguntas de seguridad y trazabilidad.
- Haz cumplir la regla de dependencias de forma automática. Unos tests al estilo ArchUnit que rompan el build cuando
domainimporteinfrastructurecuestan una hora de configuración y evitan años de deriva. - Mantén la capa de dominio libre de anotaciones de framework siempre que sea práctico. Si una regla necesita
@Transactionalpara ser correcta, está en la capa equivocada. - Haz de la capa de aplicación la frontera transaccional: un caso de uso, una transacción, un único sitio donde mirar cuando algo ha quedado confirmado a medias.
- Trata un nuevo nivel como una decisión de producto con un responsable, un SLA y un runbook, no como una caja en una diapositiva.
- Cuando una funcionalidad atraviesa muchas capas para hacer algo trivial, cuestiona la rigidez de las capas en lugar de añadir métodos de paso.
Puntos clave
- Los niveles son fronteras de despliegue; las capas son fronteras en el código. Cambia uno sin dar por hecho que el otro tiene que cambiar.
- El modelo de dos niveles ponía la lógica en el cliente, el de tres la trasladó a un servidor de aplicaciones y el n-tier dividió la parte intermedia para ganar aislamiento y escala: cada paso cambió simplicidad por una propiedad que deberías saber nombrar.
- Describe un sistema a cuatro niveles — funcional, de aplicaciones, técnico y de software — y usa una matriz de flujos para vincular las aplicaciones con la infraestructura.
- Dentro de un componente, las dependencias apuntan hacia dentro, hacia el dominio; el dominio declara las interfaces que implementa la infraestructura.
- La arquitectura hexagonal es la misma regla con el dominio dibujado en el centro, lo que evita que los equipos diseñen en torno a la base de datos.
- Los antipatrones caros son la lógica en los controladores, los dominios anémicos y las entidades expuestas por la red; los tres son baratos de prevenir pronto y caros de deshacer después.
Fuentes y lecturas recomendadas
Preguntas frecuentes
- ¿Una arquitectura de tres niveles es lo mismo que una arquitectura de tres capas?
- No. Los niveles (tiers) son físicos: procesos o máquinas separados, como un navegador, un servidor de aplicaciones y un servidor de base de datos. Las capas son lógicas: grupos de código con responsabilidades distintas que pueden ejecutarse todos en un mismo proceso. Un despliegue de tres niveles suele contener más de tres capas lógicas, y una aplicación de escritorio de un solo nivel puede estar perfectamente organizada en capas.
- ¿Cada capa debería tener sus propias clases de datos (DTO)?
- Usa una representación separada allí donde la forma o el ciclo de vida sean distintos. Los objetos de petición y respuesta de la API deben ser distintos de las entidades de persistencia, porque evolucionan por motivos diferentes y exponen campos diferentes. Entre la capa de aplicación y la de dominio, pasar directamente los objetos de dominio suele estar bien; añadir ahí un paso de mapeo es puro trámite sin beneficio.
- ¿La arquitectura en capas está obsoleta frente a los microservicios?
- Responden a preguntas distintas. Las capas organizan el código dentro de una unidad desplegable; los microservicios dividen un sistema en varias unidades desplegables. Todo microservicio bien construido está organizado en capas por dentro, y la mayoría de las migraciones exitosas a microservicios parten de un monolito cuyas capas estaban lo bastante limpias como para trocearlo. Unas capas mal diseñadas hacen que ambos estilos sean un suplicio.
- ¿Puede una capa inferior llamar a una capa superior?
- No directamente. Las dependencias deben ir en un solo sentido, normalmente de fuera hacia dentro. Cuando una capa inferior necesita desencadenar un comportamiento en una superior, publica un evento o llama a una interfaz propia que la capa superior implementa. Esta inversión mantiene el núcleo libre de referencias a frameworks y a mecanismos de entrega.