jueves, 27 de octubre de 2022

Desarrollo ágil con Arquitectura Hexa3

 ¿Cómo podemos usar arquitectura limpia para desarrollar ecosistemas de aplicaciones distribuidas con equipos ágiles?

Patrón de tres capas

Antes de contestar esta pregunta anterior, podemos comenzar por… ¿Cómo podemos implementar una arquitectura limpia?

  • Domain: esta capa se encarga del “¿Cómo hacen los casos de uso para cumplir su función?”, es decir: el código de lógica, lógica compartida, de negocio o de dominio. Esto incluye los servicios de dominio y modelo de datos (agregados, entidades, value object, etc.).
  • Infrastructure: aquí va el código de acceso a recursos (acceso a base de datos, recursos, archivos, clientes api para servicios externos, consumo de colas de eventos específicos, etc.).
Ejemplo de 3 capas basado en DDD
Ejemplo de una estructura de backend MERN NestJS en 3 capas

Patrón de puertos y adaptadores

Claro que con separar en capas no alcanza para tener un código limpio y lejos de ser una madeja de código en monolito. Necesitamos seguir un gran principio en el desarrollo de software: “aumentar cohesión y disminuir acoplamiento”; y eso lo podemos hacer aplicando el principio de inyección de dependencias (“Dependency inversion principle”) con el patrón de “puertos y adaptadores”, también conocida como arquitectura de “Ports and Adapters” o como parte de la Arquitectura Hexagonal.

  • Adaptador: Es la implementación de la interfaz, en ella se generará el código específico para consumir una tecnología en concreto. Esta nunca se usará de forma directa desde otras capas, ya que su uso se realizará a través del tipo del puerto.
Ejemplo de puerto/adaptador en tres capas
Ejemplo con el patrón repositorio.
Ejemplo de inyección de dependencia con anotaciones NestJS (MERN stack) donde se inyecta en el dominio la implementación de infra. Se usa el patrón repositorio (código del ejemplo anterior).

La pauta del dominio como núcleo

Además, si nos guiamos por la propuesta de la Arquitectura Hexagonal que consiste en que el “dominio sea el núcleo de las capas y que este no se acople a nada externo”. Entonces será nuestro dominio el que contenga los puertos de entrada (incoming) y salida (outgoing). Es decir que toda acción o evento externo (i.e. como pedidos http) que llega a la aplicación por un puerto, es convertido, a través de un adaptador específico, a la tecnología del evento exterior, pasando por el dominio solo a través de sus puertos. Por ejemplo la GUI es un ejemplo de un adaptador que transforma las acciones de un usuario en la API de su puerto. La inversión de dependencia hace que la capa de aplicación y la capa de infraestructura conozcan a la capa de dominio, pero no a la inversa. Y en el caso de la capa de aplicación y la de infraestructura, es la de aplicación quién puede conocer a la de infraestructura y no a la inversa.

Diagrama de dominio como núcleo

Aplicación hexagonal

Una manera de interpretar e implementar esta arquitectura es considerar cada gran pieza de software (que será un proyecto), como una aplicación que empaqueta un conjunto de funcionalidades bajo algún concepto o dominio de problema.

Ejemplo de un API backend hexagonal

Frontend hexagonal

Inclusive, en aplicaciones cliente servidoras u orientadas a micro-servicios, un frontend puede ser desarrollado como una app hexagonal en sí misma, que depende de otras que forman el backend. Habitualmente no se implementa la arquitectura hexagonal de esta manera y se llega a tener frontends que son una verdadera madeja enredada de código. La propuesta aquí es aplicar arquitectura hexagonal también al frontend.

Ejemplo de una aplicación cliente-servidor
Ejemplo de estructura de un proyecto frontend en React (MERN stack)

Testing por capas

También tenemos que pensar en el testing. Sin ‘test’ no hay software de calidad. Un beneficio importante de esta arquitectura de software es que facilita la automatización de pruebas independientes por capas usando inyección de dependencias de Mocks y/o Stubs.

