Skip to content

Instantly share code, notes, and snippets.

@marcossevilla
Created February 4, 2021 17:28
Show Gist options
  • Save marcossevilla/1a69e893988a480c03cd078b7d2ac0e5 to your computer and use it in GitHub Desktop.
Save marcossevilla/1a69e893988a480c03cd078b7d2ac0e5 to your computer and use it in GitHub Desktop.

Vol. 6: Modelando datos

Una vez finalizada la capa de dominio y todos los comportamientos generales de nuestro proyecto mediante abstracción, ya podemos empezar a usar todas esas interfaces que hicimos para crear sus implementaciones.

Y bueno, al fin cambiamos de capa. Esta vez le toca a la capa de datos, empezando por los modelos.

Capa de datos

Vol%206%20Modelando%20datos%20a17c23da61d3489ca4976d210b21d604/data.png

En la capa de dominio la base eran las entidades. Estas definían los tipos de datos propios en el proyecto y sus propiedades esenciales.

En la capa de datos pasa de la misma manera, debemos de establecer los tipos de dato que vamos a manipular en los métodos que ocupa un repositorio (ahora implementado) al traer datos.

En este volumen vamos a hacer bastante referencia al volumen 3. Si no lo han leído, aquí les dejo el link.

En esta nueva capa ya nos encontramos con nuestros componentes que ya se comunican directamente con un API externo de cualquier tipo, ya sea Firebase, un REST API, etc.

La construcción de la capa de datos se basa en primero crear los modelos, luego creamos los datasources que hacen la petición de datos directamente y finalmente mandamos a llamar un datasource desde el repositorio para que nos devuelva información, ya sea en datos primitivos o modelos.

Aclaro que los repositorios son implementaciones de otros repositorios abstractos y los modelos son herederos de entidades (que son clases como tal, no interfaces), pero eso no significa que los datasources sean una sola clase y ya.

Los datasources también deben tener una clase abstracta para poder hacer lo mismo que con los repositorios: poder implementar con un paquete distinto su interfaz sin afectar alguna capa externa y poder realizar mejores pruebas en base a la interfaz.

Ya vamos a los modelos.

https://media.giphy.com/media/l0HlFZ3c4NENSLQRi/giphy.gif

Modelos

Supongo que ustedes ya han definido alguna carpeta en sus proyectos llamada models. Aquí realmente no hay mucha diferencia, con nuestras entidades queríamos dejar lo menos posible en propiedades y métodos porque nuestros modelos se iban a encargar de agregar esa funcionalidad.

Un método constructor muy famoso en Flutter y Dart es el fromJson. Este constructor nos permite parsear un JSON decodificado a un Map y así serializar nuestros datos cuando construimos el objeto.

Ese tipo de funcionalidad es la que contiene un modelo, ya que podemos tener múltiples modelos que hereden de esa entidad y definan un comportamiento distinto basado en su implementación.

Además nos permite tener entidades que pueden utilizarse independientemente de si contacta a un backend alojado en Firebase o si es un REST API propio.

Vol%206%20Modelando%20datos%20a17c23da61d3489ca4976d210b21d604/carbon-3.png

StoreItem va a ser la entidad que usamos de ejemplo. Nosotros vamos a hacer nuestros modelos en base a lo que esta entidad defina, entonces creamos primeramente un modelo orientado por si viene de un API, al que vamos a nombrar StoreItemAPIModel.

Vol%206%20Modelando%20datos%20a17c23da61d3489ca4976d210b21d604/carbon.png

Sólo debemos extender o heredar de StoreItem para tener todas nuestras propiedades.

Noten que el constructor crea los parámetros dentro de sí y no vuelve a declarar propiedades que ya definimos en la entidad, sólo tenemos que enviar estas variables en el constructor a la clase padre llamado a super.

Igualmente añadimos un constructor extra, el famoso fromJson. Este constructor es la funcionalidad adicional que va a contener nuestro modelo con respecto a la entidad.

Y si queremos hacer otro modelo con las mismas propiedades de nuestra entidad, nos sale muy sencillo.

Vol%206%20Modelando%20datos%20a17c23da61d3489ca4976d210b21d604/carbon-3%201.png

Es exactamente lo mismo que hacemos con el otro pero tiene métodos distintos ya que está orientado a un servicio diferente. De igual manera, la variable isAvailable que definimos queda solamente en nuestro modelo, por lo que debemos declararla fuera del constructor.

