Una de las razones por las que una arquitectura de software es necesaria es que los frameworks en los que habitualmente trabajamos como laravel o symfony promueven un estilo de código que no satisface las necesidades de las grandes aplicaciones.

Cuando trabajamos en un proyecto desde el inicio tenemos todo el control. Según avanza el proyecto se generan un montón de reglas no escritas que aplicamos habitualmente. El problema es que cuando la aplicación crece y tal vez nuevos programadores llegan o los que había se marchan, todas esas reglas no serán necesariamente aplicadas. El código será cada vez más un problema.

Se necesitan por lo tanto principios de organización. El más conocido es MVC.

Los gurús del software nos están siempre diciendo lo que está bien y lo que está mal. Finalmente solo tú como programador sabes lo que conviene a tu aplicación según tus medios y el contexto en general en el que te mueves. Cualquier decisión sobre arquitectura y prácticas de desarrollo en general debe ser tomada, bajo mi punto de vista, siempre tomando en cuenta tu contexto. Por ejemplo no tendrá mucho sentido aplicar DDD si nuestra aplicación es un CRUD con 4 controladores. Aunque por supuesto siempre podrás hacerlo, aunque solo sea por diversión.

MVC no es suficiente

Hay polémica sobre si MVC es arquitectura o no. Desde mi punto de vista no lo es. Lo veo como una guía de diseño como lo puede ser DDD, aunque mucho más simple y no apta para resolver problemas complejos. Por supuesto su aplicación genera una topología que produce modelos, vistas y controladores. En el fondo la discusión sobre si es arquitectura o es un principio de diseño es bastante estéril y no tiene importancia. Es habitual que los programadores nos pongamos muy pesados con los detalles sin importancia. El problema principal que yo veo es que la M de MVC tiene normalmente demasiado trabajo, y es responsable de demasiadas cosas. A menudo algunas de sus responsabilidades se trasladan al controlador creando poco a poco clases demasiado grandes y con demasiadas responsabilidades, lo que lleva al título del siguiente punto:

Difícil hacer tests

MVC viene de los frameworks. Y mucho software está íntimamente ligado al framework en el que fue creado. Este podría ser un proceso de trabajo habitual:

  • Elegir un framework
  • Elegir una librería de persistencia
  • Elegir una librería frontend
  • Instalar el skeleton de la aplicación
  • Borrar el código de demo
  • Auto-generar las entidades
  • Auto-generar los controladores CRUD

Aunque todo esto es muy importante, no es el núcleo de nuestro software. Todas estas decisiones, tomadas a priori, harán más lento y complicado hacer tests ya que estaremos obligados a instanciar todos esos elementos para poder probar una clase. Qué duda cabe que si no vas a hacer tests esto no representará un problema.

En mi experiencia personal estoy en un momento en el que tras rechazar los frameworks, quiero volver a experimentar con ellos, ya que tienen indudables ventajas, por ejemplo llevar a cabo miles de tareas tediosas que no queremos programar por nosotros mismos. Eso está bien, pero es necesario saber establecer los límites de nuestra aplicación y dejar claro su alcance y donde termina el framework.

Los frameworks encapsulan muchos detalles para nosotros, como transformar una request en un tipo de dato como XML o JSON. También la comunicación con la base de datos o el uso del protocolo HTTP. Sin embargo hay que encontrar la forma de trabajar limpiamente con ellos.

Abstracción

Un aspecto muy importante del desarrollo de aplicaciones limpias es la abstracción. Otro problema de los frameworks es que no son capaces de generar abstracción por nosotros. Por ejemplo para hacer una consulta y extraer unos datos a través de la capa de persistencia, el framework nos propondrá elementos muy concretos relativos a bases de datos relacionales, métodos como buildQuery o where ademas de ver los nombres de campos y tablas de bases de datos en el código. No es nada abstracto, son más bien implementaciones muy concretas.

Código acoplado

Otro problema con los frameworks es que tu código está muy acoplado con el llamado delivery mechanism. Me cuesta traducir esa expresión al castellano, tal vez “mecanismo de entrega”. En los frameworks veremos que habitualmente ese mecanismo de entrega es de un solo tipo: La web. Normalmente el controlador expresará lenguaje relacionado con la web y la base de datos. ¿Pero qué ocurre si queremos ejecutar código desde una línea de comandos? Será imposible. El código está muy ligado a un controlador web.

Revelar intenciones

Otro problema es que el código MVC normalmente no muestra la intención del código. En inglés habréis oído hablar de “reveals intentions. Leyéndolo no sabemos de qué trata, qué hace, cuál es la necesidad o su significado. Si observas su árbol de directorios, solo ves carpetas como models, views, controllers que en efecto no revelan nada sobre el comportamiento de la aplicación.

Todo esto puede estar muy bien cuando haces un desarrollo de app muy rápido. El código que obtienes usando la documentación de un framework normalmente es útil para hacer CRUDs.

¿Como arreglar esto?

Arquitectura hexagonal

A menudo instalamos el framework y sentimos que nuestra aplicación ya está funcionando. Creo que en realidad aun no tienes nada aparte de un montón de código que no tiene nada que ver con tu dominio.

Tenemos que saber diferenciar entre lo que constituye nuestra aplicación y todo lo demás. A nuestra aplicación nos referimos con el nombre core:

El core es lo que permite solucionar los problemas concretos a los usuarios de nuestra aplicación en un dominio concreto. También forma parte del core las formas de interacción con el mismo, definidas en los casos de uso.

 

