3. Capas principales y sus responsabilidades

Una arquitectura en capas establece límites explícitos para dividir responsabilidades y evitar que las decisiones se mezclen. Este tema profundiza en las tres capas tradicionales (presentación, dominio y persistencia) y describe capas complementarias frecuentes en escenarios empresariales. La clave es preservar la direccionalidad de las dependencias: de la capa superior a la inferior, nunca al revés.

3.1 Capa de presentación o interfaz de usuario

La capa de presentación es el punto de encuentro con las personas usuarias o con clientes externos. Puede materializarse como una aplicación web, una app móvil, una consola o un conjunto de APIs REST. Sus responsabilidades principales son:

  • Captura de intención: recibir entradas y traducirlas a comandos comprensibles para la capa de dominio.
  • Formateo de respuestas: transformar los resultados del dominio en vistas, JSON, XML o cualquier formato acordado.
  • Validaciones superficiales: aplicar reglas de formato, autenticación y autorización básica antes de delegar la lógica profunda.

Herramientas como Spring Framework ofrecen controladores y anotaciones para implementar esta capa manteniendo un código legible y consistente.

3.2 Capa de lógica de negocio o dominio

El dominio concentra las reglas esenciales del problema: cómo se generan pedidos, cómo se calculan promociones, qué restricciones aplican al inventario. Esta capa debe permanecer aislada de detalles de interfaz y persistencia para preservar su estabilidad.

  • Casos de uso o servicios de aplicación: orquestan reglas para cumplir objetivos concretos (registrar un pedido, emitir una factura).
  • Modelo de dominio: entidades, objetos de valor, agregados y eventos que representan el lenguaje ubicuo del negocio.
  • Reglas y políticas: invariantes, validaciones y cálculos que responden a la realidad del negocio.

El dominio expone interfaces (puertos) que el resto de capas implementa, garantizando independencia respecto de frameworks y bases de datos.

3.3 Capa de acceso a datos o persistencia

La capa de persistencia resuelve el almacenamiento y la recuperación de información. Puede interactuar con bases de datos relacionales, sistemas NoSQL, colas de mensajes o servicios externos. Sus responsabilidades incluyen:

  • Mapeo de objetos: traducir estructuras del dominio a tablas, documentos o mensajes.
  • Gestor de transacciones: mantener consistencia, gestionar sesiones y conexiones.
  • Implementaciones de puertos: concretar las interfaces definidas por el dominio para acceder a los datos.

Frameworks como Jakarta Persistence o controladores JDBC proporcionan utilidades para minimizar el código repetitivo y estandarizar la comunicación con fuentes de datos.

3.4 Capas adicionales (servicios, infraestructura, API, etc.)

En proyectos amplios es habitual agregar capas intermedias o transversales que complementan a las tres principales:

  • Capa de servicios: expone APIs internas o microservicios que reutilizan el dominio y facilitan integraciones entre equipos.
  • Capa de infraestructura: implementa detalles técnicos como mensajería, caching, seguridad avanzada o integraciones con terceros.
  • Capa de integración: coordina adaptadores encargados de comunicarse con sistemas externos mediante protocolos específicos.
  • Capa de soporte transversal: provee logging, observabilidad, manejo de errores, internacionalización y configuración centralizada.

Estas capas adicionales refuerzan la modularidad y permiten que cada responsabilidad evolucione siguiendo su propio ritmo de cambio.

Interfaz de usuario

Centraliza la comunicación con la persona usuaria, aplica validaciones básicas y delega las decisiones complejas al dominio.

package com.example.ventas.presentation;

import com.example.ventas.application.RegistrarPedido;

public class PedidoController {

    private final RegistrarPedido registrarPedido;

    public PedidoController(RegistrarPedido registrarPedido) {
        this.registrarPedido = registrarPedido;
    }

    public PedidoResponse crear(PedidoRequest request) {
        var comando = request.toCommand();
        var resultado = registrarPedido.ejecutar(comando);
        return PedidoResponse.from(resultado);
    }
}
Delegación
Respuesta

Lógica de negocio

Orquesta casos de uso, protege invariantes y coordina la persistencia mediante contratos definidos por el dominio.

package com.example.ventas.application;

import com.example.ventas.domain.Pedido;
import com.example.ventas.domain.PedidoRepositorio;

public class RegistrarPedido {

    private final PedidoRepositorio repositorio;

    public RegistrarPedido(PedidoRepositorio repositorio) {
        this.repositorio = repositorio;
    }

    public Pedido ejecutar(RegistrarPedidoCommand command) {
        Pedido pedido = Pedido.crear(command.numero(), command.cliente(), command.items());
        pedido.validarPoliticas();
        repositorio.guardar(pedido);
        return pedido;
    }
}
Persistencia
Confirmación

Acceso a datos

Implementa los puertos definidos por el dominio y se conecta con la base de datos garantizando consistencia transaccional.

package com.example.ventas.infrastructure;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
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) {
        String sql = "INSERT INTO pedidos(numero, cliente, total) VALUES (?, ?, ?)";
        try (Connection connection = dataSource.getConnection();
             PreparedStatement statement = connection.prepareStatement(sql)) {
            statement.setString(1, pedido.numero());
            statement.setString(2, pedido.cliente());
            statement.setBigDecimal(3, pedido.total());
            statement.executeUpdate();
        } catch (SQLException ex) {
            throw new RuntimeException("No se pudo almacenar el pedido " + pedido.numero(), ex);
        }
    }
}

El controlador se limita a recibir la solicitud y delegar la lógica al caso de uso, mientras que la capa de infraestructura implementa la persistencia. Esta separación facilita probar cada pieza por separado y cambiar proveedores tecnológicos sin afectar el resto del sistema.

3.5 Próximos pasos

Habiendo identificado las responsabilidades de cada capa, avanzaremos hacia el flujo de comunicación entre ellas. Comprender cómo circulan los datos y qué dependencias están permitidas es esencial para mantener la arquitectura en capas alineada con los objetivos del negocio.