¿Deberías implementar arquitectura limpia en Flutter?

Daniel Herrera Sánchez
Bancolombia Tech
Published in
7 min readApr 25, 2021

Después de leer “Clean Architecture — Aislando los detalles”, nos cuestionamos como equipo de desarrollo frontend … ¿Será que podremos utilizar esta arquitectura en nuestras soluciones móviles? ¿Qué ventajas traería Flutter? ¿Hará más complicadas nuestras implementaciones?

Acompáñanos en esta historia. Te contaremos cómo nos fue, cómo estructuramos nuestro proyecto, y las conclusiones finales de esta agradable travesía.

Aislando los detalles … ¿Algo nuevo en el mundo “frontend”?

Aislar los detalles no es algo nuevo en el frontend, de hecho, ya teníamos en nuestras implementaciones algunas soluciones que nos ayudaban a independizar responsabilidades; tales como arquitectura viper o modelo vista controlador.

Sin embargo, la arquitectura limpia nos introdujo un concepto muy importante; el lenguaje ubicuo en todas las capas de un producto digital. Tanto el negocio como los desarrolladores tenían claras las entidades y los casos de uso. Gracias a esto podemos tener un mejor trabajo de equipo.

Como aparece en la imagen, las capas internas son independientes de las externas. De forma que las capas de casos de uso, entidades y controladores, no son afectados por los cambios en UI. También podemos decir que; si el frontend desea consumir a un API diferente, no cambiamos el esquema; a no ser que existan cambios relevantes en el caso de uso.

Por estas ventajas nos motivamos a probar esta arquitectura en Flutter …

Definamos el proyecto

Utilizamos un API público que contiene la lista de unos álbumes y sus fotos: https://jsonplaceholder.typicode.com/photos.

Cuando ingresamos un id específico nos devolvió la siguiente información:

https://jsonplaceholder.typicode.com/photos/2

Entonces, hicimos una aplicación que renderizara este elemento siguiendo la arquitectura limpia.

Construyendo nuestra capa de Dominio

La capa de Dominio contenía los casos de uso y los modelos.

Capa de modelos

Dentro de modelos estaban todas nuestras entidades con los atributos establecidos por el negocio.

¿No sienten que nos faltó algo en la capa de modelos?

¡¡Así es!! Nos faltaron nuestros enrutadores. Estos nos dirán qué podemos hacer con la entidad álbum, mas no el cómo. En Dart no existen las interfaces como en JavaScript o Java, por lo tanto, utilizamos algo similar;
las clases abstractas.

Capa de casos de uso

Como su nombre lo dice, acá implementamos los casos de uso de cada una de las entidades.

Para el caso de nuestro álbum obtuvimos lo siguiente:

Como vemos, pidió como entrada una clase del tipo AlbumGateway. De forma que si tenemos dos APIs diferentes, pero se extienden de esta, podrán servir como entrada para el caso de uso. Así las dos APIs obtengan o procesen de forma diferente los datos, al final deberán devolver la entidad tipo Album cuando se utilice la función getAlbumByID. Esto permitió que, al cambiar más adelante elementos en la capa de infraestructura; el impacto fuera menor. De hecho, la capa de dominio no debería verse afectada por cambios en sus capas externas.

¡¡Ahora si!! Construyamos nuestra capa de infraestructura

Espero que no estés sudando, ten paciencia ya vamos por medio camino y ahora …¡¡lo bueno está por llegar!!

En esta capa debían ir nuestros puntos de entrada, adaptadores y los helpers transversales a estos elementos. Sin embargo … ¿Debería tener una aplicación frontend puertos expuestos para el consumo? Al menos en nuestras soluciones no es así. En la infraestructura, nuestra distribución de carpetas quedó así:

Implementando nuestros “helpers”

Cuando comenzamos este análisis, definimos que partiendo de una API pública obtendríamos los datos del álbum. En ese momento vimos que la información entregada por el servicio resultó ser un json, entonces necesitamos ayuda para convertir ese json en una entidad; ahí es en donde entraron en escena nuestros helpers.

Los helpers nos ayudaran con formateos, mapeos y otras operaciones útiles para nuestra capa de infraestructura.

Para el caso que estamos analizando solo necesitamos crear los mappers.

Estructura carpeta “helpers”

En la carpeta de comunes construimos una base para todos nuestros mappers, la cual es la siguiente:

