DEV Community

Lucas D
Lucas D

Posted on

Arquitectura Hexagonal: El camino hacia un Software Robusto, Flexible y Fácil de Mantener

Tras mi paso por distintos proyectos de desarrollo de software que aplicaban una “arquitectura hexagonal”, he llegado a la conclusión de que la información que podemos encontrar en internet sobre esta no es precisa, y en muchos casos puede llegar a ser incluso contradictoria, ya que cada programador se forma una idea distinta sobre en que consiste la arquitectura hexagonal y como implementarla en un proyecto real. En este artículo trataré de explicar desde mi punto de vista (formado tras muchas horas invertidas leyendo libros, artículos y todo tipo de contenidos sobre esta, además de su implementación en varios proyectos) en qué consiste y cuando puede resultar útil implementarla.
Aunque mi experiencia se basa en desarrollo backend de aplicaciones web, trataré que este artículo sea agnóstico al lenguaje de programación y al campo en el que se quiera aplicar, y en él compartiré opiniones personales sobre esta arquitectura, con las que no todo el mundo estará de acuerdo, ya que como todo en el mundo del desarrollo de software… hay muchas formas de implementarla, y ninguna de ellas es la correcta 😉

Índice

  1. Historia de la Arquitectura Hexagonal
  2. El Patrón "Ports and Adapters"
  3. Principios de la Arquitectura Hexagonal
  4. Capas de la Arquitectura Hexagonal
  5. Cuándo Utilizar la Arquitectura Hexagonal
  6. Glosario de Términos

Historia de la Arquitectura Hexagonal

Para comprender por qué surge la arquitectura hexagonal, debemos ir algunos años atrás y explicar el desarrollo de las arquitecturas de software desde sus inicios.
A principios del siglo XXI, el mundo del desarrollo de software sufre un cambio de paradigma. La necesidad de crear software de manera más rápida y efectiva hace que se pase del modelo de desarrollo en cascada, donde se considera que las fases del desarrollo son: requisitos, diseño, implementación, pruebas y mantenimiento; a un desarrollo centrado en la interacción con el cliente, donde se va entregando feedback de la aplicación a lo largo de todo su ciclo de vida y se tienen en cuenta factores como la escalabilidad, la capacidad de mantenimiento, la interoperabilidad y la capacidad de adaptación a los cambios futuros en el entorno (The Software Sustainability Model).

Evolucion del desarrollo de software

Una vez adoptado este modelo de “Ciclo de vida del software” y la adopción de las llamadas “Metodologías ágiles” para gestionar los desarrollos, los desarrolladores comenzaron a centrarse en mejorar la arquitectura de las aplicaciones, para hacerlas más mantenibles, tolerantes al cambio y fáciles de testear, con lo que surgieron las “Arquitecturas limpias”. Estas arquitecturas buscaban crear sistemas de software que fueran fáciles de entender, mantener y evolucionar a lo largo del tiempo. Algunas de las arquitecturas más conocidas y utilizadas durante esta época fueron MVC (Model-View-Controller) y “Layered Architecture” o “Onion Architecture”, y algunos de los promotores más conocidos de este tipo de arquitecturas fueron Robert C. Martin (o Uncle Bob), Martin Fowler, Kent Beck…
Sin embargo, en 2006 Alistair Cockburn, un desarrollador que había enfocado su carrera en trabajar con casos de uso aplicados a POO (en 2001 escribió el libro “Writing Effective Use Cases”), publicó en su página web un artículo hablando sobre una nueva arquitectura de capas a la que nombró “Arquitectura Hexagonal”.
La principal diferencia de esta nueva arquitectura con respecto al resto de arquitecturas limpias, es que en ella se buscaba separar la lógica de negocio de los detalles de implementación técnica, lo que hace que la lógica de negocio sea más fácilmente probada, evolucionada y cambiada independientemente de la tecnología subyacente y a su vez mejora la escalabilidad, flexibilidad y mantenibilidad del software.

El Patrón "Ports and Adapters"

