4. Flujo de comunicación entre capas

Una arquitectura en capas cobra sentido cuando definimos cómo se comunican sus componentes. Las interacciones dictan la estabilidad del sistema tanto como la división de responsabilidades. En este tema describimos el flujo de mensajes, las reglas de dependencia y un recorrido completo desde la interfaz de usuario hasta la base de datos.

4.1 Cómo interactúan las capas entre sí

En un sistema N-Tier cada capa actúa como proveedor de servicios para la capa inmediatamente superior. La comunicación se realiza mediante interfaces claras que exponen datos de entrada y salida. En términos generales, el recorrido sigue estos pasos:

  • Presentación recibe la solicitud, la valida de manera superficial y crea un mensaje o comando.
  • Aplicación o dominio procesa el comando aplicando reglas de negocio, interactúa con entidades y coordina operaciones.
  • Persistencia ejecuta operaciones concretas contra la base de datos u otras fuentes de información.

El flujo puede extenderse a capas adicionales, como servicios externos o mensajería, pero siempre se mantiene el principio de escalones: cada mensaje avanza hacia abajo y retorna por la misma ruta.

4.2 Reglas de dependencia: solo hacia abajo

Un principio esencial es que las dependencias solo se permiten en dirección descendente. La capa de dominio no conoce detalles de presentación y la de persistencia no invoca servicios del dominio directamente sin que medien interfaces. Este orden facilita el reemplazo de componentes y evita ciclos peligrosos.

  • Dependencias explícitas: las capas superiores dependen de contratos definidos por las inferiores, no de sus implementaciones concretas.
  • Inversión de dependencias: el dominio define interfaces que las capas inferiores implementan, manteniendo la dirección conceptual del flujo sin violar el principio.
  • Comunicación ascendente controlada: los resultados viajan de regreso como respuestas o eventos, nunca como llamadas directas de una capa inferior a otra superior.

Respetar la dirección descendente evita que un cambio en la interfaz rompa la persistencia o que la base de datos se convierta en un atajo para saltar reglas de negocio.

4.3 Ejemplo de flujo de datos desde la UI hasta la base de datos

El siguiente diagrama textual describe el recorrido de un pago desde que una persona confirma la operación en la interfaz hasta que queda persistido:

  1. La interfaz de usuario envía una solicitud POST para confirmar el pago de una orden.
  2. El controlador transforma la solicitud en un comando para el caso de uso ConfirmarPago.
  3. El caso de uso valida el comando con un servicio antifraude, crea el registro de pago y solicita la persistencia.
  4. El repositorio implementado en la capa de infraestructura ejecuta una sentencia SQL y confirma la transacción.
  5. El caso de uso devuelve el recibo al controlador, que a su vez construye la respuesta para la interfaz.

Todo el viaje respeta el sentido descendente de las dependencias. El siguiente esquema destaca cada capa con su código representativo y las flechas del flujo de solicitud y respuesta.

Interfaz de usuario

Recibe la petición, valida datos de entrada y delega al caso de uso.

package com.example.ventas.presentation;

import com.example.ventas.application.ConfirmarPago;

public class PagoRestController {

    private final ConfirmarPago confirmarPago;

    public PagoRestController(ConfirmarPago confirmarPago) {
        this.confirmarPago = confirmarPago;
    }

    public PagoResponse confirmar(PagoRequest request) {
        var comando = request.toCommand();
        var recibo = confirmarPago.ejecutar(comando);
        return PagoResponse.from(recibo);
    }
}
Solicitud descendente
Respuesta ascendente

Lógica de negocio

Procesa el comando, aplica reglas de negocio y coordina la persistencia del pago.

package com.example.ventas.application;

import com.example.ventas.domain.Pago;
import com.example.ventas.domain.PagoRepositorio;
import com.example.ventas.domain.ServicioFraude;

public class ConfirmarPago {

    private final PagoRepositorio repositorio;
    private final ServicioFraude servicioFraude;

    public ConfirmarPago(PagoRepositorio repositorio, ServicioFraude servicioFraude) {
        this.repositorio = repositorio;
        this.servicioFraude = servicioFraude;
    }

    public Pago ejecutar(ConfirmarPagoCommand command) {
        servicioFraude.validar(command);
        Pago pago = Pago.registrar(command.ordenId(), command.monto());
        repositorio.guardar(pago);
        return pago;
    }
}
Operación de datos
Resultado al dominio

Persistencia

Concreta la operación contra la base de datos y confirma la transacción.

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.Pago;
import com.example.ventas.domain.PagoRepositorio;

public class PagoRepositorioJdbc implements PagoRepositorio {

    private final DataSource dataSource;

    public PagoRepositorioJdbc(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public void guardar(Pago pago) {
        String sql = "INSERT INTO pagos(orden_id, monto, confirmado_en) VALUES (?, ?, ?)";
        try (Connection connection = dataSource.getConnection();
             PreparedStatement statement = connection.prepareStatement(sql)) {
            statement.setString(1, pago.ordenId());
            statement.setBigDecimal(2, pago.monto());
            statement.setTimestamp(3, pago.confirmadoEn());
            statement.executeUpdate();
        } catch (SQLException ex) {
            throw new RuntimeException("No se pudo confirmar el pago de la orden " + pago.ordenId(), ex);
        }
    }
}

La capa de presentación transforma los datos de entrada en comandos, el dominio ejecuta las reglas y la capa de persistencia contacta a la base de datos. No existe una referencia inversa desde el repositorio hacia el controlador, garantizando la dirección del flujo.

4.4 Próximos pasos

Dominar el flujo de comunicación permite identificar cuellos de botella y planificar optimizaciones. En el siguiente tema construiremos un ejemplo completo de aplicación en tres capas, incluyendo decisiones de empaquetado y pruebas coordinadas.