Photo by Adeolu Eletu on Unsplash

Un Flutter Más Limpio Vol. 4: Contratando Repositorios

Cómo definimos el uso de repositorios en nuestro proyecto

Marcos Sevilla
Jan 15 · 7 min read

Bienvenidos/as de nuevo a Un Flutter Más Limpio, espero que hasta el momento les haya estado gustando esta serie y que estén aprendiendo bastante.

💡 Recuerden mostrar su apoyo con su respectivo aplauso o reacción y compartir para que más personas puedan tener acceso a esta información.

En el volumen anterior hablamos sobre entidades y empezamos a ver la capa de dominio. Como pueden ver en el título, vamos a seguir con los repositorios… y seguimos haciendo contratos, que si no saben qué son, pueden ver el volumen anterior.

Pero antes de meternos con los repositorios, quiero presentarles algo que no les había mostrado, para que comprendan mejor la estructura Clean que estamos implementando y puedan compararla con alguna otra.

CleanScope

CleanScope Architecture
CleanScope Architecture
Este diagrama igual puede expresarse como los círculos en el diagrama original de Clean Architecture.

A la arquitectura con todos los paquetes y las configuraciones de nuestra preferencia le hemos puesto el nombre de CleanScope. En los siguientes artículos vamos a ahondar mucho más en qué nos gusta usar como herramientas para facilitar el desarrollo ya que sabemos que hay demasiadas opciones, por ejemplo en el manejo de estado.

El propósito de implementar Clean es que todas nuestras capas sean independientes, pero realmente la única capa que tiene completa autonomía es dominio. Esto es porque dominio define tanto los tipos de datos que vamos a ocupar alrededor de nuestra app (entidades) y las acciones que se van a realizar en nuestro proyecto (repositorios y casos de uso).

La capa de dominio define los comportamientos generales de nuestro proyecto, digamos que todas las clases que son base para que este funcione. La capa de datos viene a usar esa capa de dominio, implementándola de la forma que se desee (un paquete en específico).

Para que quede claro…

☝️ La capa de dominio dice (por ejemplo):

“Tengo que desarrollar un API que haga [x acción] y [y acción]

La capa de datos dice:

“Voy a desarrollar mi API en Go con 4 endpoints distintos, uno para…”

Por esta razón es que decimos que nuestra capa de datos implementa los componentes de la capa de dominio. Pero es importante destacar que aunque una capa de dominio puede servirnos para distintas implementaciones de una capa de datos, ambas capas se utilizan cuando las incorporamos en nuestra capa de presentación.

De la capa de datos y presentación vamos a hablar en los siguientes volúmenes, sólo quería que observaran el flujo general. Más adelante vamos a profundizar en la función de cada componente de las otras capas, así que no se preocupen.

Repositorios

☝️ Antes de seguir quiero aclarar que, a como dije desde el primer volumen, esta es una de las implementaciones de arquitectura Clean. Ustedes pueden quitar y agregar más capas de lo que yo estoy estableciendo.

Muy atentos en este momento porque puede que en esta parte se confundan un poco.

Si vieron en el diagrama, los repositorios no están totalmente en la capa de dominio, sino que son “compartidos” a su vez con la capa de datos. Esto no es un error, digamos que hay 2 tipos de repositorios:

  1. Repositorios en capa de dominio: Son clases abstractas, o contratos, y define las propiedades y métodos que va a necesitar nuestro proyecto en un feature en específico.
  2. Repositorios en capa de datos: Son las implementaciones de los contratos que definimos en la capa de dominio.

Por eso definimos que…

Un repositorio es el encargado de traernos los datos.

Pero eso se puede abstraer un poco más. Probablemente en otras implementaciones van a ver que el repositorio hace el llamado a un API utilizando los modelos, y está bien. Pero hay un detalle, la forma en la que él llama a ese servicio externo o API puede variar en implementación según el paquete que se use.

Por ejemplo, y puedo tener un repositorio que traiga datos de un API y puedo implementar eso con el paquete http clásico de Dart. Pero también puede ser que decida utilizar el paquete dio en lugar de http y mi código debe ser lo suficientemente extensible como para cambiar esas implementaciones y no tener problemas.

Ahí vemos que abstraer la lógica solamente en repositorios para traer los datos nos queda un poco corta. Y aclaro que hago esta observación para cuando el proyecto que estén construyendo sea mediano o grande, no es necesario que para cada aplicación pequeña abstraigan tanto.

En CleanScope, vamos a usar datasources para obtener nuestra información con el paquete que queramos, pero eso será en otra ocasión.

