Arquitectura de Software - Arquitectura Hexagonal

La Arquitectura Hexagonal, también conocida como “Arquitectura de Puertos y Adaptadores”, es un modelo para crear sistemas desacoplados y fáciles de mantener, basándose en la separaración de las responsabilidades del núcleo del negocio, de los detalles técnicos (se considera detalles a todo lo externo al negocio… como la base de datos, la interfaz de usuario, servicios web).

arquitectura hexagonal

Fue propuesta por Alistair Cockburn en 2005, como un conjunto de ideas, patrones, una filosofía de diseño de software, y poco a poco fue ganando popularidad (hoy es una arquitectura muy usada en microservicios).

¿Por qué Hexagonal?

El término “hexagonal” tiene que ver con la idea de que la lógica de negocio está en el centro de la aplicación y que cada lado representa un puerto de entrada/salida de la aplicación, y una forma poligonal expresa mejor que un círculo este principio.

Estos puertos son el punto de entrada o salida de detalles externos, mecanismos que interactúan con nuestro sistema, son las abstracciones (interfaces) de comunicación.

Cada uno de estos puertos tiene asignado uno o varios adaptadores, las implementaciones concretas, por ej. las querys para insertar en una base de datos MySQL.

Esto es más flexible que un modelo en capas tradicional, donde la interacción suele ser lineal y en un solo sentido.

Motivación

Una de los grandes problemas de los sistemas de software es la inclusión de lógica de negocio en el código fuente de la interfaz de usuario, lo que origina que:

  • El sistema no se puede probar con suites de pruebas automatizadas, porque parte de la lógica que se necesita probar depende de detalles visuales como el ingreso de datos, el tamaño de los campos, el posicionamiento de los botones, la base de datos.
  • Por la misma razón, resulta imposible pasar de un uso del sistema por parte de un ser humano, a una ejecución automatizada.
  • A la hora de hacer cambios de tecnologías, mejorar la interfaz o cambiar el proveedor de base de datos, hay que volver a codificar gran parte del sistema (sino todo).

La arquitectura hexagonal propone hacer foco en estos temas, con la idea de facilitar el desarrollo, el mantenimiento y las pruebas. Que sea independiente de los posibles dispositivos y bases de datos para desarrollar… que se pueda adaptar a nuevos detalles externos sin tener que volver a codificar los casos de uso (plugins, conectar y desconectar detalles)… y que se pueda probar sin tener que disponer de todos los elementos.

Los 3 Conceptos Clave

  1. Dominio: En el corazón de la arquitectura hexagonal está el dominio de la aplicación, que contiene la lógica de negocio, la razón de la existencia del sistema. Este dominio debe ser independiente de detalles externos como bases de datos, interfaces de usuario, etc.
  2. Puertos: Son abstracciones a través de las cuales el dominio de la aplicación interactúa con el mundo exterior. Hay dos tipos de puertos:
    • Puertos Primarios: Definen cómo el mundo exterior interactúa con la aplicación (por ejemplo, a través de una API).
    • Puertos Secundarios: Definen cómo la aplicación interactúa con el mundo exterior, como bases de datos o servicios externos.
  3. Adaptadores: Son los componentes que implementan los puertos secundarios y conectan el dominio con las tecnologías externas. Al igual que los puertos, hay dos tipos de adaptadores:
    • Adaptadores Primarios: Adaptan las entradas del mundo exterior al dominio de la aplicación (p.ej., controladores en una aplicación web).
    • Adaptadores Secundarios: Adaptan las salidas del dominio a los sistemas externos (p.ej., implementaciones de persistencia).

Gráficamente

arquitectura hexagonal

Vemos una aplicación con dos puertos activos y varios adaptadores para cada puerto. Los dos puertos son el lado que controla la aplicación y el lado para recuperar datos.

Este dibujo muestra que la aplicación puede ser dirigida de igual manera por una suite de pruebas automatizadas de regresión a nivel de sistema, por un ser humano, por una aplicación http remota, o por otra aplicación local.

En el lado de los datos, la aplicación se puede configurar para ejecutarse desacoplada de bases de datos externas, utilizando una base de datos de sustitución MySQL en memoria, o mock, o se puede ejecutar con la base de datos de pruebas, o con la de producción.

La especificación funcional de la aplicación, puede que en casos de uso, se hace contra la interfaz del hexágono interno, y no contra ninguna de las tecnologías externas que se podrían utilizar.

La regla de la dependencia

En el siguiente artículo hablaremos de Arquitectura Limpia (Clean Architecture) y veremos que la arquitectura hexagonal es una de las implementaciones más puras que podemos encontrar de Clean Architecture.

La distribución de las capas, su contenido, y las reglas de dependencia, son practicamente las mismas.

Acá hago una pausa, no todo el mundo considera que la arquitectura hexagonal sigue la regla de la dependencia, que dice que las capas externas conocen a las internas, pero no al revés. Dependiendo de donde se coloquen los puertos puede que no se respete esta regla (si las reglas de negocio usan puertos, y los puertos se colocan afuera de la capa de negocio, no se respeta la regla de la dependencia). Personalmente considero que debe respetarse, pero queda a criterio de cada uno.

Ventajas

  • Desacoplamiento: La lógica de negocio está completamente separada de las preocupaciones externas.
  • Flexibilidad: Cambiar la forma en que la aplicación interactúa con el mundo exterior (cambiar de una base de datos a otra) es más fácil, ya que solo requiere cambios en los adaptadores.
  • Facilidad de Pruebas: La lógica de negocio puede ser probada de forma aislada, sin depender de elementos externos como bases de datos o servicios web.

Implementación

Si bien la arquitectura se dibuja como un hexágono, a la hora de implementarla se parece más a una arquitectura de capas, vamos a verlo con un ejemplo:

arquitectura hexagonal

 

Descripción de la Estructura:

  • dominio/ : Contiene las clases de dominio (Pedido, Articulo) que definen la lógica de negocio central y las entidades del sistema.
  • aplicacion/ : Incluye clases como ServicioPedido que forman parte de la lógica de la aplicación, gestionando la interacción entre el dominio y los adaptadores.
  • puerto/ : Alberga interfaces como RepositorioPedidos, que definen los contratos para la interacción entre la lógica de negocio y los adaptadores externos.
  • adaptadores/ : Contiene implementaciones concretas de los puertos, como RepositorioPedidosMemoria, que se conectan con infraestructuras externas (bases de datos, sistemas de archivos, APIs externas, etc.).
  • main/ : Tiene la clase Main, es el punto de entrada de la aplicación, la clase sucia (porque conoce todo), dónde se configuran e inician todos los componentes necesarios.

Código Java para los Archivos

Pedido.java (Dominio)


    package restaurante.dominio;
    
    public class Pedido {
        private Articulo articulo;
        private int cantidad;
    
        public Pedido(Articulo articulo, int cantidad) {
            this.articulo = articulo;
            this.cantidad = cantidad;
        }
    
        // Getters y otros métodos si son necesarios
    }
              

Articulo.java (Dominio)


    package restaurante.dominio;
    
    public class Articulo {
        private String nombre;
        private double precio;
    
        public Articulo(String nombre, double precio) {
            this.nombre = nombre;
            this.precio = precio;
        }
    
        // Getters y otros métodos si son necesarios
    }
              

ServicioPedido.java (Aplicación)


    package restaurante.aplicacion;
    
    import restaurante.dominio.Pedido;
    import restaurante.puerto.RepositorioPedidos;
    
    public class ServicioPedido {
        private RepositorioPedidos repositorioPedidos;
    
        public ServicioPedido(RepositorioPedidos repositorioPedidos) {
            this.repositorioPedidos = repositorioPedidos;
        }
    
        public void guardar(Pedido pedido) {
            repositorioPedidos.guardarPedido(pedido);
        }
    }
              

RepositorioPedidos.java (Puerto)


    package restaurante.puerto;
    
    import restaurante.dominio.Pedido;
    
    public interface RepositorioPedidos {
        void guardarPedido(Pedido pedido);
        // Otros métodos si son necesarios, como obtenerPedido, eliminarPedido, etc.
    }
              

RepositorioPedidosMemoria.java (Adaptador)


    package restaurante.adaptadores;
    
    import restaurante.dominio.Pedido;
    import restaurante.puerto.RepositorioPedidos;
    
    public class RepositorioPedidosMemoria implements RepositorioPedidos {
        @Override
        public void guardarPedido(Pedido pedido) {
            // Implementación de guardar en memoria
        }
    
        // Implementación de otros métodos si son necesarios
    }
              