La arquitectura hexagonal se basa en el patrón “Ports and adapters”, por lo que también se la conoce como arquitectura de puertos y adaptadores. Este patrón consiste en definir un puerto (interfaz) que puede ser utilizado por distintos adaptadores (implementaciones de esta interfaz).
Con ello, haciendo uso del “Principio de inversión de dependencias” de SOLID que establece que las clases de alto nivel no deben depender de las clases de bajo nivel, sino que ambos deben depender de abstracciones, se consigue que nuestra aplicación se mantenga agnóstica a las implementaciones concretas que implementen estos puertos.
Por ejemplo, suponiendo que nuestra aplicación hace uso de un sistema de mensajería para mandar emails, existirá una interfaz de envío de emails y distintas implementaciones en función del canal por el que se vayan a enviar estos (una para Gmail, otra para Outlook…).

Ports & Adapters

Principios de la Arquitectura Hexagonal

La arquitectura hexagonal busca que una aplicación pueda ser dirigida tanto por usuarios, programas, tests automatizados o scripts, además de ser desarrollada y testeada agnóstica a tecnologías concretas con implementaciones puntuales y bases de datos.
La regla de dependencia establece que cada capa de nuestra aplicación solo debe conocer a clases de la capa inmediatamente más interna.
El dominio de nuestra aplicación es inmutable, y no debe estar condicionado a cambios debidos a factores externos. Únicamente debe variar debido a decisiones propias (de negocio), y no impuestas.

Regla de dependencia

Capas de la Arquitectura Hexagonal

Capa de Dominio

El dominio es la capa más interna. Este contiene toda nuestra lógica de negocio y es agnóstico del resto de capas.
Únicamente debe variar en función de las necesidades de nuestro negocio, y no influido por factores externos.
En esta capa se encuentra nuestro modelo de datos o "Read model1", y a la hora de construirlo hay que tener en cuenta que debemos modelarlo en función de nuestras necesidades y no en función del modelo de nuestra base de datos.
Además, en esta capa encontramos las validaciones de negocio, situadas en “Value Objects3” y “Entidades4”, y los puertos de nuestra aplicación (Interfaces de adaptadores y servicios de dominio).

Capa de Aplicación

La capa de aplicación contiene los casos de uso que necesita cada “Bounded context2” o unidad de nuestro programa.
La función de las clases de aplicación es orquestar las clases necesarias para realizar una acción solicitada por el cliente.
Además, se encarga de traducir los datos primitivos recibidos a objetos manejados por el dominio.
La capa de aplicación nunca debe conocer detalles de infraestructura.

Capa de Infraestructura

La capa de infraestructura es la encargada de la comunicación entre la aplicación y factores externos, y viceversa.
Sus clases se encuentran fuertemente acopladas a tecnologías específicas como frameworks, motores de bases de datos, sistemas de colas concretos, librerías externas…
En esta capa se sitúan los adapters del “Ports and adapters pattern”.
Podemos encontrar además clases que traducen la información recibida/enviada por el cliente, puntos de entrada a nuestra aplicación…

Ejemplos infraestructura

Shared Kernel

El “Shared kernel” es un directorio que contiene clases de propósito general, o clases que permiten la comunicación entre distintos bounded contexts2.
En este directorio podemos colocar por ejemplo clases de utilidades, clases que ayuden a implementar un patrón de diseño concreto, wrappers de librerías externas utilizadas en todo el proyecto (como librerías de validaciones, generadores de UUIDs….) o clases que faciliten la comunicación con otros módulos.
Además, en esta capa podemos situar las conexiones a nuestra base de datos. Por ejemplo, en el caso de utilizar un ORM como JPA o Doctrine, aquí colocaríamos nuestros repositorios y entidades de nuestro ORM acoplados a estas tecnologías, de tal manera que distintos bounded contexts2 de nuestra aplicación accedan a una base de datos concreta desde un mismo punto. Nuestro adaptador de la capa de infraestructura, tendrá inyectados estos repositorios y se encargará de mapear las entidades recuperadas a objetos de dominio.
Por lo general, nuestro Shared Kernel únicamente contendrá clases de dominio e infraestructura, ya que en nuestra capa de aplicación se encuentran las clases relacionadas directamente con cada caso de uso, propias de cada bounded context.

