Blog Acensi
Beyond flux : going full cycle with functional reactive programming

[Ncrafts 2016] Retour sur la conférence "Beyond Flux" par Clement Delafargue

Je voudrais vous présenter une session à laquelle j’ai assisté lors de la conférence des Ncrafts 2016 à Paris en mai 2016 :

« Beyond Flux, going full cycle with functional reactive programming »

React et Flux nous permettent de concevoir des interfaces utilisateur de façon modulaire. L’idée de base est que les données doivent toujours circuler dans une seule direction. Avec ce concept, il est plus facile de concevoir des applications à partir de modules indépendants. Clément Delafargue nous a montré les concepts de React Core et comment ces derniers permettent aux développeurs de gérer la complexité dans les applications.
Pour aller plus loin, Clément nous a présenté la programmation réactive fonctionnelle (Functional Reactive Programming ou FRP). Après avoir expliqué ce qu’est FRP, il nous a montré comment les principes de FRP peuvent unifier beaucoup de choses différentes dans les architectures basées sur le pattern Flux. Cycle.js nous permet d’obtenir un échange de flux circulaire en représentant à la fois DOM et l’interaction utilisateur grâce aux observables. FRP a été utilisé pendant un certain temps dans Reactive Cocoa pour MacOs. Maintenant, il apparaît dans le monde Javascript, grâce à des bibliothèques comme RxJS et aux idées avancées par React et Flux.
L’objectif, à la fin de la conférence, était de comprendre les mécanismes de base de Flux et comment la FRP nous permet de concevoir des applications clientes.

Introduction

Clément a introduit sa conférence avec les « Single Page Applications » (ou SPAs). Comme son nom l’indique, il s’agit d’une application Web accessible via une seule et unique page web. L’objectif étant d’éviter le chargement d’une nouvelle page à chaque action demandée. Cela permet de fluidifier l’expérience utilisateur. Les SPAs doivent être capables de se modifier elles-mêmes et l’état modifié de l’application doit être persistant. Un exemple simple est celui du client Gmail. Vous êtes sur la page principale et vous pouvez voir votre boîte de réception, aller vers d’autres dossiers et composer des emails. Pour faire tout cela sur une page unique, vous devez implémenter la logique dans votre page principale qui va faire la navigation entre les différentes parties de d’application mais aussi pouvoir modifier, valider les changements et soumettre les modifications côté serveur.
Clément a continué sa présentation par Flux et React.
Flux est un pattern popularisé par Facebook. Flux a 4 rôles : « Actions », « Stores », « Dispatcher » et « Views ». Le principe de ce pattern est que les données doivent circuler dans un seul sens (unidirectionnel) afin de concevoir des applications à partir de modules indépendants.
Quelques explications sur les différents rôles :
Actions. Les actions sont de simples objets avec une propriété type et des données. La propriété type contient le nom de l’action.
Exemple : {“type”: “submit”, “local_data”: {“data”: 1}}.
Stores. Les stores contiennent l’état de l’application et la logique. Ils permettent la gestion d’un domaine particulier de l’application. Ils n’ont pas besoin de modélisation et stockent tout état de l’application.
Dispatcher. C’est le hub central. Le dispatcher traite les actions (par exemple, les interactions des utilisateurs) et invoque les « callbacks » des « stores » enregistrés auprès du dispatcher.
Views. Les vues sont des vues-contrôleur. Elles écoutent les changements des « stores » et se modifient elles-mêmes. Les vues peuvent également ajouter de nouvelles actions au Dispatcher. Elles sont généralement codées avec « React », mais il n’est pas nécessaire d’utiliser React avec Flux.

diagramme flux

Ci-dessous le diagramme général des flux de Flux :

La chose la plus importante à retenir est que tous les changements sont réalisés à travers des actions transmises au Dispatcher. Les données circulent donc dans un seul sens à travers ce Dispatcher.
Plus spécifiquement, le diagramme devrait ressembler à cela :
Diagramme Flux

React-Redux

