Saltar al contenido
Módulo 1 de 7Básico

¿Por qué separar el negocio de la tecnología?

El problema antes que el patrón: qué duele cuando la UI, la base de datos y el framework se enredan con las reglas de negocio, y qué quería resolver Alistair Cockburn en 2005.

Antes de dibujar un solo hexágono, conviene sentir el problema. Imagina que mantienes el sistema de pedidos de una cafetería que también surte encargos grandes para oficinas. Un día te piden un cambio pequeño: “cuando un pedido supere los $2,000, aplica un 10% de descuento”. Abres el código y la regla no está en ninguna parte… y está en todas: un pedazo vive en el handler HTTP, otro en una consulta SQL y otro en la plantilla que pinta el total del ticket. Cambiar una regla de negocio te obliga a tocar la interfaz, la base de datos y el framework a la vez. Eso es acoplamiento, y es el enemigo que este curso te enseña a aislar.

Este pedido de cafetería nos va a acompañar durante todo el curso. Módulo a módulo lo iremos refinando hasta convertirlo en una aplicación hexagonal completa, con un dominio Pedido, un caso de uso ConfirmarPedido y un par de contratos con el mundo exterior. Pero primero hay que entender por qué la forma “natural” de organizar el código nos metió en este lío.

La arquitectura en capas y sus dependencias transitivas

La respuesta clásica fue la arquitectura en capas: presentación arriba, negocio en medio, datos abajo, cada capa dependiendo de la de abajo. El diagrama se ve ordenado, pero esconde una trampa. Recorre la cadena paso a paso y observa la flecha que nadie dibujó:

  1. PresentaciónLa pantalla del ticket declara una sola dependencia: la capa de negocio.
  2. NegocioEl negocio, a su vez, depende de la capa de datos para leer y guardar.
  3. DatosAl fondo viven el esquema de tablas, el ORM y los tipos del driver.
  4. Presentación depende de DatosLa flecha que nadie dibujó: la presentación termina dependiendo de los datos aunque nunca lo declaró.

A esa filtración se le llama dependencia transitiva: si A depende de B y B depende de C, entonces A depende de C aunque nunca lo haya declarado. Por eso un cambio en el esquema de la base de datos sacude el edificio completo, piso por piso, hasta llegar a la pantalla — y la capa “de negocio” acaba hablando el idioma de la infraestructura.

El costo se paga en las pruebas

El síntoma más caro aparece al probar. La regla del descuento quedó enredada con la petición HTTP y la consulta SQL, así que hazte la pregunta: ¿qué necesitas levantar para verificar que el 10% se aplica bien? Recorre la utilería completa:

  1. Levantar BDArrancar un servidor de base de datos solo para esta prueba.
  2. Crear tablasReproducir el esquema completo antes de poder insertar nada.
  3. Sembrar pedidoInsertar un pedido con líneas que sumen más de $2,000.
  4. Petición HTTP falsaFabricar una petición con el pedido_id correcto.
  5. EjecutarRecién entonces: ejecutar la función y mirar el resultado del if.

Cinco pasos de utilería para una sola línea de negocio.

La cadena de consecuencias es siempre la misma: las pruebas se vuelven lentas y frágiles, así que se ejecutan menos; como se ejecutan menos, los cambios dan miedo; como los cambios dan miedo, se posponen; y el código se degrada un poco más con cada parche tímido. Todo por una flecha de dependencia que apunta en la dirección equivocada.

La misma regla, sin rehenes

Ahora sí, el código. Mira la misma regla dos veces, frente a frente: a la izquierda, tomada como rehén por HTTP y SQL; a la derecha, viviendo en un tipo propio del dominio, con el descuento como un método sobre ese tipo:

Con rehenes

pedidos.rs — negocio y tecnología revueltosInfraestructura
// La "regla de negocio" necesita HTTP y SQL solo para existir.
fn total_con_descuento(req: &HttpRequest, db: &Conexion) -> f64 {
    let id: u64 = req.query("pedido_id").parse().unwrap();
    let filas = db.query("SELECT precio, cantidad FROM lineas WHERE pedido_id = ?", id);
    let subtotal: f64 = filas.iter().map(|f| f.precio * f.cantidad).sum();
    if subtotal > 2000.0 { subtotal * 0.90 } else { subtotal }
}
Acoplado — necesita HTTP y SQL

