Haciendo (Vue)n Frontend

0
4073

¿SOLID, testing, separación por capas y patrones de diseño en el frontend con Vue y TypeScript? ¿Estamos locos? No, se puede hacer y además te lo enseño en este tutorial.


Índice

Vue es un framework progresivo, versátil y que tiende a la optimización. Opta por la simplicidad en algunos aspectos y adopta cosas de React y Angular. En este post veremos consejos para que tu código con Vue sea impoluto y fácil de testear.

El ejemplo en vivo lo tienes aquí: https://codesandbox.io/s/rw7jm9ovzo y el código en Github: https://github.com/cesalberca/gravatar-viewer.


1. Usa TypeScript

TypeScript te va a ayudar mucho a detectar errores antes de que ejecutes el programa, vas a ser más productivo ya que tu IDE te va a ofrecer más ayudas y tendrás documentación acerca de las estructuras y modelos que usas en tu aplicación.

Vue da soporte a TypeScript, e incluso han anunciado con la versión 3 un rewrite de Vue en TypeScript. Hasta entonces el soporte que dan en ficheros .vue deja un poco que desear, ya que dependes de extensiones como Vetur o que tu IDE de un buen soporte.

Por las razones antes mencionadas, y dado que han anunciado que en el nuevo API será orientada a clases y no a objetos yo recomiendo usar vue-class-component y vue-property-decorator. Con estas bibliotecas logramos que nuestro código sea más legible y más seguro en cuanto a tipos. Si usas Vuex te recomiendo que eches un ojo a vuex-class.

Nota: Estas bibliotecas están arropadas por Vue. Su creador es un miembro core del equipo de Vue que lleva toda la parte de TypeScript dentro de Vue.


2. Usa Inject/Provide

Inject / provide es una API de Vue para hacer las veces de un contenedor simple de IoC, donde podremos proveer de dependencias dentro de un árbol de componentes a cualquier nivel, lo que evita en cierta medida el denominado prop drilling.

Un mecanismo análogo en React sería el API de contexto.

Ahora veamos cómo lo usaríamos en una mini aplicación que hace peticiones al API de Libravatar para mostrar el avatar de un usuario buscando por su email.

Comenzamos imaginando que nuestro componente llama a un repositorio:

Aquí tendremos un problema muy grande si queremos mañana consumir un repositorio distinto. Tendremos que cambiar todos los componentes donde haya una concreción como la hay cuando se llama a la factoría GravatarRepositoryFactory.photo().

Además los tests de este componente van a ser insufribles, porque tendremos que de alguna forma mockear el import, ya que no queremos que haga peticiones de verdad en el test unitario ya que este pasaría de ser unitario a de integración.

Nota: La función debounce hace que un método no se ejecute hasta que pase un tiempo mínimo de 1000ms, lo que evita que hagamos un montón de peticiones al API.

Con inject podemos mejorar esta situación:

El cambio es muy sutil, pero muy efectivo. Ahora nos falta proveer de la implementación de GravatarRepository (que adelanto que es una interfaz). Para ello nos podemos crear un componente ProviderFactory que sea el que se encarga de gestionar la creación de instancias y el que las inyecta:

Y ahora en el fichero App.vue haremos uso de él:

Si mañana tenemos un nuevo repositorio basado en local storage, solamente tendríamos que cambiarlo en el ProviderFactory. Incluso podríamos jugar a cambiar la implementación en caliente basándonos en cierta lógica.

El test además sería mucho más fácil, ya que podríamos decir que para el test se haga un provide de un mock.

¿Identificas además otra dependencia en el componente? Sí, el md5 y el debounce son una concreción, si queremos seguir la D de SOLID no deberíamos depender de concreciones si no de abstracciones.


3. Usa componentes y contenedores

Los componentes y contenedores no son más que el patrón de diseño Mediator. Donde los componentes serán los que se encargar de pintar, podrán tener lógica de pintado, pero muy poca. Reciben datos y los pintas, nada más. Los contenedores son aquellos que orquestan a los componentes y les pasan los datos. Suelen tener más lógica pero no suelen tener ni estilos visuales ni markup.

Los componentes se comunican con los contenedores mediante eventos, y los contenedores se comunican con los componentes mediante props. Esto hace que los tests sean mucho más fáciles, los componentes estén más desacoplados y si falla algo en cuanto a la orquestación, sabremos que tenemos que mirar el contenedor.

Como ejemplo vamos a refactorizar nuestro componente UserComponenteInject. Creamos primero el componente AvatarComponent

Ahora podemos crear el componente que se encarga del input del email. Creamos el componente UserForm:

Y por último creamos AvatarViewerContainer:

También modificamos el ProviderFactory para inyectar el debounce y el hasher:


4. Haz testing

Como puedes ver en el proyecto de ejemplo, todos los componentes y piezas están testadas. Al seguir SOLID y buenas prácticas el añadir test unitarios de todo lo necesario es trivial.

Por ejemplo veamos el AvatarViewerContainer.spec.ts:

O el componente AvatarComponent.spec.ts:

Y el UserFormComponent:

No hacer testing nunca está justificado.


5. Mueve lógica de negocio fuera de los componentes

Como hemos visto antes en el contenedor AvatarViewerContainer había un GravatarRepository. Básicamente este repositorio luego por debajo se conecta con un API y te devuelve la imagen del usuario. Podríamos hacer la llamada en el contenedor directamente, pero estaríamos acoplando nuestra obtención de datos con un framework (Vue) y romperíamos SOLID, ya que el motivo de cambio del contenedor es que la interacción cambie o que el API cambie.

Veamos más detenidamente GravatarRepository:

Pues resulta que es una interfaz. ¿Por qué? Porque esto nos permite definir varios repositorios para acceder a los datos. Podríamos tener una implementación de esta interfaz en GravatarHttpRepository, GravatarLocalStorageRepository o GravatarBlobRepository como es nuestro caso. Además podríamos cambiar la implementación tanto en «compilación» como en caliente.

Aquí está GravatarBlobRepository:

Por constructor le hemos pasado un fetcher, este es el encargado de recoger los datos de una API y tiene el siguiente tipo:

A aquellos que les suene verán que es igualito que el API de fetch, que es el que usaremos luego en verdad.

Y por último, ¿dónde creamos la instancia de GravatarBlobRepository? Pues con una factoría.

GravataRepositoryFactory:

Aquí vemos que hemos pasado de una abstracción Fetcher a una concreción: window.fetch, siendo en un futuro configurable e intercambiable por otra solución.

Por último hemos modelado nuestro usuario con una clase. Aquí tenemos el modelo del User:

Es importante evitar que nuestros modelos sean interfaces sin comportamiento o clases con setters o propiedades públicas, ya que nos pueden llevar a modelos anémicos, es decir modelos que son una bolsa de propiedades, siendo imposible determinar cual es su estado válido, delegando en el consumidor la lógica de validez, lo que haría a su vez que duplicásemos esa lógica.


Conclusión

El front no es fácil. Venimos de un mundo dónde a lo más que podíamos aspirar es a maquetar, aplicar estilos y usar algo de JavaScript para lograr animaciones e interactividad con la página. Todo esto ha cambiado, ahora debemos gestionar un montón de estado, asincronía, optimización de peticiones y caché, diseño responsive, reactividad y un montón más de cosas.

¿Por qué nos privamos de usar las herramientas y mecanismos que se llevan usando en la programación orientada a objetos desde hace más de 20 años que se han visto que funcionan?

Sígueme en Twitter y en Github.

Dejar respuesta

Please enter your comment!
Please enter your name here