W. Libardo Pantoja Y.

Arquitectura de Software Hexagonal

¿Qué es la arquitectura hexagonal?

En la arquitectura de software hexagonal el centro de la aplicación es la lógica de negocio. El problema de muchas aplicaciones es que la lógica de negocio está mezclada o contaminada con la interfaz gráfica, con la lógica de bases de datos y con sistemas externos. Esto representa un gran problema pues hace la aplicación más difícil de probar y reutilizar (por las dependencias generadas).
/Blogs/ArquitecturaHexagonal/ArquitecturaHexagonal.png 
Lo ideal es que la lógica de negocio esté en una sola capa. También es importante destacar que la lógica de negocio esté totalmente independiente de la tecnología de bases de datos y de otras capas. Lo único que se admite dentro de la capa de lógica de negocio son interfaces a las capas externas.

A continuación se resumen la arquitectura hexagonal como un patrón:
 
Intención: La intención de la Arquitectura Hexagonal es permitir que una aplicación sea usada de la misma forma por usuarios, programas, pruebas automatizadas o scripts, y sea desarrollada y probada de forma aislada (isolated) de sus eventuales dispositivos y bases de datos en tiempo de ejecución.

Problema: Una de las grandes problemas en las aplicaciones de software ha sido la infiltración de la lógica del negocio en el código de la interfaz de usuario. Esto trae múltiples problemas:
  1.     Dificulta la automatización de pruebas.
  2.     Impide el cambio de uso de la aplicación.
  3.     Dificulta o impide el uso por otro programa.
Otro gran problema es el acoplamiento con detalles de infraestructura como la lógica de acceso a base de datos.

La solución: La Arquitectura Hexagonal, tiene como principal motivación separar la aplicación en distintas capas o regiones con su propia responsabilidad. De esta manera consigue desacoplar capas de la aplicación permitiendo que evolucionen de manera aislada. Además, tener el sistema separado por responsabilidades  facilita la reutilización.

¿Por qué es importante la capa de dominio o lógica de negocio?

Las aplicaciones empresariales actuales se están volviendo más sofisticadas. Pero hay un problema, se está relegando las decisiones de arquitectura a la tecnología en lugar del negocio. Esto hace que las aplicaciones no sean flexibles conforme a la evolución del negocio. Al final, el framework de desarrollo es quien va a definir la evolución de la arquitectura, y esto no es lo adecuado. Ante un desarrollo, el equipo se preguntan de inmediato ¿Cuál es la tecnología que más se está utilizando en la actualidad?, sin llegar a pensar lo que necesita el cliente.  No se tiene en cuenta si la tecnología está inmadura y va a ser obsoleta en poco tiempo. No importa lo que necesites (así sea un simple formulario), se va  a hacer en esta tecnología. Esto conlleva  a decisiones arquitectónicas erróneas. Se introducen deudas técnicas por no darle foco al negocio.

¿Cómo es la comunicación de la lógica de negocio hacia otros elementos?

La comunicación a otros elementos como la base de datos, la interfaz gráfica, sistemas de archivos, etc.,  es por medio de puertos. Sobre estos puertos se tienen los adaptadores. Los conceptos de puertos y adaptadores son los mismos de la vida real, por ejemplo, para que algo externo se comunique a un computador, lo hace a través de un puerto.
/Blogs/ArquitecturaHexagonal/PuertosAdaptadores.png
Cualquier acceso de un elemento externo a la lógica de negocios debe hacerse a través de las API. Por ejemplo, se podría tener una API para comunicarse con los clientes, y una API para comunicarse con los repositorios de datos. Siguiendo con el ejemplo, suponiendo que una GUI (interfaz gráfica de usuario) se quiera comunicar con la lógica de negocio, lo haría a través de un adaptador para que se comunique con la API de clientes.  En el futuro, si quiero cambiar la GUI de una tecnología de escritorio a una interfaz web, simplemente se crea un nuevo adaptador para la parte web y la aplicación sigue funcionando. De igual manera, si se quisiera comunicar otra aplicación (utilizando una API REST), se crearía un adaptador de REST.

El puerto de la izquierda (API clientes) es activo pues alguien externo hace una solicitud que llega hasta la lógica de negocio. En cambio, la API de la derecha API del repositorio, es la lógica de negocio quien inicia la solicitud (guardar o pedir datos).

La API del repositorio podría tener dos adaptadores, uno de MySql para producción y otro adaptador de Mock (con datos parametrizados) para hacer pruebas unitarias.

Comparación con la arquitectura de tres capas

En la arquitectura de tres capas las dependencias se dan entre capas superiores a inferiores. La presentación conoce el dominio y el dominio conoce explícitamente la capa de acceso a datos. En esta arquitectura, a pesar que se puede usar inyección de dependencias, hay acoplamiento entre las tres capas.
/Blogs/ArquitecturaHexagonal/ArquitecturaCapas.png
En la arquitectura en capas, el dominio depende del acceso a datos. En  la arquitectura hexagonal la dependencia se invierte, va desde la lógica de acceso a datos hacia la lógica de negocio. La idea es que la aplicación no conozca al acceso de datos (ni a ninguna otra capa). La capa de negocio debe ser agnóstica de las capas externas.
/Blogs/ArquitecturaHexagonal/PuertosAdaptadoresInversion.png

Actores primarios y secundarios

