Por qué es importante Flux y por qué (probablemente) no debes usarlo

Flux es el patrón (o la arquitectura, según se mire) de moda. Normal, si tienes en cuenta que funciona muy bien con React, React es la librería de moda en Javascript, y Javascript es el lenguaje de moda (no, Elixir está bien pero no está tan de moda como Javascript).

flux-logo

Como siempre que algo se pone de moda, hay mucha gente escribiendo sobre Flux y mucho incauto usando Flux en situaciones donde, posiblemente, no sea la mejor opción. En este post vamos a intentar conocer un poco mejor lo que hay detrás de Flux, por qué es importante conocer este patrón, y, lo fundamental: por qué no debemos lanzarnos a implementarlo en nuestra próxima aplicación.

OJO: con la cantidad de buzzwords de este post es probable que acabe bien posicionado en Google y llegues desde allí. Si esperas encontrar un tutorial paso a paso de cómo usar Flux, lo siento pero este no es tu post. Luego no te quejes de que no hay código, que ya te estoy avisando. Por el contrario, si quieres entender cómo funciona Flux, tal vez merezca la pena que sigas leyendo.

De dónde viene Flux

Para entender mejor de dónde sale Flux es bueno partir de la filosofía que hay detrás de React. Si no la recuerdas, échale un vistazo a esta introducción a ReactJS para poder seguir el resto de este post. A fin de cuentas, Flux es un patrón ideado por Facebook para usar en aplicaciones con React y la influencia entre uno y otro es clara.

En React podemos llegar a entender la vista como una función pura, es decir, sin efectos colaterales, que sólo depende del modelo o estado de la aplicación. Aunque el estado se separe en información mutable propia del componente (state), e inmutable que fluye entre componentes (props), cada componente podríamos simplificarlo como la siguiente función:

component :: Model -> VirtualDOM

De hecho, a eso en React se le llama componentes funcionales sin estado (por aquello de que no usan su state mutable) y desde la versión 0.14 son una realidad.

Para que esto funcione un punto clave es el DOM Virtual que usa React, que hace que podamos modelar los componentes como funciones puras que devuelven un DOM Virtual pero que no modifican el DOM real. La vista es sólo una función que transforma el modelo en Virtual DOM.

Esto está muy bien para la parte de pintar cosas en pantalla pero, ¿qué hacemos con las interacciones del usuario o de sistemas externos (timers, websockets, etc.)?

Inspirándonos en la programación funcional y, concretamente, en la programación reactiva funcional, podemos intentar modelar los cambios de estado de forma parecida a como hemos hecho con las vistas.

Cada acción que se genera cuando se produce una interacción desencadena un cambio en el estado. Podemos ver entonces cada acción como una función que a partir del estado anterior y los parámetros de la acción genera un nuevo estado:

action:: (Model, ActionData) -> Model

En un lenguaje como ClojureScript mundo ideal, nuestro modelo estaría formado por estructuras de datos inmutables y al ejecutarse acciones sobre él, tendríamos nuevas versiones del modelo, no mutaciones sobre el modelo anterior. Llevando esto al extremo, partiendo del modelo inicial y las acciones que han ocurrido podemos reconstruir cualquier punto de la historia. Si os recuerda a Event Sourcing es porque la idea es similar.

Bien, con esto tenemos dos partes: un modelo que podemos convertir en vista a través de funciones puras y unas acciones que podemos aplicar sobre el modelo para dar lugar a un nuevo estado. Sólo nos falta una parte: actualizar la vista cuando el modelo cambia. Para conseguir eso, lo más fácil es hacer que el modelo dispare un evento cada vez que cambia y hacer que la vista se repinte en respuesta a ese evento.

¿Un evento? ¿En React? Sí, puede sonar extraño porque los eventos se asocian más a patrones como MVVM o MVC y React huye de eso, pero en este caso se trata de un evento que capturarán los componentes de más alto nivel de la aplicación (en el caso ideal, sólo el componente raíz) y serán estos componentes los que propagarán el estado hacia abajo, haciéndolo llegar a todos sus hijos a través de los props de cada hijo.

Un dato importante: sólo existe un evento. No hay un evento por cada posible modificación, sino que el modelo lanza un evento changed cuando se produce cualquier cambio, y se renderiza toda la vista entera. Esto, que puede parecer ineficiente, suele ser bastante rápido porque toda la regeneración de las vista se hace contra el DOM Virtual, sin involucrar al DOM real que es lo que más penaliza el rendimiento.

¿Qué ventajas nos aporta esto?

La ventaja más importante, y sólo por ella ya merece la pena plantearse usar este diseño, es que es mucho más fácil razonar sobre la aplicación porque la información sólo fluye en una dirección. Además, proporciona una forma fácil y homogénea de organizar toda la funcionalidad de la aplicación, aunque para mi eso queda en un segundo plano. Tener funciones puras también facilita la creación de test, si es que eso es lo tuyo.

Qué es Flux en realidad

Lo que hemos visto ahora son (parte de) las bases en las que se asienta Flux, pero en realidad Flux no es exactamente como lo que he descrito. Aunque parte de esas ideas, Flux se estructura alrededor de varios componentes.

Views

Son componentes de React que transforman el modelo en DOM Virtual.

Existen unas vistas especiales, que se suelen llamar ViewController, que se corresponden con esas vistas de alto nivel que mencionábamos antes y que son las encargadas de escuchar el evento changed del modelo, actualizar su state y forzar así el renderizado de todo su subárbol.

Stores

Representan el modelo de la aplicación, desde la información descargada del servidor hasta los filtros activos en una búsqueda. Puede haber más de uno para facilitar una separación lógica de la información, y cada uno de ellos dispara su propio evento changed.

Las vistas pueden referenciar los stores para acceder a su estado interno, aunque lo recomendable es que sólo lo hagan los ViewController, pasando estos a sus hijos la información necesaria a través de los props normales de todo componente de React.

Los stores no exponen ningún método para modificar su estado interno, para ello hay que pasar por los Actions y el Dispatcher, que veremos más adelante.

Actions

Cada operación que podemos realizar en la aplicación se representa mediante un action, que es un objeto literal que contiene un identificador de tipo único para cada acción, y la información asociada a esa acción.

Por ejemplo, si tenemos la típica aplicación de TODOs, podríamos tener una acción con este aspecto:

{
  type: Actions.ADD_TODO,
  text: 'Comprar panceta adobada'
}

Estas acciones se envían al dispatcher, que se encargará de propagarlas hasta los stores. Cualquier vista o componente (por ejemplo un WebSocket) puede enviar estas acciones al dispatcher para desencadenar un proceso.

Para simplificar el manejo de las acciones, es frecuente introducir un módulo que consolide varias acciones relacionadas y ofrezca un API más amigable. A estos módulos se les denomina ActionCreators.

Por ejemplo, en el caso de la aplicación de TODOs, podríamos tener en algún action creator el siguiente método que sería invocado desde las vistas adecuadas:

TodoActions.addTodo = function(text) {
  var action = {
    type: Actions.ADD_TODO,
    text
  };
  Dispatcher.dispatch(action);
}

Dispatcher

Es el encargado de mediar entre los actions y los stores.

Básicamente lo único que hace es permitir que los stores registren una callback que será invocada siempre que se reciba un action. Siempre. Es decir, el dispatcher, a diferencia de lo que haría un EventBroker, no discrimina a quién manda las acciones, sino que es cada store el que tendrá un «precioso» switch sobre el action.type para decidir si le interesa o no la acción y qué hacer con ella.

Visto así, parece que el dispatcher no aporta mucho a todo esto ya que podríamos pasar las acciones directamente a los stores, pero la ventaja de usarlo es que desacopla las vistas de los stores porque no hace falta saber qué stores están involucrados en procesar un action concreto, y además ofrece métodos (como waitFor) para coordinar unos stores con otros si es necesario controlar el orden en que se ejecutan.

El resultado

Y éste es el pack completo de Flux. El diagrama de cómo fluye la información es el siguiente:

flux-diagram

Un «sencillo» flujo unidireccional de información en el que el proceso es más o menos así:

  1. Los stores disparan el evento changed.
  2. Los view controllers suscritos al evento acceden a la información de los stores y actualizan su propio state.
  3. Esto fuerza el renderizado de las views que componen los view controllers, las cuales reciben el nuevo estado a través de sus props.
  4. Cuando se genera alguna interacción, las views invocan un método en un action creator con la información necesaria para procesarla.
  5. El action creator construye un objeto action con el tipo de la acción y la información asociada, y lo manda al dispatcher.
  6. El dispatcher propaga el action hasta los stores y garantiza que los stores lo procesan en el orden adecuado.
  7. Los stores reciben el action y, si les interesa, actualizan su estado interno, generando un nuevo evento changed, lo que nos lleva otra vez al punto de partida.

Como veis, hay algunas diferencias con respecto a los conceptos básicos que hemos explicado antes, especialmente la forma en que se gestiona el estado, ya que en lugar de implementar las acciones como funciones puras que generan un nuevo estado, tenemos unos stores que encapsulan el estado y el comportamiento, al más puro estilo orientado a objetos.