Clément nous a également parlé de React. C’est une librairie développée en Javascript par Facebook. Elle permet entre autres de faciliter le développement de SPA (Single Page Application) via la création de composants générant une portion de code Html à chaque changement d’état. React est en général utilisé pour gérer les vues (Views dans Flux) mais peut être aussi utilisé avec d’autres librairies comme AngluarJS. Pour en savoir plus sur React : https://facebook.github.io/react/index.html
Il existe une implémentation de Flux, aujourd’hui très largement utilisée, c’est Redux : « The single Immutable State Tree ». Plus généralement, c’est un container d’état pour les applications Javascript qui applique un « reducer » à chaque action et permet de maintenir un état unique. Il peut être utilisé avec React ou avec n’importe quelle autre librairie.
Redux introduit de nouvelles propriétés et de nouvelles idées. Notamment des propriétés fonctionnelles.
Pour en savoir plus sur Redux : https://egghead.io/series/getting-started-with-redux

Cependant, Redux ne permet pas d’observer et d’écouter les évènements. Cela peut être obtenu par un autre Framework,  RxJS qui introduit le concept de collection Observables.
Il existe cependant un Framework qui implémente les deux concepts : Angular 2. En effet, angular-2-redux supporte les observables RxJS.
Pour encore aller plus loin, Clément nous a parlé de la FRP (ou Functional Reactive Programming).

Qu’est-ce que la Functional Reactive Programming (FRP) ou programmation réactive fonctionnelle ?

RxJS-CycleJS

Tout d’abord, essayons de comprendre la programmation réactive. Cette dernière consiste à programmer avec des flux d’évènements asynchrones.
D’une certaine façon, ce n’est pas quelque chose de nouveau, car les bus d’évènements ou les simples clics souris sont des flux d’évènements asynchrones. La programmation réactive introduit le fait de pouvoir en plus observer ces évènements. Vous pouvez donc écouter ce flux et réagir en conséquence.
Dans la programmation réactive, un flux est une séquence d’évènements en cours ordonnés dans le temps. Il peut émettre trois choses différentes : une valeur (d’un certain type), une erreur ou un signal « terminé ». Nous capturons ces évènements émis uniquement de manière asynchrone, en définissant une fonction qui va s’exécuter lorsqu’une valeur est émise, une autre fonction en cas de l’émission d’une erreur et enfin une fonction lorsque « terminé » est émis. Parfois, les deux dernières fonctions peuvent être omises et nous nous concentrons uniquement sur la définition de la fonction des valeurs. Dans la programmation réactive, « écouter » est appelé « abonnement », les fonctions que nous définissons sont des observateurs. Le flux est le sujet observé (ou « observable »). C’est précisément le pattern « Observer » qui est décrit.
Dans les librairies Reactive, il y a de nombreuses fonctions qui sont rattachées à chaque flux. Par exemple : map, reduce, scan. On est aussi capable de chaîner ces fonctions. Par exemple, clickStream.map(f).scan(g). Ces fonctions ne modifient pas le flux d’origine, elles renvoient un nouveau flux.  C’est ce qu’on appelle l’immutabilité. La programmation réactive adopte une syntaxe déclarative sans jamais modifier l’état d’un objet (ici un flux).
Clément nous a fait une brève présentation de RxJs (ou Reactive Extension for JavaScript). RxJs est une implémentation de la programmation réactive en Javascript qui introduit la notion de flux d’évènements observables.
Alors que RxJS permet de programmer directement avec des flux d’évènements grâce à des séquences d’observable, Clément nous a donc présenté Cycle.js. Il s’agit d’une extension de RxJS. Cycle.js fait une unique chose (notion d’unidirectionnel) en transmettant les flux de données de manière circulaire  entre votre application et le monde extérieur.  Ce qui rend Cycle.js unique par rapport à React, c’est son raisonnement entre les opérations et les dépendances, rendant explicite les graphes de flux de données. Cycle.js est donc plus facile à lire et à comprendre.
Selon Clément, le noyau de Cycle.js est incroyablement petit mais très efficace pour modéliser les flux de données et la communication entre l‘application et le monde extérieur. En d’autres termes, Cycle.js fournit une façon simple et ingénieuse de composer l’interaction de votre programme avec le monde extérieur avec une vraie séparation des problèmes.

Exemple d’implémentation :

Le point d’entrée du programme appelée main().  A l’intérieur de cette fonction, nous devons écrire ce que le programme doit faire.

// The program’s entry point.
function main()
{
// What the program does goes here.
}

Le programme a besoin d’avoir accès aux sources depuis le monde extérieur.

// Sources are injected
function main(sources)
{
// The program now has access to sources from the outside world.
}

Ensuite, on a besoin de lancer le programme :

// First, we import <code>run</code> from the core.
import { run } from '@cycle/core'
&amp;nbsp;
function main(sources)
{
// ...
}
&amp;nbsp;
// We kick-start Cycle.js
run(main, sources)