Otra cosa que podemos destacar de este modelo es que también hay que implementarle otro fromJson porque no extiende de nuestra clase anterior y los constructores quedan al nivel del modelo.

Este último punto puede ser algo medio molesto y repetitivo. Para utilizar el constructor fromJson tendríamos que heredar de StoreItemAPIModel, pero aún así se nos complica al llamar el otro constructor. De este último párrafo quédense con el primer enunciado e ignoren el resto ya que es un completo enredo.

Mejores maneras de hacer modelos

Con la forma que vimos de hacer modelos anteriormente, nos sobra. Pero esto no significa que eso mismo no se puede automatizar. Hay dos paquetes que yo ocupo bastante para mis modelos y en general cualquier clase inmutable que tenga (un estado, por ejemplo).

Equatable + json_serializable

El primer paquete que les voy a mencionar es Equatable. Hasta ahora este es el paquete con el que yo hago entidades y, por consecuencia, modelos. Nos permite crear un clase cuyas propiedades no cambian, además que nos da un método que debemos sobre escribir que se llama props, este nos devuelve una lista con las propiedades que queramos de la clase. Muy útil y completo.

https://gist.github.com/3da54d074e922be117f47c3cfe88a479

Una vez creada nuestra entidad, les sugiero combinar Equatable con json_serializable y json_annotation para crear sus modelos con el constructor fromJson y el método toJson para poder serializar de cualquier API que devuelva en formato JSON.

https://gist.github.com/c4656972050f915d3781d6e6cb24e862

Les recuerdo que json_serializable debe generar el código en un archivo con extensión **.g.dart. Para esto se debe correr el comando:

https://gist.github.com/2f6ada7e7e3a3fe9f365341a827d2ebf

Igualmente si queremos crear nuestro modelo para Firestore, solo extendemos al modelo que creamos anteriormente para utilizar su constructor fromJson y listo.

https://gist.github.com/430aa8a9104ede6d612959836638d963

Aunque Equatable hace lo necesario, y lo hace bien, no incluye un método importante de las clases inmutables: copyWith. Por esto les voy a presentar la segunda alternativa.

Freezed

Para solucionar el problema de lo mucho que tarda la creación de todos esos constructores, freezed es una excelente opción. Para saber más de este paquete, pueden ver mi artículo por acá.

Freezed incluye en sí lo siguiente:

  • Inmutabilidad (copyWith incluido).
  • Igualdad de valores.
  • Uniones.
  • Compatibilidad con json_serializable.

Así que podemos definir todos los modelos en una misma clase abstracta y utilizar los métodos en ellas mismas de esta manera...

https://gist.github.com/90a33b7c4274c2cebf33777582824b2d

Pero no todo es bonito en Freezed, ¿notan el problema?

Claro, las uniones son buenísimas para crear todos los posibles constructores sin tener que estar creando varios archivos por separado. Pero esa separación de clases y archivos es la que nos permite tener entidades y modelos por separado.

Una clase Freezed no puede ser subclase de otra e intentar extender la funcionalidad de una clase Freezed es una inversión de tiempo que no vale la pena y se pierde el tiempo que nos ahorramos generando el código.

¿Equatable o Freezed?

https://gist.github.com/e33d5bd3e5bf34ab56053c8ed2bfd352

Realmente viene siendo una cuestión de gustos ya que el resultado es muy similar. Pero en el caso de CleanScope, preferimos usar Equatable para entidades y modelos, ya que nos permite una mejor abstracción y control sobre el código.

Equatable tiene el método de props que en muchas ocasiones resulta bien útil. Freezed tiene integrado el copyWith y muchos otros métodos que detallo en mi artículo que cité antes, como lo son el when, maybeWhen, map, maybeMap.

Freezed nos encanta, de hecho lo usamos para generar los estados para la lógica en la capa de presentación, pero no nos cumple para el nivel de atracción que necesitamos en los modelos. Es una limitante con los generadores de código, te quitan control sobre tu código hasta cierto punto.

Cuando terminemos esta serie de artículos, vamos a proponer una versión minimalista de CleanScope donde sí vamos a usar Freezed para los modelos ya que vamos a tener menos capas de abstracción. Pero eso va a ser en otra ocasión.

Por el momento, nos quedamos con Equatable para nuestros modelos, en combinación con json_serializable.

https://media.giphy.com/media/FA77mwaxV74SA/giphy.gif

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:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment