Apuntes sobre Arquitecturas Limpias (de Software)
Me gustaría compartir algunos apuntes acerca de Arquitecturas Limpias y por qué considero que pueden aportarnos mucho en el desarrollo de software (y trataré de hacerlo mediante bloques como éste)
Pero para hablar de Arquitecturas Limpias es importante empezar entendiendo a qué nos referimos con Arquitectura de Software (AS). La AS se entiende, de forma sintetizada, como un conjunto de especificaciones o reglas que aplicamos voluntariamente a la hora de diseñar las clases y estructuras de una aplicación. Por tanto estaremos hablando en términos de un diseño lógico que aplicamos como desarrolladores que no viene impuesto por la tecnología con la que trabajemos.
Inevitablemente esta práctica implica un mayor esfuerzo y un coste de tiempo inicial, por lo que está más orientada a proyectos a largo plazo en los que si buscamos un mayor nivel de testabilidad y mantenibilidad del código, y no tanto a proyectos más sencillos como podría ser un portfolio web donde no tiene tanto sentido añadir esta ‘dificultad’
Y bien ¿Por qué aplicar Arquitectura de Software?
En cualquier proyecto a gran escala es inevitable encontrarnos con funcionalidades que tienen una cierta complejidad inherente (realizar un cálculo, seguir un flujo condicional..), pero si no establecemos unas reglas de diseño que estén claras, es muy fácil que acabemos encontrando que un problema que podría tener una complejidad inherente mínima se convierte en una ‘Big Ball of Mud’ derivada de una gran complejidad accidental. Esto finalmente se traduce en un mayor coste de tiempo y en el arrastre de gran deuda técnica
Las Arquitecturas Limpias coinciden en la definición de varias capas en la aplicación, las cuales se comunican entre sí y además están sometidas a una regla de dependencia
Regla de Dependencia: Lo que haya en una capa interior no puede conocer lo que haya en otra capa más exterior
Si nuestras Entidades se encuentran en el núcleo de la aplicación o arquitectura, no pueden “salir” a conocer servicios del framework que nos aporte por ejemplo la lectura de cabeceras HTTP o la persistencia en Base de Datos, ya que entonces estaríamos contaminando nuestro Dominio con detalles de la Infraestructura.
En este sentido, los principios SOLID nos ofrecen una buena manera de respetar esta regla, en concreto el Principio de Inversión de Dependencias (DIP) nos sugiere el uso de interfaces, que quedarían dentro del dominio e implementaciones de éstas que quedarían en la infraestructura
¿Qué encontramos en el núcleo de la arquitectura?
Entidades: Son elementos con identidad propia dentro de nuestro dominio (Usuario, Libro, Reserva…), esto implica que un objeto puede estar representado como entidades distintas (Usuario, Jugador, Cliente…)
Interfaces: Serían los contratos (puertos en Arquitectura Hexagonal) que pasaremos a nuestros casos de uso y que nos permitirán desacoplarnos de implementaciones concretas
Servicios de Dominio: Se trata de aquellas reglas de negocio o comportamientos de nuestro dominio que no pueden encapsularse en una entidad, ya se por su complejidad o porque afecte a varias entidades. Este tipo de servicios son completamente ajenos a servicios de terceros
Eventos de Dominio: Son mensajes que notifican acerca de algo que ha sucedido en el dominio y que podría ser interesante para otra parte de la aplicación (Se ha creado un usuario, se ha procesado una operación, se ha prestado un libro…) y por tanto se prestan a generar acciones derivadas
¿Qué encontramos en la capa de aplicación o de caso de uso?
Servicios de Aplicación: Representan los distintos casos de uso que pueden darse dentro de nuestra aplicación. Es decir, son acciones atómicas que los interesados de la aplicación querrían realizar (Crear un usuario, realizar una operación, reservar un libro…). Estos servicios serán los encargados de orquestar la lógica en torno a ese caso de uso concreto como persistir o recuperar información de BD
Objetos de transferencia de datos: Dentro de esta clasificación encontramos una pequeña diferenciación:
- DTOs: Son objetos mutables (podremos modificarlos tras ser instanciados) que nos permiten la comunicación con terceros
- Requests/Responses: Estos objetos son inmutables y están más orientados a la comunicación entre capas de nuestra arquitectura
Buses de comunicación: También pertenecen a esta capa el Bus de Eventos y, si procede, los derivados de aplicar CQRS (Command/Query Responsibility Segregation)
Finalmente, en la capa de Infraestructura se encuentran las implementaciones concretas de las interfaces (contratos), constituyen así los adaptadores (término proveniente de la Arquitectura Hexagonal) para conectar dichas interfaces con los componentes externos (repositorios de BD, sistema de colas, particularidades del framework…)
El hecho de aplicar este tipo de arquitecturas nos brinda importantes beneficios:
- Mantenibilidad: Ya que nuestro dominio no se verá en ningún momento afectado por cambios en la infraestructura
- Cambiabilidad de los adaptadores (Implementaciones concretas)
- Reusabilidad de los puertos (Interfaces / Casos de uso)
- Testabilidad: Al no acoplar nuestros casos de uso a la infraestructura, podremos “mockearla” con facilidad