Hay otras implementaciones del patrón, como Redux, que se ajustan más a la idea inicial, pero la implementación «oficial» de Facebook funciona como lo que acabamos de ver.

Entonces, ¿merece la pena?

Ahora que ya tenemos una idea de cuáles son los principios detrás de Flux y conocemos mejor cómo se suele implementar, estamos en mejor posición para evaluar hasta dónde nos puede ayudar.

Las ideas en las que se basa Flux son relativamente simples y nos aportan unos beneficios claros, especialmente a la hora de razonar sobre la aplicación y el flujo de información. Desde ese punto de vista, aplicar esas ideas para diseñar una aplicación nos puede ayudar a conseguir un diseño más fácil de entender y mantener, y eso lo convierte en un patrón más a tener en cuenta a la hora de estructurar nuestra capa de presentación junto a los clásicos MVC o MVVM.

Es posible simplificar Flux y renunciar a algunas partes. Si la aplicación no es muy complicada, podemos llegar a unificar actions, action creators, dispatcher y stores en un único módulo que encapsule el estado y exponga un API de commands (métodos que cambian estado pero no devuelve nada) que utilicen todas las vistas, y queries (métodos que devuelven cosas pero no cambian estado) que usen sólo los componentes raíz. Implica mayor disciplina a la hora de trabajar, pero ahorra mucho código (posiblemente) innecesario y permite mantener el «espíritu» de flujo de información unidireccional de Flux.

Si optamos por la implementación «completa» de Flux, con sus stores, su dispatcher, sus actionsy y sus action creators vamos a necesitar una cantidad de código importante para ponerlo en marcha. Antes de decidir utilizar todo esto tenemos que estar seguros de que realmente lo necesitamos, porque la complejidad que introduce en la aplicación no es despreciable.

Entre el extremo de unificar todo en un único módulo y montar el «pack completo» de Flux, existe toda una escala de grises en la que nos podemos mover en función del escenario en que nos encontremos.

Para finalizar, si estás dispuesto a utilizar otro lenguaje y transpilarlo a Javascript, hay algunos que facilitan bastante una implementación más ortodoxa de Flux o alguna de sus variantes, especialmente lenguajes funcionales como Elm, Purescript o ClojureScript. Éste último cuenta con construcciones muy idiomáticas (atoms y estructuras de datos persistentes) que hacen que implementar Flux resulte más natural y requiera menos código, por lo que se convierte en una opción más atractiva.

7 comentarios en “Por qué es importante Flux y por qué (probablemente) no debes usarlo

  1. John Benavides dijo:

    Hola Juanma.

    Muy interesante el Post, sin embargo luego de leerlo no me queda muy claro el titulo «Porque probablemente no utilizarlo» a parte de que para el initial setup se necesita un poco mas de codigo que MVC y MVVC. no veo lei ninguna razon.

    Gracias y Saludos

  2. Hola John,

    La razón principal para no usarlo es la complejidad que introduce, que hace que tengas que valorar si realmente te interesa o no. En realidad eso pasa con cualquier otra tecnología, pero en el caso de Flux, como está de moda, veo a muchos lanzándose a implementarlo en aplicaciones que no necesitan más que un par de plugins de jQuery.

    Si usas la implementación «oficial» de flux, hay que escribir bastante código (no sé si más o menos que con MVC o MVVM, depende de la librería que uses en cada caso), y muchas veces no merece la pena.

    Un saludo,

    Juanma.

  3. Hola Juan, primero que todo, tengo que felicitarte, tus posts son cada día mejores, muy de otro mundo y sobre todo muy discutibles (en el buen sentido), ya quisiera tener yo ese nivel para escribir post.

    Continuando, Flux al parecer, es bueno en el caso de que necesites que dos o mas componentes requieran utilizar el mismo stores, pero, aun así, requiere de una curva de aprendizaje algo pesada para algunos que tratamos de entenderlo del todo. En mi caso podría decir que Flux es exactamente lo que necesito, dado que mi aplicación tiene componentes que muchos tienen en común la fuente de datos (según entendí los stores).

    ¿El por que no deberíamos usarlo? no me quedo del todo claro, pero me imagino que nos aconsejas primero que todo entenderlo y estudiarlo antes de entrarse con este patrón.

    Gracias por tu aporte :).

  4. Yo prefiero redux también y, ya puestos a elegir, re-frame para ClojureScript me parece una implementación de la idea muy elegante y que aprovecha muy bien las características del lenguaje.

  5. Miguel Caravantes dijo:

    Hola Juan, podrías revisar el ejemplo que pones en el action creator, creo que hay un error, la idea seria mandar el parámetro text en el action supongo.

Comentarios cerrados.