El método no necesita cuerpo, de eso se encargan sus implementaciones.

Es por eso que creamos una clase abstracta de repositorio para definir los métodos que necesitamos para traer la información que requerimos. Yo le pongo el prefijo I para especificar que es mi interfaz, así cuando utilice mi repositorio en la lógica de negocio que está en la capa de presentación, tiene un nombre más corto y expresivo.

Tomen en cuenta que hay desarrolladores que utilizan la I como prefijo para la implementación y no para la interfaz, realmente no hay una regla para eso y es un debate que he tenido con Elian.

Me parece más sencillo ya que la interfaz sólo la voy a ocupar en mi capa de datos y en la capa de presentación ya voy a ocupar la implementación en más lugares, entonces me conviene en ese caso un nombre más corto.

Así se vería más o menos la implementación del repositorio en la capa de datos.

Pruebas

Otra de las razones por las que es bueno abstraer la interfaz y no codificar una implementación del MovieRepository de una vez, es que nos facilita bastante las pruebas unitarias a esta clase.

Yo sé que probablemente no hayan escrito muchas pruebas en Flutter, yo tampoco puedo decir que soy muy bueno en eso más allá de las pruebas unitarias que les hago a mis clases y funciones. Pero no significa que no sea bueno hacerlas, por el contrario, si aún no hacen pruebas (aunque sea unitarias) es momento de empezar.

Ya todos lo sabemos, pero nunca está de más volver a mencionar que cuando creamos una clase abstracta estamos creando un contrato para que alguna implementación de esa clase cumpla con los métodos establecidos.

Es por eso que al tener nuestra clase abstracta, podemos extender de la clase Mock del paquete mockito -no necesariamente tienen que usar este, ya es preferencia- e implementar nuestra interfaz y que nuestra clase mock o de prueba tenga los métodos que establecemos en el contrato.

Así…

La clase de prueba se utiliza como dependencia de un caso de uso, pequeño spoiler.

Y con la ayuda de mockito y el paquete de test, tenemos la capacidad de hacerle pruebas a nuestro repositorio para confirmar que todo funcione a como debe.

⚠️ Como usualmente en la capa de dominio o datos no tenemos a Flutter como dependencia, el paquete que uso es test, ya que tampoco incluye Flutter y al igual que las capas que estamos tocando en este momento, están escritas en Dart puro*.

*A menos que ocupen algún paquete que SÍ tenga a Flutter como dependencia.

Recapitulando

Quería dejar algunos puntos más claros porque la capa de dominio se cubre relativamente rápido al ser más teoría y luego definición de todos los contratos que vamos a tener en el proyecto.

Ya vimos la primera parte de los repositorios, que es crear su interfaz o clase abstracta para luego poder implementarlo en la capa de datos (segunda parte) y de paso, podernos facilitar las pruebas.

Esa separación de módulos en capas es el principio de inversión de dependencias de SOLID que mencionaba Elian en el Vol. 2 de esta serie. Nosotros vamos a usar las abstracciones que estamos creando como contratos en dominio para que las implementaciones en la capa de datos sean capaces de sustituirlas al ser del mismo tipo, por así decirlo.

Y esa capacidad de sustitución de las implementaciones a sus interfaces es precisamente el principio de sustitución de Liskov, que también cubrió Elian en el volumen 2.

Y así, mis queridos lectores, es cómo se van cumpliendo las reglas de SOLID en nuestras capas y vamos siguiendo cabalmente Clean.

💡 Nunca olviden que ustedes pueden implementar esta arquitectura más sencilla o compleja, según les convenga, nosotros simplemente les ofrecemos esta propuesta para que aprendan los conceptos y vean cómo los ponemos en práctica.

Lo de siempre…

Si aprendiste algo nuevo y te fue de utilidad, podés compartir este artículo para ayudar a otro/a desarrollador(a) a seguir mejorando su productividad y calidad al escribir aplicaciones con Flutter.

También hay una versión de este mismo artículo en inglés publicado en dev.to. De nada. 🇺🇸

Además, si te gustó este contenido, podés encontrar aún más y seguir en contacto conmigo en mis redes sociales:

GitHub, dev.to, Medium, LinkedIn, Twitter, YouTube.

Originally published at http://github.com.

Comunidad Flutter

Artículos e Historias de la Comunidad de Flutter

Comunidad Flutter

Artículos e Historias de la Comunidad de Flutter

Marcos Sevilla

Written by

Mobile engineer. Minimalist. Writer. Firebender.

Comunidad Flutter

Artículos e Historias de la Comunidad de Flutter