Ejemplo de full test por capas

Estructora de carpetas

Al implementar la arquitectura en un proyecto recomiendo que quede la distinción explícita en 3 directorios (uno por capa). Un ejemplo es como la siguiente:

Estructura de carpetas en un proyecto Back-end

Red de aplicaciones hexagonales

Entonces… ¿Cómo podemos desarrollar ecosistemas de aplicaciones distribuidas con arquitectura limpia? Lo podemos hacer con esta arquitectura hexagonal, distribuida, orientada a microservicios y microapps. Es decir que, en sistemas mayores o en el escalamiento, se pueden conformar sistemas distribuidos como una red de aplicaciones hexagonales interconectadas. Es decir que podemos desarrollar ecosistemas de aplicaciones interdependientes, aunque robustas, y conformadas por micro-apps (incluyendo micro-frontends de ser necesario) y micro-services hexagonales.

Red de equipos ágiles

En honor al principio de Conway, que dice que las estructuras del sistema desarrollado serán congruentes con las estructuras sociales de la organización que lo produce, es que tendremos un ecosistema de equipos acorde a esta arquitectura. En este sentido, tendríamos equipos encargados cada uno de una o varias Apps relacionadas bajo un mismo dominio de contexto. Estos equipos deberían poder desplegar software de forma independiente, en sus propios repositorios y entregar de forma evolutiva e incremental.

Vertical slicing

Y para entregar de forma evolutiva e incremental, los equipos pueden desarrollar desglosando entregables/soluciones en features con ‘vertical slicing’ que a su vez se pueden desglosar en historias de usuario. En un vertical slice pueden haber una o más historias de usuario y/o historias técnicas para completar una feature completa.

Ejemplo de ‘vertical slicing’ de una feature

Desarrollo evolutivo

Estos entregables/soluciones evolucionarían desde algún MVP a productos más complejos, adaptándose a las necesidades de los usuarios según la población foco de cada momento cronológico del ciclo de vida del producto/servicio.

Conclusión

Finalmente, concluyo que estos patrones de arquitectura, como otros semejantes, ofrecen una manera de aplicar buenas practicas de código para crear código funcional, mantenible, de fácil crecimiento y que satisfaga las necesidades de los usuarios. Lo que se recomienda es usar los principios de desarrollo de software que llevan a este tipo de arquitecturas, en busca de encontrar mejores formas de desarrollar software de calidad.

  • Principio de ‘Dependency Inversion’ con el patrón ‘Ports and Adapters’ y el patrón de diseño ‘Dependency Injection’.
  • El principio de ‘Testing’ con el patrón de diseño ‘Dependency Injection’ (de Mocks y Stubs).
  • Y por último, el clásico principio de “más cohesión y menos acoplamiento”.
  1. Applying UML and Patterns,1997, Craig Larman.
  2. Presentation Domain Data Layering, 2015, Martin Fowler.
  3. Artículo de softwarecrafters: Arquitectura hexagonal frontend.
  4. Article: Domain Driven Design, Project structure.
  5. Article: What is the 3-Tier Architecture? 2012 by Tony Marston.
  6. Artículo en Medium: Arquitectura Hexagonal.
  7. Article: Hexagonal Architecture applied to typescript react project.
  8. Article: “Hexagonal Architecture: three principles and an implementation example”.
  9. Arquitectura Hexagonal con Typescript en APIs web con Nodejs.
  10. Ejemplo de proyecto backend hexagonal/DDD con NestJs: https://github.com/ecaminero/nestjs-ddd
  11. https://javascript.plainenglish.io/applying-atom-design-methodology-and-hexagonal-architecture-using-react-6dbb1863a5d5
  12. https://medium.com/ssense-tech/hexagonal-architecture-there-are-always-two-sides-to-every-story-bc0780ed7d9c
  13. Ejemplo code de clean architecture: https://github.com/bazaglia/shopping-cart