La arquitectura en capas, conocida también como modelo N-Tier, organiza una aplicación en zonas con responsabilidades bien definidas. Cada capa se especializa en resolver un tipo de problema y se comunica con las capas adyacentes mediante contratos explícitos. Esta separación aporta orden a sistemas complejos y allana el camino para evolucionar sin romper los cimientos existentes.
El modelo N-Tier se utiliza tanto en aplicaciones web tradicionales como en soluciones empresariales distribuidas. Adaptar la cantidad de capas a la realidad del proyecto permite balancear simplicidad y escalabilidad. Lo fundamental es capturar el flujo de datos y reglas de negocio dentro de una estructura predecible.
El modelo N-Tier define un conjunto de capas jerárquicas donde cada nivel aporta un tipo de servicio específico. El número N indica que la cantidad de capas puede variar según el caso, pero siempre se mantiene la regla de dependencia descendente: una capa superior puede usar las capacidades de la inmediata inferior, mas no al revés.
Cada capa representa un punto de estabilidad: se diseña para mantenerse relativamente inmutable incluso cuando cambian otras partes del sistema. Esta estrategia reduce el riesgo de que una modificación puntual afecte a toda la aplicación.
Dividir en capas persigue objetivos tanto técnicos como organizacionales. Entre los beneficios más importantes se encuentran:
En el plano operativo, la separación también facilita la automatización de pruebas y el monitoreo. Cada capa puede exponer métricas de salud, tiempos de respuesta y tasas de error alineadas con sus funciones.
El modelo N-Tier distingue dos formas de partición: lógica y física. Aunque suelen convivir, es importante tratarlas por separado.
Capas lógicas son divisiones en el código y en la organización del proyecto. Se expresan mediante paquetes, módulos o espacios de nombres que agrupan funcionalidades. Por ejemplo, un proyecto puede tener paquetes presentation, application y infrastructure, cada uno enfocado en un rol específico.
Capas físicas representan despliegues o nodos separados que colaboran para ejecutar la aplicación. Una arquitectura de tres capas físicas habitual incluye un servidor web, un servidor de aplicaciones y un servidor de base de datos. Las capas lógicas pueden coexistir dentro de la misma capa física o distribuirse en varias máquinas, dependiendo de los requisitos de rendimiento y seguridad.
El siguiente fragmento en Java muestra un proyecto organizado con capas lógicas que interactúan a través de interfaces. Este diseño permite decidir posteriormente si cada capa se despliega en servidores distintos:
Expone una API REST que transforma la petición en un comando para la capa de aplicación.
package com.example.ventas.presentation;
import com.example.ventas.application.ConsultarPedido;
public class PedidoRestController {
private final ConsultarPedido consultarPedido;
public PedidoRestController(ConsultarPedido consultarPedido) {
this.consultarPedido = consultarPedido;
}
public PedidoResponse obtenerPorNumero(String numero) {
return PedidoResponse.fromDomain(consultarPedido.ejecutar(numero));
}
}
Orquesta la consulta, protege la lógica y delega en una interfaz de repositorio.
package com.example.ventas.application;
import com.example.ventas.domain.Pedido;
import com.example.ventas.domain.PedidoRepositorio;
public class ConsultarPedido {
private final PedidoRepositorio repositorio;
public ConsultarPedido(PedidoRepositorio repositorio) {
this.repositorio = repositorio;
}
public Pedido ejecutar(String numero) {
return repositorio.buscarPorNumero(numero);
}
}
Implementa la interfaz de repositorio y decide dónde despliega la persistencia física.
package com.example.ventas.infrastructure;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.example.ventas.domain.Pedido;
import com.example.ventas.domain.PedidoRepositorio;
public class PedidoRepositorioJdbc implements PedidoRepositorio {
private final DataSource dataSource;
public PedidoRepositorioJdbc(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void guardar(Pedido pedido) {
System.out.println("INSERT INTO pedidos ...");
}
@Override
public Pedido buscarPorNumero(String numero) {
String sql = "SELECT numero, cliente, total FROM pedidos WHERE numero = ?";
try (Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, numero);
try (ResultSet resultSet = statement.executeQuery()) {
if (!resultSet.next()) {
return null;
}
return mapearPedido(resultSet);
}
} catch (SQLException ex) {
throw new RuntimeException("No se pudo recuperar el pedido " + numero, ex);
}
}
private Pedido mapearPedido(ResultSet resultSet) throws SQLException {
return new Pedido(
resultSet.getString("numero"),
resultSet.getString("cliente"),
resultSet.getBigDecimal("total")
);
}
}
En este escenario, la capa de infraestructura podría desplegarse en un servidor distinto para aislar la base de datos, mientras que presentación y aplicación podrían residir juntas en un contenedor de servicios. Mantener la distinción entre lógico y físico ayuda a planificar la evolución del producto.
Comprender el concepto N-Tier, sus objetivos y la dualidad entre capas lógicas y físicas permite construir bases sólidas para diseños modulares. En el siguiente tema analizaremos las responsabilidades específicas de cada capa y cómo asignar funciones sin que se mezclen los dominios.