Cuándo Utilizar la Arquitectura Hexagonal

La arquitectura hexagonal conlleva aumentar la complejidad accidental del desarrollo enormemente, creando abstracciones que pueden no ser necesarias en determinados contextos y una estructura de directorios y clases muy grande.
Por otro lado, su estructura permite desarrollar proyectos escalables y con gran tolerancia al cambio, facilitando el trabajo en diferentes partes (o contextos) del proyecto al mismo tiempo y permitiendo ajustes finales en la etapa de desarrollo.
Por tanto, para desarrollos cortos, que no necesitan mantenimiento y tienen un modelo de datos pequeño, seguramente no sea necesario aplicar esta arquitectura, aunque para desarrollos a largo plazo (con previsión de evolucionar y necesidades de adaptación), o proyectos que necesiten una exhaustiva fase de pruebas, puede resultar útil implementarla.

Glosario de Términos

1Read model
Representación optimizada de los datos de un sistema de software que se utiliza específicamente para consultas de lectura.
Este modelo se construye a partir de la información almacenada en el modelo de dominio principal (también conocido como "Write model"), que es la fuente principal de los datos de la aplicación. El proceso de construcción del Read model implica la transformación y proyección de los datos del modelo de dominio principal en un formato más adecuado para las consultas de lectura.

2Bounded context
Se refiere a un límite conceptual que separa el modelo de dominio en diferentes partes, cada una de las cuales representa un contexto limitado dentro del negocio.
Un Bounded context es un espacio autónomo y delimitado en el cual el lenguaje ubica en una forma específica, y en donde el equipo de desarrollo trabaja para resolver un conjunto específico de problemas de negocio.
Cada contexto tiene su propio conjunto de conceptos, términos y modelos de dominio específicos, y puede tener su propia implementación técnica, lo que significa que puede estar compuesto por varios componentes de software, servicios o microservicios.
El propósito de establecer Bounded contexts es permitir a los equipos de desarrollo enfocarse en las áreas específicas del dominio del negocio y evitar la complejidad innecesaria en otras áreas. También facilita la comunicación y colaboración entre equipos de diferentes áreas del negocio, ya que cada equipo está trabajando dentro de su propio contexto acotado, con su propio lenguaje y comprensión específicos.

3Value Object
Objeto que representa un valor único e inmutable dentro del modelo de dominio de una aplicación de software.
A diferencia de las entidades, que tienen una identidad única y mutable, los Value Objects no tienen una identidad única y su valor está determinado únicamente por sus atributos. Por lo tanto, dos Value objects que tienen los mismos atributos se consideran iguales, independientemente de su ubicación o momento en el tiempo en que se crearon.
Los Value objects son inmutables, lo que significa que una vez creados, no pueden cambiar su valor. Esto permite que sean compartidos y reutilizados en diferentes partes de la aplicación sin preocuparse por efectos secundarios no deseados.
Estos pueden contener validaciones propias del objeto al que representan.
Ej: Si hablamos de un VO que representa un email, este podría tener validaciones para que su valor siga el formato válido de estos.

4Entidad
Objeto que representa algo que tiene una identidad única y mutable dentro del modelo de dominio de una aplicación de software.
Las Entidades se utilizan para modelar conceptos dentro del dominio de la aplicación que tienen una identidad única y pueden cambiar con el tiempo. Por ejemplo, en una aplicación de comercio electrónico, el concepto de "Pedido" se puede modelar como una Entidad, ya que cada pedido tiene un identificador único y puede cambiar de estado a medida que avanza el proceso de compra.
La identidad de las entidades no está determinada por sus atributos. Incluso si dos Entidades tienen los mismos atributos, se consideran diferentes si tienen identificadores distintos.
Las Entidades se utilizan para representar objetos complejos dentro del modelo de dominio de una aplicación de software, y su diseño y modelado adecuados son esenciales para una implementación efectiva de DDD.

Top comments (0)