¿Cómo modularizar Apps con React?

¿Cómo modularizar Apps con React?
¡Hola! Mi nombre es Carlos Vergara. Me desempeño como JavaScript Developer en DataArt, mayormente enfocado en grandes aplicaciones con React y Angular.

El 12 de Julio compartimos una Meetup junto a Carlos Bucheli, también JS Developer en DataArt. Fue una pequeña propuesta de diseño de aplicaciones basada por un lado en independizar la lógica de negocios de una app de su capa de datos y, por otro, proveer una manera clara de agregar y modificar funcionalidad.

Este es el código que escribimos, y estos los slides de la charla.

En esta oportunidad presentamos un pequeño juego, donde es posible apostar a que el personaje que uno elija va a poder matar a Batman, de acuerdo a un número elegido al azar, pudiendo ganar aún más puntos de acuerdo a otro número elegido.  

En esta ocasión quiero presentar una visión ligeramente más completa de lo que hablamos.

Elegimos armar un juego porque presenta varios desafíos comparables con una aplicación “businessy”, como integración con librerías diversas, varias capas claramente diferenciadas, reglas de negocio estrictas, y al mismo tiempo tiene pocas partes móviles y presenta una frontera muy clara entre el código de las distintas partes.

Esto nos permite ilustrar cómo es posible aplicar una forma de organizar el código para aportar flexibilidad y puntos claros de extensión a una aplicación que de otra forma sería firme candidata a transformarse en una pila inmantenible de código spaghetti.

React es, en principio, una librería de vistas (la V de MVC, entre otras posibilidades).

Da la chance de estructurar una aplicación de un modo primitivo con facilidad; todos los componentes pueden estar en cualquier parte, traídos por una (típicamente grande) serie de imports, y esos componentes sólo pueden hablar con aquellos componentes que contienen ellos mismos, y no viceversa… es un modelo mental excelente para estructurar una aplicación pequeña.

Sin embargo, típicamente no es usada así, sino como el corazón de todo el frontend, trayendo una cantidad grotesca de librerías asociadas para poder manejar algún tipo de información externa sin perder la cordura en el intento.

Esto se enseña desde el principio en casi todos los tutoriales de la comunidad de React.

Entre otras, es típico encontrar que incluso para las aplicaciones más mínimas se traen Redux, Reselect, redux-form, redux-saga o redux-thunk, axios, y combinaciones de utilidades alrededor para soportar todo ese ecosistema.

Nos referimos a esto como el kitchen-sink approach, similar a lo que se conoce como Banana Monkey Jungle Problem, donde originalmente pedimos una banana y termina siendo entregada junto al mono que la sostenía, el árbol donde este estaba parado, y la jungla donde se encontraba el árbol.

En este caso, nosotros queríamos usar una librería para manejar formularios, y terminamos construyendo una superestructura con generators, selectors, higher order components, etcétera.

Otro problema típico es encontrar selectores como el siguiente (basado en un caso real):

Este engendro es el resultado natural de pasar diversas iteraciones cambiando el propósito y sentido de un selector para usarlo en diversos lugares de una aplicación que no tenía ninguna necesidad de estar estructurada de este modo.  

A su vez es probable que este selector se haya utilizado del siguiente modo:

Perdiendo entonces con el tiempo todo vínculo entre exactamente qué es ese dato y para qué se utiliza.

Muchas veces, esto no tiene ninguna necesidad de ocurrir, y puede ser atenuado.

Nuestro enfoque impide, a partir de aplicar una visión simple, que esto ocurra.  Todo en la capa de datos existe contenido en un lugar claro y evidente y sus proveedores son explícitos, permitiendo rastrear exactamente dónde está y por qué es así esta estructura.

Es importante destacar que nuestra propuesta es fundamentalmente simple, cambiando entera la capa de datos y reemplazando por ejemplo Redux por MobX sin que la capa de negocios o incluso específicamente la vista tenga ninguna clase de modificación pendiente.

Patrones

Para toda aplicación de software existen Patrones de Diseño que se encuentran con mayor o menor regularidad, como Módulos, Registros, Observadores y alguna variante de MV* como Model, View, ViewModel o MVC.

Decidimos explotar el patrón módulo, por justamente permitirnos estructurar esta aplicación fundamentalmente como una serie de paquetes independientes, uno de los cuales aporta Modelos y Controladores a la Vista que proveen React y Pixi.js.

Por sí sola, esta simple descripción ya provee una idea fuerte de cómo estructurar la aplicación, qué directorios se pueden encontrar, y donde es posible que se realicen modificaciones de acuerdo a su única responsabilidad designada (Single Responsibility).

El approach modular

Existen distintas formas de modular, ya sea proveyendo pequeños paquetes de funcionalidad con distintos elementos de la interfaz dentro, organizando el código en capas, o atando todas las partes componentes en archivos de contenido vagamente relacionado.

El approach modular tiene una larga y noble historia en JavaScript. En algún punto es posible ver distintas etapas de la comunidad en su conjunto de acuerdo a qué estructura utilizaban para separar sus aplicaciones en bloques más manejables.

Algunos ejemplos de este fenómeno:

Cada una de estas maneras de modular nos permite tomarnos distintas libertades, siempre proveyendo la garantía que lo que se ve dentro de un módulo es exactamente lo que necesita este para proveerlo.

Esto resulta ser de vital importancia al intentar separar por ejemplo todo lo relacionado a la generación y manipulación de animaciones o a toma de decisiones respecto a acciones del usuario.

React & Redux

Redux, en pocas palabras, es una estructura montada sobre un objeto común y corriente, que sólo permite modificarlo a través de una serie de operaciones (reducers) desencadenadas por eventos (acciones).

Entonces esto:

No deja nunca en realidad de ser equivalente a:

Pero impone una estructura sobre esa información, para poder manejarla de un modo particular.

¿Cómo podríamos entonces tratar al store como si fuera un simple objeto, aprovechando las bondades que provee Redux?

En Kill The Bat, cada módulo define exactamente qué claves dentro de este store (o cualquier otro) requiere y puede actualizar, mientras que el StoreManager se encarga de manejar la inmensa mayoría del cableado necesario.

Una cosa de la que no se encarga hoy es de la construcción de acciones, porque quisimos mostrar un punto evidente de expansión donde es posible agregar a StoreManager un Action Creator Factory por ejemplo.

Modelos & Controladores

Para KillTheBat nuestra implementación sigue bastante de cerca MVC, teniendo Controllers y Models para los componentes de React y Pixi.

Por ejemplo, en un modal con información del usuario es su respectivo controller quien toma la decisión de enviar o no información al backend, y qué hacer con el modelo de datos  correspondiente, mientras el componente de React se mantiene firmemente convencido que todo lo que requiere son unas props inyectadas a través de connect().

Todo continúa su cauce natural, pero ahora podríamos –por ejemplo, reemplazar el envío de datos al backend vía AJAX por un push hacia un websocket, agregar analytics, etc. Todo en un lugar claro y evidente por su relación con el componente en sí.

Todo lo descrito se logra utilizando un patrón extremadamente común para separar partes dedicadas a distintos intereses en bloques distanciados unos de otros (el patrón modular).

Es enteramente posible que haya muchas más sorpresas observando lo que se ha hecho con anterioridad; siempre estamos investigando y siempre podemos cuestionar más partes, mecánicas y preconceptos, sólo es necesario parar un momento a reflexionar ¿es realmente necesario todo esto? Y los beneficios para el proyecto pueden potencialmente ser increíbles.

Les recomendamos intentarlo.