Dado un json, devolvimos una entidad. Creamos nuestro AlbumMapper, para el cual implementamos el BaseMapper; en el cual introdujimos un json para que nos devolviera una entidad tipo Album.

Los adaptadores nos conectan con el mundo

¡En efecto! los adaptadores nos conectaron con nuestras APIs y repositorios. Si nuestra base de datos cambiara o tuviéramos una nueva API, solo se vería afectada la capa de infraestructura. Dicho impacto nos haría crear un nuevo adapter o repository. Teniendo esto claro, veamos cuál sería la estructura de carpetas en esta capa:

Las carpetas deben ser descriptivas, de forma que con solo leerlas sepamos el contenido. Album API nos permitió conectarnos a nuestro endpoint. Se debe extender del gateway para servir como entrada a nuestros casos de uso.

La clase AlbumApi se extiende del AlbumGateway, adicionalmente utiliza las bondades de la capa helper para hacer el mapeo del servicio a la entidad. En este caso solo mostraremos cómo implementar getByID … ¿Te atreves a hacer un fork de nuestro repo e implementar la función getAll?

La capa UI ¿El fin de todo?📱

Si has llegado hasta aquí queremos felicitarte, tienes el deseo sincero de aprender💪💪. Ahora continuaremos con nuestra capa de UI. En esta capa deberán ir todos los elementos relacionados a la parte visual, arquitectura limpia no dice cómo debemos descomponer esta capa. Sin embargo, para el ejemplo lo hicimos de la siguiente forma …

  • common: contiene los widgets comunes con todas las páginas.
  • helpers: los helpers que ayudan a los elementos gráficos .
  • model_view: son widgets relacionados directamente con la entidad. Por ejemplo, una tarjeta que muestre la información de álbum sería un model_view de álbum.
  • pages: son las páginas de nuestra experiencia digital

recuerda que cada necesidad podría estructurar diferente esta capa.

Sin embargo, queda una inquietud… ¿Cómo comunicamos a la capa UI con sus interiores de forma que no quede fuertemente atada a la capa de infraestructura?

La capa de configuración

En la capa de configuración es donde vamos a hacer nuestra inyección de dependencias. Existen varias librerías que permiten hacer este tipo de inyección; pero de forma nativa, a la fecha de publicación este artículo, no contamos con esta herramienta. Por lo tanto, decidimos crear una capa de configuración para no tener dependencia de terceros.

En el archivo de configuración haremos una inyección directa de nuestra API de la siguiente forma:

Esto es muy ventajoso porque si cambiamos la API, pero sigue devolviendo la misma entidad, solo tendríamos que hacer cambios sobre esta capa; conociendo la clase inyectada en GetAlbumUseCase.

El front solo debe depender de esta capa de configuración y utilizar los casos de uso definidos en GetAlbumUseCase, a continuación un ejemplo:

Y ahora, ¿qué sigue?

No explicamos en detalle la implementación de la capa UI, ya que deseábamos enfocar este artículo en arquitectura limpia. Sin embargo, si eres de los curiosos entra a el link del repositorio justo aquí.

Por otro lado, te dejamos un reto …¿Te atreves a implementar usando arquitectura limpia el caso de uso getAll(), dibujarlo en pantalla y compartirlo con la comunidad?

Si cumples el reto no olvides contarlo en la caja de comentarios para que podamos ver cómo lo solucionaste.

Conclusiones

  • Arquitectura limpia se acopla perfectamente con Flutter y no resulta en algo complejo o bloqueante.
  • La capa de UI puede ser estructurada de formas diferentes dependiendo de las necesidades de cada proyecto.
  • Dado que no existe una herramienta nativa para la inyección de dependencias, se creó una capa de configuración para ejecutar esta tarea.
  • No existe una única forma de implementar arquitectura limpia en Flutter, lo importante es respetar los principios de la misma.

Próximamente publicaremos cómo unir TDD a esta arquitectura, por ahora cuéntanos qué tal te pareció el artículo. Si tienes algunas dudas, escríbelas en los comentarios. ¡¡Por favor regálanos +50 aplausos 👏!!

--

--

Daniel Herrera Sánchez
Bancolombia Tech

Flutter,Dart@GoogleDevExpert💙 • 🔴Youtube Channel Weincode •👨🏻‍💻FlutterMedellin Community Lead • 🙅🏻‍♂️Angular Content creator • Speaker •GitHub: weincoder