A todo lo demás nos referimos con el nombre detalles (de la aplicación). Es aconsejable que nuestra aplicación no sepa nada del exterior y que sea únicamente a través de interfaces bien definidas donde se produce la comunicación. Es decir que nuestro software es permeable solo en la medida que queremos. En contraste con una aplicación MVC común incrustada en un framework, que está completamente infestada por los detalles de implementación de la infraestructura.

Llamamos infraestructura a todo aquello que es exterior al core. Estos son algunos ejemplos:

  • Comunicar con la web
  • Comunicar con la línea de comandos
  • Relación con el sistema de archivos
  • Comunicación con la base de datos
  • Envío de emails

Un diseño demasiado pobre y compacto arruinará la capacidad de mantener, probar y hacer evolucionar correctamente los sistemas complejos. Para aliviar eso tenemos arquitecturas de capas que permiten separar el código, así como establecer reglas para comunicarse entre ellas, establecer los límites o boundaries en inglés, además de ayudar a colocar nuestro código donde corresponda en lugar de depender de esas “reglas no escritas”.

Hablando de sistemas de capas, Uncle Bob dice algo muy importante que debe destacarse (traslado la idea, no traduzco su frase):

Las dependencias sólo pueden ser de fuera hacia adentro, es decir, un modulo solo puede depender de algo que está en una capa inferior y nunca al contrario. Por lo tanto nuestro core nunca debe saber nada de las capas exteriores.

 

Ports

Para cruzar los límites enviamos mensajes en nuestro código. Estos mensajes pueden ser funciones y argumentos. Los límites de la aplicación están en los inputs ports. Un mensaje viene del exterior y si quieres que tu app se comunique con el exterior hay que establecer un input port que lo permita. Quiero experimentar con esta idea y escribir un post mas adelante sobre lo que puede ser un port usando un framework web.

Adapters

Por ejemplo el caso típico en la web sería un puerto que utiliza el protocolo de comunicación HTTP. Siempre que hay un puerto, hay algún tipo de traducción o transformación que es realizada por un adapter, de tal manera que una web request pueda ser procesada. Un adapter sería por lo tanto un grupo de clases que pueden transformar una petición externa, por ejemplo en la web, en algo que pueda ser tratado por la capa de dominio.

“Ports and adapters” es un alias para arquitectura hexagonal.

 

Los puertos permiten que exista la comunicación y los adapters traducen los mensajes del exterior para que puedan ser tratados en el interior. Por ejemplo una request web será convertida en un objeto command. Este objeto revela sus intenciones gracias a su nombre y ya no tiene nada que ver con el mecanismo de entrega (la web), es por lo tanto self-contained y pierde toda relación con el mundo exterior. Este objeto command es únicamente un mensaje y no realiza ninguna acción. A continuación un handler aceptará el command y hará lo necesario para que la acción se lleve a cabo.

Si la acción implica una persistencia, se usará de la misma forma un puerto de persistencia que puede actuar sobre cualquier tipo destino, por ejemplo una base de datos. La particularidad aquí es la siguiente:

Normalmente en un diseño clásico nuestro dominio (core) tendrá una clase que depende de un repositorio situado en la capa de infraestructura. Por ejemplo:

  • Core
  • usersRepository (Class)
  • Infrastructure
  • queryBuilder
  • entityManager

El usersRepository depende del queryBuilder y del entityManager. Como hemos visto anteriormente nuestro core no puede depender de clases situadas fuera del dominio (en capas externas). Para solucionar este dilema tenemos que usar inversión de dependencias:

  • Core
  • usersRepository (Interface) Nuestro dominio define la interfaz, esta no sabe nada de los detalles de implementación de la persistencia.
  • Application layer
  • usersRepositoryHandler
  • Infrastructure
  • usersRepository (Implementa la interfaz)
  • Podemos añadir cualquier implementación que respete la interfaz para conectar con otro tipo de persistencias.

En nuestro dominio definiremos una interfaz de repositorio en la capa de aplicación y tendremos un handler que usará el repositorio, dependerá de la interfaz, no de la implementación concreta. En la capa de infraestructura tendremos un repositorio que implementa la interfaz para crear un tipo de persistencia concreta.

¿Para qué sirve todo esto?

Separation of concerns. Tendremos varias capas en las que repartir nuestro código:

  • Core
  • Domain layer
  • Application layer
  • Infrastructure

Estamos seguros de poner nuestro código en la capa correcta. Es un principio de organización que ayuda en aplicaciones de cierta complejidad.

Tenemos casos de usos con comandos y handlers que realizan la acción que nuestra aplicación debe llevar a cabo. Esto aísla nuestro core del exterior.

La arquitectura hexagonal soporta muy bien diversos métodos de diseño como BDD, DDD, TDD o CQRS. También podría aplicarse en microservices, obteniendo muchos pequeños hexágonos.

Esto permite escribir tu aplicación como si el mundo exterior no existiera, y a continuación conectarla creando ports y adapters. Esto es muy interesante para hacer tests.

Conclusión

Creo firmemente en las ventajas de posponer siempre que sea posible todas las decisiones relativas a la capa de infraestructura, partiendo de la necesidad de crear un dominio sólido y testeable, estableciendo los puertos y los adaptadores que mas tarde nos permitirán conectar nuestra aplicación al mundo exterior.

Al principio podremos mockear esos boundaries y trabajar cómodamente por ejemplo usando TDD.

Por supuesto, el 90% de este post son ideas traducidas y adaptadas por mi durante el estudio y presentadas aquí para quien pueda sacar provecho de ello. No me doy ningún crédito por ello.

Arquitectura hexagonal es un concepto desarrollado por Alistair Cockburn. Como no, hace falta leer y releer los consejos de Robert C. Martin (Uncle Bob) para entender este ecosistema y en general las buenas practicas de programación orientada a objetos.