Los actores primarios son los que invocan (o manejan) la lógica de negocio. En este caso serían la GUI y el cliente REST. Los actores secundarios son invocados por la lógica de negocio, en este caso seria la base de datos MySQL y Mock. Los actores secundarios son manejados o conducidos por la lógica de negocio.

¿Quién se inventó la arquitectura hexagonal?

La arquitectura hexagonal la inventó el norteamericano Dr. Alistair Cockburn en el año 2005. En base a los problemas en el manejo de la lógica de negocio publicó un artículo en su blog.

¿Por qué un hexágono?

El hexágono es sólo un tema de representación visual, pues no significa que se puede tener máximo seis puertos. En realidad se pueden tener uno o más puertos dependiendo de la necesidad. Estos seis lados puede generar confusión, por eso muchas prefieren llamarla arquitectura de puertos y adaptadores.

¿Cómo se modela el bloque de lógica de negocio de la arquitectura hexagonal?

El autor no habla nada sobre cómo modelar la lógica de negocio. En este sentido hay propuestas concretas sobre cómo hacerlo. Una es el Domain-Driven Design (DDD) que habla sobre agregados, entidades, objetos de valor  y otros conceptos.  Por otro lado, existen las arquitecturas limpias o clean architectures que muestra detalles de cómo sería una buena implementación de la arquitectura hexagonal. Estos dos enfoques dan detalles sobre cómo se debería diseñar el bloque central de esta arquitectura hexagonal y cómo debe ser el bloque que envuelve la lógica de negocio.

Un ejemplo sencillo

Supongamos que se está implementando una nueva funcionalidad en la plataforma de Tareas Pendientes (ToDo) que permite a un usuario tener una lista de tareas e ir marcando las que ya realizó (similar a las tareas de google).
El hecho de que se tenga que persistir los datos de una tarea es una regla de negocio. No obstante, cómo debe persistir es algo ajeno a la lógica de negocio, un simple detalle de implementación irrelevante para la lógica de negocio. Será por lo tanto el módulo de persistencia quien defina como persistir (adapter).
Teniendo esto en cuenta en la capa de negocio se podría definir una interfaz que será el puerto:
public interface TodosRepository {
    List<TodoSummary> readTodos();
    Todo readTodo(Long id);
    Long create(Todo todo);
    Long update(Todo todo);
    void delete(Long id);
}
 
Y en el módulo de persistencia el adapter que implemente esa interfaz y persista es el siguiente:
public class TodosRepositoryImpl implements TodosRepository {

    @Inject
    @Repository
    private TodosHibernateRepository todosHibernateRepository;
   

    @Override
    public List<TodoSummary> readTodos() {
        List<TodoHibernate> todosHibernate = (List<TodoHibernate>) todosHibernateRepository.findAll();
        List<TodoSummary> summaries = todosHibernate.stream()
                .map(todoHibernate -> new TodoSummary(todoHibernate.getId(), todoHibernate.getTitle())).collect(Collectors.toList());
        return summaries;
    }
    @Override
    public Long create(Todo todo) {
        TodoHibernate todoHibernate = TodoHibernateMapper.fromTodo(todo);
        TodoHibernate todoResp = this.todosHibernateRepository.create(todoHibernate);
        return todoResp.getId();

    }
    @Override
    public Todo readTodo(Long id) {
        TodoHibernate todoHibernate = todosHibernateRepository.findById(id);
        if (todoHibernate == null ){
            return null;
        } else {
            return TodoHibernateMapper.fromTodoHibernate(todoHibernate);
        }
        ...
    }
}
Dentro del negocio, quien quiera que use a TodosRepository como colaborador, podrá recibir una de estas implementaciones. Esto  permite mediante inyección de la dependencia, usar cualquiera haciendo, que el negocio sea independiente del cómo se persiste.

En conclusión

La arquitectura hexagonal favorece el atributo de calidad de modificabilidad, por los siguiente:
  • Obliga a que el dominio no esté acoplado a nada externo mediante el uso de interfaces propias del  dominio que son implementadas por elementos externos.
  • Facilita estar desacoplado del método de entrega, haciendo que sea más sencillo que un caso de uso funcione para un App móvil, API, una web tradicional, una web single App por REST, etc.
  • Por otro lado permite estar preparado para cambiar los detalles de implementación tales como la persistencia o el framework. El negocio puede evolucionar de manera independiente a la tecnología de persistencia.
  • Como cualquier arquitectura basada en la inversión de dependencias facilita que los componentes se puedan testear con pruebas unitarias automatizadas.
para más información de la arquitectura hexagonal recomiendo este blog: Build Maintainable Systems With Hexagonal Architecture. Si quieres leer temas de arquitecturas de software recomiendo el blog de Martin Fowler
En el siguiente blog se explicará el ejemplo de tareas pendientes, que implementa la arquitectura hexagonal, de manera detallada.

Referencias 

  1. Qué es la Arquitectura Hexagonal? | Puertos y Adaptadores. https://youtu.be/VLhdDYaW-uI.
  2. Arquitectura hexagonal o patrón de puertos y adaptadores. https://medium.com/@edusalguero/arquitectura-hexagonal-59834bb44b7f
  3. Hexagonal Architecture: three principles and an implementation example.
    https://blog.octo.com/en/hexagonal-architecture-three-principles-and-an-implementation-example/
Universidad del Cauca - Facultad de Ingeniería Electrónica y Telecomunicaciones - Dpto Sistemas