Durante el año 2020, el equipo de TI de Envíame estaba compuesto por un grupo acotado de personas, entre ellas se encontraban principalmente desarrolladores y analistas de soporte.
La piedra angular del desarrollo hasta esos momentos era un proyecto con una arquitectura Modelo Vista Controlador (MVC), heredado de la etapa de MVP, donde Envíame operaba como un equipo “fuertemente acoplado” con una única fuente y organización de requerimientos. En él se hallaban integradas las principales funcionalidades de nuestra plataforma, de las cuales algunas de ellas siguen funcionando en producción a día de hoy.
Esta plataforma se desempeñó de muy buena manera, pero la integración de nuevas funcionalidades, el aumento del volumen de código, y el alto acoplamiento heredado, hizo que se volviera difícil de mantener.
Estas problemáticas, junto con el crecimiento de clientes y transacciones, fueron identificadas por el equipo, llevándonos a rediseñar nuestra arquitectura, pasando de monolitos a microservicios desacoplados y dejando atrás el patrón MVC por Clean Architecture.
Qué es Clean Architecture
Clean Architecture es un patrón de Arquitectura de Software creado por Robert C. Martin, cuyas principales características son:
- La aplicación es independiente de cualquier entidad externa tales como frameworks, librerías o bases de datos
- Código altamente desacoplado que permite realizar modificaciones, nuevas funcionalidades y testear de manera óptima
- La arquitectura está basada en una jerarquía de capas que se comunican por medio de inyección de dependencias
La capa de dominio es la capa más interna de la aplicación, en ella se definen las entidades que representan elementos propios del caso de negocio que se busca resolver. Éstas suelen ser implementadas como clases.
En la imagen anterior se presenta un ejemplo de una entidad en JS, que representa a un videojuego que pertenece a una compañía, se encuentra disponible en ciertas plataformas y posee un título y un año. Estas entidades pueden implementar métodos que satisfagan reglas de negocio básicas que se relacionen con los atributos de la misma, para nuestro ejemplo se podría añadir un método que obtenga el título del videojuego o que verifique que una plataforma exista o no en el array platforms.
La capa de casos de uso encapsula la lógica de negocio de la aplicación en funciones que corresponden a determinadas reglas de negocio del contexto que se esté trabajando.
En el siguiente ejemplo se representa la capa de casos de uso para el módulo de videojuegos.
El método addPlatform de la clase VideoGamesUsecases implementa la lógica referente a añadir una nueva plataforma a un video juego. En este caso el método se encarga de:
- Obtener el video juego solicitado y verificar su existencia
- Verificar que la plataforma que se quiere añadir no se encuentra ya almacenada
- Añadir la plataforma al video juego
- Actualizar el video juego en la base de datos
El método databaseRepository.getVideoGameById retorna una variable llamada videoGame que es una instancia de la entidad VideoGame, por lo cual en el caso de uso se pueden llamar todos los métodos implementados para esa entidad, como es el caso del método videogame.isPlatformAssociated(platform).
Una característica importante de la clase VideoGamesUsecases es que inyecta como dependencia otra clase llamada databaseRepository, ella puede implementar métodos para cualquier servicio de bases de datos.
Al caso de uso VideoGamesUsecases le corresponde implementar lógica de negocio y llamar a los métodos de los repositorios, pero no conocer cómo están implementado internamente, esa lógica se implementa en la definición de dichos repositorios.
La capa más externa implementa todas las funcionalidades referentes a frameworks y dependencias de infraestructura, en ella se programan conexiones a las bases de datos, configuraciones del servidor, entre otros. Esto nos da la posibilidad de poder modificar las dependencias y frameworks utilizados en la aplicación sin causar mayores problemas, ya que los casos de uso se encuentran aislados.
A día de hoy, luego de algunos meses de haber comenzado a programar proyectos de backend con Clean Architecture, podemos hacer un balance entre las cosas positivas y negativas que ha conllevado esta decisión en el equipo.
Partiendo con las cosas positivas, vale destacar que la estandarización de la forma de estructurar los proyectos ha permitido que heredar proyectos entre desarrolladores sea mucho más sencillo, ya que la separación por capas, por contextos y la abstracción de la lógica de negocio se respeta en todos los nuevos desarrollos. Esto también permite que la labor de mantener el código y añadir nuevas funcionalidades a desarrollos ya creados sea mucho más amena.
Implementar cada capa con clases aisladas permite poder testear cada capa de manera independiente, la inyección de dependencia facilita la labor de testear con mocks o con servicios montados en ambientes de pruebas dockerizados. Se puede testear cada capa con tests unitarios o varias capas funcionando juntas con tests de integración.
Otro punto positivo de Clean Architecture por sobre el MVC, es que nos ha permitido implementar un backend completamente aislado del frontend, lo cual se adapta mucho mejor a los microservicios y ha permitido mayor autonomía entre nuestros desarrolladores frontend y backend.
Con este patrón como base hemos comenzado a plantearnos nuevos objetivos tales como realizar desarrollos orientados a tests y sentar las bases para nuestros procesos de Integración Continua, ¡tema que abordaremos en otra entrada de blog!
Como aspectos negativos se pueden mencionar cosas como la complejidad de implementación del patrón, el manejo de varios archivos para implementar una sola funcionalidad y la curva de aprendizaje media/alta.
Otro punto importante a mencionar aquí es que Clean Architecture no es la solución a todos los problemas de Software ni mucho menos, y que en casos en los que la complejidad de una solución se vuelve considerable suele ser muy buena idea revisar algún patrón de diseño que se adapte a la problemática e integrarlo dentro del proyecto.