Sin rehenes

dominio/pedido.rs — la regla en su idiomaDominio
pub struct LineaDePedido {
    pub precio_unitario: f64,
    pub cantidad: u32,
}

pub struct Pedido {
    lineas: Vec<LineaDePedido>,
}

impl Pedido {
    pub fn subtotal(&self) -> f64 {
        self.lineas
            .iter()
            .map(|l| l.precio_unitario * f64::from(l.cantidad))
            .sum()
    }

    /// Regla de negocio: 10% de descuento arriba de $2,000.
    pub fn total(&self) -> f64 {
        let subtotal = self.subtotal();
        if subtotal > 2000.0 { subtotal * 0.90 } else { subtotal }
    }
}
Negocio puro — se prueba en memoria

En el panel acoplado, la única línea que es negocio de verdad es la del if: todo lo demás es tecnología que tomó a la regla como rehén. Probar la regla del panel derecho, en cambio, cuesta una línea: construyes un Pedido en memoria y afirmas sobre total(). Sin servidor, sin tablas, sin peticiones falsas. Fíjate en que todavía no dibujamos ningún hexágono ni usamos vocabulario nuevo: solo movimos la regla a un tipo que habla el idioma del negocio. Ese gesto —proteger un núcleo que no sabe nada de la tecnología que lo rodea— es exactamente lo que el patrón de este curso convierte en disciplina.

Queda una pregunta incómoda, claro: si Pedido ya no conoce la base de datos, ¿quién guarda los pedidos? ¿Y quién avisa al cliente cuando su pedido se confirma? Responderla con precisión es el trabajo de los próximos módulos.

La intención original: Cockburn, 2005

Alistair Cockburn documentó el patrón de la arquitectura hexagonal (Ports and Adapters) en 2005 precisamente para atacar este problema. Su intención, textual:

“Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases.”

Es decir: que la aplicación pueda ser conducida por igual por un humano, otro programa, un test automatizado o un script batch, y que pueda desarrollarse y probarse aislada de sus dispositivos y bases de datos definitivos. La regla de frontera que lo hace posible también es suya: el código del interior no debe filtrarse hacia el exterior —y nada del exterior (tipos del ORM, peticiones HTTP, DTOs del framework) debe filtrarse hacia el interior.

Dentro de esa frontera queda el núcleo: solo lo que importa para el problema de negocio, “sin referencias a ninguna tecnología, framework o dispositivo del mundo real”, como lo resume Juan Manuel Garrido de Paz, colaborador de Cockburn. Es el código que sobrevive aunque mañana cambies de framework web o de base de datos.

Nota el orden de las ideas: Cockburn no partió de un diagrama bonito, sino de un dolor operativo —no poder probar sin infraestructura, no poder cambiar de tecnología sin tocar el negocio— y diseñó el patrón hacia atrás desde ahí. Por eso este curso empezó por el dolor y no por el hexágono.

Minijuego: Detective de acoplamiento

Hora de entrenar el ojo. Te presentamos descripciones de fragmentos de código del sistema de la cafetería: decide cuáles mezclan negocio con tecnología y cuáles son negocio puro. La pista de siempre: pregúntate qué necesitarías levantar para probar ese fragmento. Si la respuesta incluye una base de datos, un framework o una red, ya tienes al culpable.

Lo que viene

En los próximos módulos le pondremos nombre a cada pieza de la solución: los puertos (los contratos que el núcleo ofrece y necesita, como PedidoRepository para guardar pedidos o NotificadorDePedidos para avisar al cliente), los adaptadores (las implementaciones concretas para cada tecnología) y la dirección de las conversaciones (driving vs. driven). Por ahora quédate con la idea central del curso: el negocio no debería saber qué base de datos usas — y cuando lo sabe, lo pagas en cada prueba y en cada cambio.

Fuentes

Práctica

Practica lo aprendido

Tarjetas de repaso

Evaluación del módulo