Avant de continuer, il faut déclarer nos sources. Les sources dans Cycle.js sont implémentées par des drivers. Qu’est-ce qu’un driver ? Un driver est un point de connexion vers le monde extérieur. Tous les « side effects » y sont gérés, comme par exemple, mettre à jour le DOM, ou l’envoi de message à un serveur mais ils fournissent également une entrée au programme et une sortie depuis le programme.

import { run } from '@cycle/core'
&amp;nbsp;
// We import the DOM driver factory function
import { makeDOMDriver } from '@cycle/dom'
&amp;nbsp;
function main(sources)
{
// ...
}
&amp;nbsp;
// We declare our drivers as sources.
const sources = {
// We tell the DOM driver that the DOM element of id <code>app</code>
// is our application container.
DOM: makeDOMDriver(<code>#app</code>)
}
&amp;nbsp;
run(main, sources)

Le programme pousse les données vers le driver de la même façon que ce dernier pousse les flux d’évènements vers le programme. Cycle.js utilise le terme « sinks » pour les flux qui sont poussés à l’extérieur du programme vers les drivers. Le driver s’attend à recevoir un observable de la part du programme, on aura besoin de Rx afin de lui fournir cela. Ainsi on peut définir la séquence suivante :

// We import Rx to build streams
import Rx from 'rx'
import { run } from '@cycle/core'
// We import virtual-hyperscript helper to create VTrees
// A VTree is a tree of virtual nodes used with the virtual DOM.
import { h, makeDOMDriver } from '@cycle/dom'
&amp;nbsp;
function main(sources)
{
    const inputRangeValue$ = sources.DOM
      .select(<code>.InputRange</code>)
      .events(<code>input</code>)
      .map(ev =&gt; ev.target.value)
&amp;nbsp;
    const sinks = {
    // Using the same key for the DOM driver as in <code>sources</code>
    // to enable correct mapping. The DOM driver expects
    // an Observable. Our Observable is of just one value;
    // a VTree of an INPUT element with class name <code>InputRange</code>
    // and of type <code>range</code>.
    DOM: Rx.Observable.just(h(<code>input.InputRange</code>, { type: <code>range</code>})),
}
&amp;nbsp;
  return sinks
}
&amp;nbsp;
const sources = {
  DOM: makeDOMDriver(<code>#app</code>)
}
&amp;nbsp;
run(main, sources)

Clément ne nous a pas montré l’implémentation des drivers. Mais la mécanique interne des drivers repose sur RxJS, où la souscription est faite automatiquement côté driver.
Il existe de nombreux drivers pour répondre à différentes problématiques : des drivers http, des driver html ou même des drivers qui interagissent avec React.
Enfin, Clément nous a parlé du pattern Model-View-Intent (MVI). Afin d’éviter d’avoir une fonction main() trop volumineuse, il est possible au développeur d’utiliser ce pattern qui permet de refactorer la fonction main() en trois parties :

  • Intent : écouter les évènements d’entrée utilisateurs.
  • Model : pour traiter les informations (les actions), le modèle retourne un état à la vue.
  • View : sortie utilisateur.
interactions

Dans ce pattern, l’utilisateur est un Input, on peut modéliser les interactions de la manière suivante :

Voici comment la fonction main() peut être refactorée :

function main({DOM}) {
    const actions = intent(DOM);
    const state$ = model(actions);
    return {
      DOM: view(state$)
    };
}

Conclusion

Avec Cycle.js et le pattern  MVI, on arrive à implémenter des mécanismes proches du pattern Flux mais avec de la programmation réactive en plus. Ces deux patterns nous permettent de gérer des états uniques pour notre application. Enfin, la programmation réactive fonctionnelle et plus généralement la programmation fonctionnelle est un nouveau paradigme qui représente à mon sens, l’avenir de la programmation.
Maxime

Ajouter un commentaire

Pourquoi ce blog ?

Pour permettre à nos consultants et experts techniques de partager leurs connaissances et retours d’expérience autour des sujets qui les passionnent. Ce blog, intégralement écrit par eux, a pour vocation d’être un véritable lieu d’échanges et d’apprentissage.

Alors n’hésitez pas à commenter nos articles pour rejoindre la conversation !

Une suggestion ?

Si vous avez des idées pour améliorer ce blog, nous sommes à l’écoute de vos remarques. Vous pouvez nous écrire via le formulaire de contact qui se trouve en bas de page.

Bonne visite !