Main.java (Main)


              package restaurante.main;
              
    import restaurante.adaptadores.RepositorioPedidosMemoria;
    import restaurante.aplicacion.ServicioPedido;
    import restaurante.dominio.Articulo;
    import restaurante.dominio.Pedido;
    
    public class Main {
        public static void main(String[] args) {
            RepositorioPedidosMemoria repositorio = new RepositorioPedidosMemoria();
            ServicioPedido servicioPedido = new ServicioPedido(repositorio);
    
            Articulo articulo = new Articulo("Pizza Margarita", 8.99);
            Pedido pedido = new Pedido(articulo, 2);
            servicioPedido.guardar(pedido);
    
            // Otros códigos para iniciar la aplicación
        }
    }
              

Este código refleja la estructura de directorios y las responsabilidades de cada componente dentro de la arquitectura hexagonal.

Cada clase está ubicada en el paquete correspondiente, y las clases de dominio, aplicación, puerto, adaptadores y main se encargan de sus roles específicos dentro del sistema.

Lo que no te dicen pero debés considerar… 

La arquitectura hexagonal, como cualquier patrón de diseño arquitectónico, tiene sus desventajas y limitaciones, que son importantes considerar al decidir si es apropiada para un proyecto específico. Algunas de las desventajas incluyen:

  • Complejidad Adicional: agrega complejidad en comparación con estructuras más sencillas. Esto puede ser un exceso para proyectos pequeños o con requerimientos poco complejos.
  • Curva de Aprendizaje: hay una curva de aprendizaje para los desarrolladores no familiarizados con este patrón. Comprender y aplicar correctamente la separación de responsabilidades y la interacción entre puertos y adaptadores requiere un entendimiento claro del diseño.
  • Sobrediseño para Proyectos Pequeños: En proyectos pequeños o poco complejos, el uso de una arquitectura hexagonal puede resultar en sobrediseño, haciendo que el código sea más difícil de entender y mantener de lo necesario.
  • Costo de Desarrollo: La necesidad de definir interfaces y clases adicionales puede incrementar el tiempo y el costo de desarrollo, especialmente al principio del proyecto.
  • Riesgo de Abstracciones Incorrectas: Si las interfaces y abstracciones no se definen correctamente, puede resultar en una arquitectura rígida y difícil de mantener. Es crucial entender bien los límites y responsabilidades de cada componente.
  • Integración y Pruebas: Aunque facilita las pruebas unitarias y de componentes, la arquitectura hexagonal puede complicar las pruebas de integración debido a la cantidad de adaptadores y la separación entre las capas.
  • Rendimiento: En algunos casos, las múltiples capas de abstracción pueden afectar negativamente el rendimiento, especialmente si no se gestionan adecuadamente las interacciones entre las diferentes capas.
  • Gestión de Dependencias: La gestión de dependencias entre las capas puede volverse compleja, sobre todo en sistemas grandes con muchos adaptadores y puertos.

Pero si está bien aplicada…

Cuando se aplica bien, a un caso que realmente la requiera, puede mejorar el proceso de desarrollo:

  • Desarrollo enfocado en la lógica del negocio: Al separar la lógica del negocio de las preocupaciones técnicas permite a los desarrolladores centrarse en el negocio, en los casos de uso, lo que realmente da sentido al sistema.
  • Facilita el trabajo en equipos: La separación de responsabilidades hace que sea más fácil para los equipos trabajar de manera independiente en diferentes aspectos del proyecto.
  • Adaptabilidad a cambios: Arquitectura de plugins, permite adaptarse a cambios en la tecnología, sobre todo de los adaptadores.
  • Facilita las pruebas: Es más sencillo crear pruebas unitarias y de integración, permite probar la lógica del negocio y los adaptadores de manera independiente. 
  • Mantenibilidad: El código generado es más fácil de mantener por estar claramente separado, el sistema está preparado para integrar nuevas tecnologías y enfoques. Permite reemplazar adaptadores sin tener que rediseñar todo el sistema.

Finalizando…

Como arquitectura hexagonal es una guía, un enfoque, no hay una única manera de implementarlo, y Amazon propone la siguiente estructura de carpetas o proyectos:

estructura amazon hexagonal

Acá se puede ver el artículo completo: Amazon Arquitectura Hexagonal 

En Resumen

La arquitectura hexagonal mantiene un enfoque claro en cuanto a la separación de la lógica de negocio y los detalles de implementación, lo que lleva a sistemas más limpios, mantenibles y evolutivos, algo sumamente valioso en aplicaciones complejas y en entornos en los que se espera actualizar las tecnologías externas con el tiempo.