2. Concepto de arquitectura en capas (N-Tier)

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.

2.1 Definición general del modelo N-Tier

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.

  • Capa superior: orientada al uso final (interfaces de usuario, APIs públicas o integraciones externas).
  • Capas intermedias: encapsulan reglas de negocio, orquestación de procesos o servicios compartidos.
  • Capa inferior: aborda la persistencia de datos, la comunicación con sistemas legados o la infraestructura básica.

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.

2.2 Propósito de dividir una aplicación en capas

Dividir en capas persigue objetivos tanto técnicos como organizacionales. Entre los beneficios más importantes se encuentran:

  • Cohesión y acoplamiento controlados: cada capa trabaja con un conjunto limitado de responsabilidades, lo que facilita el mantenimiento y la extensión.
  • Reemplazo incremental: es posible actualizar tecnologías o servicios dentro de una capa sin alterar el resto del sistema, siempre que los contratos se respeten.
  • Escalabilidad dirigida: las capas pueden desplegarse y escalarse de forma independiente para responder a picos de demanda específicos.
  • Colaboración por equipos: distintos grupos pueden trabajar en capas separadas con interfaces claras, evitando conflictos constantes en el código.

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.

2.3 Diferencias entre capas lógicas y físicas

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:

Capa de presentación

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));
    }
}
Comando
Respuesta

Capa de aplicación

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);
    }
}
Consulta
Entidad

Capa de infraestructura

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.

2.4 Próximos pasos

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.