1. Introducción a la evolución de las arquitecturas de software

La evolución de las arquitecturas de software explica por qué hoy buscamos modelos que prioricen la independencia del dominio. Comprender este recorrido es imprescindible para adoptar la arquitectura hexagonal, también conocida como arquitectura de puertos y adaptadores. Su propuesta de separar la lógica de negocio de las tecnologías de soporte se alimenta de aprendizajes acumulados durante décadas.

Este primer tema repasa los hitos que llevaron al sector desde los esquemas jerárquicos hasta estilos más flexibles. Analizaremos el acoplamiento que surge al depender de frameworks o infraestructuras específicas y veremos cómo la comunidad comenzó a aislar el dominio como respuesta a ese problema.

1.1 De la arquitectura en capas a las arquitecturas más flexibles

Las primeras aplicaciones empresariales siguieron el paradigma en capas. Separar presentación, negocio y datos resolvía el caos de los monolitos iniciales, permitiendo roles definidos para equipos distintos. Sin embargo, la dinámica digital aceleró la necesidad de incorporar nuevos canales, microservicios y automatizaciones sin reescribir el corazón del sistema.

Para responder, las organizaciones empezaron a experimentar con arquitecturas dirigidas por eventos, orientadas a servicios y basadas en componentes. Estos estilos pretendían flexibilizar la integración, pero con frecuencia mantenían dependencias fuertes hacia la infraestructura: un cambio de base de datos o un bus de mensajes distinto implicaba alterar el dominio o hasta volver a entrenar a todo el equipo.

El giro llegó cuando se introdujo la idea de definir contratos en la frontera de la aplicación. Las clases de negocio debían conocer qué necesitaban (un puerto de persistencia, una fuente de eventos), no cómo se implementaba. La arquitectura hexagonal consolidó ese enfoque al disponer el dominio en el centro, rodeado por adaptadores intercambiables.

1.2 El problema del acoplamiento entre lógica y tecnología

Cuando la lógica central depende de detalles técnicos concretos, el proyecto pierde capacidad de evolución. Este acoplamiento se manifiesta al invocar directamente APIs de frameworks, bibliotecas de acceso a datos o SDKs externos desde los casos de uso. Como consecuencia:

  • Las migraciones tecnológicas se vuelven riesgosas, porque cada capa debe reescribirse para adaptarse a la nueva herramienta.
  • Las pruebas unitarias quedan limitadas a entornos complejos que replican infraestructura real (bases de datos, colas, servidores web).
  • El conocimiento funcional se diluye, ya que la lógica se mezcla con código de configuración y detalles de bajo nivel.

Quienes diseñaron la arquitectura hexagonal observaron que la dependencia hacia la infraestructura atentaba contra la modularidad. Alistair Cockburn propuso tratar cada punto de interacción como un puerto, delegando la implementación a adaptadores externos. De esa manera, un caso de uso solicita operaciones mediante interfaces que describen necesidades del dominio, sin referenciar cómo se satisfacen.

1.3 Cómo surge la necesidad de aislar el dominio

El movimiento de Domain-Driven Design (DDD) reforzó la importancia de mantener un modelo de negocio expresivo y autónomo. Equipos inspirados en estas ideas observaron que, si el dominio dependía de frameworks de persistencia o controladores HTTP, el lenguaje ubicuo se contaminaba con terminología técnica. Al aislarlo, se protegía el conocimiento esencial del negocio frente a cambios externos.

Esta evolución se vio influida por voces como la de Alistair Cockburn, quien formalizó el patrón puertos y adaptadores a principios de los 2000. Su propuesta describe una aplicación como un hexágono conceptual: el centro alberga casos de uso y entidades, mientras los lados representan puertos conectados a adaptadores. Cada adaptador puede reemplazarse sin tocar el núcleo, lo que habilita pruebas aisladas y despliegues controlados.

En la práctica, aislar el dominio implica establecer contratos claros. El siguiente ejemplo en Java ilustra un caso de uso de catálogo de productos que se mantiene al margen de las decisiones de infraestructura:

package com.example.catalogo.dominio;

import java.util.List;

public class BuscarProductos {

    private final PuertoCatalogo puertoCatalogo;

    public BuscarProductos(PuertoCatalogo puertoCatalogo) {
        this.puertoCatalogo = puertoCatalogo;
    }

    public List<Producto> porCategoria(String categoria) {
        return puertoCatalogo.obtenerPorCategoria(categoria);
    }
}

package com.example.catalogo.dominio;

import java.util.List;

public interface PuertoCatalogo {
    List<Producto> obtenerPorCategoria(String categoria);
}

package com.example.catalogo.infraestructura.sql;

import com.example.catalogo.dominio.Producto;
import com.example.catalogo.dominio.PuertoCatalogo;
import java.util.List;

public class CatalogoPostgresAdapter implements PuertoCatalogo {

    private final FuenteSql fuenteSql;

    public CatalogoPostgresAdapter(FuenteSql fuenteSql) {
        this.fuenteSql = fuenteSql;
    }

    @Override
    public List<Producto> obtenerPorCategoria(String categoria) {
        return fuenteSql.consultar("SELECT * FROM productos WHERE categoria = ?", categoria);
    }
}

package com.example.catalogo.infraestructura.memoria;

import com.example.catalogo.dominio.Producto;
import com.example.catalogo.dominio.PuertoCatalogo;
import java.util.List;
import java.util.stream.Collectors;

public class CatalogoMemoriaAdapter implements PuertoCatalogo {

    private final List<Producto> productos;

    public CatalogoMemoriaAdapter(List<Producto> productos) {
        this.productos = productos;
    }

    @Override
    public List<Producto> obtenerPorCategoria(String categoria) {
        return productos.stream()
                .filter(p -> p.tieneCategoria(categoria))
                .collect(Collectors.toList());
    }
}

El caso de uso BuscarProductos depende de la interfaz PuertoCatalogo y desconoce si los datos provienen de Postgres, memoria u otra fuente. Esta separación permite escribir pruebas unitarias con el adaptador en memoria y aplazar decisiones tecnológicas hasta etapas posteriores sin comprometer el flujo de negocio.

1.4 Conclusiones y próximos pasos

La evolución histórica muestra que cada avance arquitectónico responde a la necesidad de controlar la complejidad y proteger el dominio. La arquitectura hexagonal cristaliza esa búsqueda al introducir puertos y adaptadores como frontera natural entre lógica y tecnología. En el siguiente tema profundizaremos en las limitaciones del modelo en capas tradicional, identificando los dolores específicos que motivaron este cambio de paradigma.