Parce que nous n'avons pas (encore) GraphQL

Dans un avenir idyllique, nous utiliserons simplement un outil tel que GraphQL pour composer dynamiquement des requêtes de données auprès du serveur, en récupérant ni plus ni moins que ce dont nous avons besoin. Mais ce jour n'est pas encore arrivé.

En attendant, nous devons demander des ressources via la même API que celle utilisée par nos clients, ce qui inclut parfois des données complémentaires, et parfois non.

Prenons, par exemple, notre point de terminaison « Job », tel qu'il est représenté dans Thrift :

En effet, une requête adressée à l'API pour obtenir des données relatives aux tâches inclura des tableaux d'utilisateurs et de documents associés à ces tâches, et ne fera référence aux modèles de tâches que par le biais de clés étrangères. Sans tenir compte du contexte métier d'une tâche, on peut imaginer une simple liste de ces tâches dans une barre latérale, dont la sélection devient alors l'élément central du panneau principal.

Première itération : React – Transmission des props

Lors de notre première tentative de création d'une interface utilisateur autour de ce concept, nous avons laissé React se charger d'une grande partie de la gestion des données, en transmettant ces données — parfois en très grande quantité — sous forme de props aux composants enfants.

Poursuivons avec l'exemple d'une liste d'emplois (barre latérale gauche ci-dessus). Voici une première ébauche relativement aboutie :

Les points essentiels à retenir sont les suivants :

  • Le composant parent reçoit en tant que props la tâche actuellement sélectionnée, une liste de toutes les tâches, ainsi qu’un « idObjectCache », qui n’est autre qu’un espace de stockage de données de recherche mis à jour manuellement.
  • Le parent transmet à l'enfant toutes les mêmes informations
  • Traitement des données sous forme de fonctions Underscore enchaînées au sein de la méthode `getRows` (Pardon, Lodash, bien sûr. Désolé à tous.)
  • Une petite fonction de recherche un peu farfelue dans le fichier enfant, appelée `templateIdFromName`

Il s'agit là d'un exemple relativement simple, mais la situation devient rapidement ingérable dans les parties plus complexes de l'application, où nous devons faire la synthèse de sources de données plus hétérogènes.

De plus, il s'avère que le modèle « job » recèle le modèle « team » complet, que nous utilisons dans le code ci-dessus (this.props.job.team.name) mais que nous n'abstraisons jamais pour d'autres composants. Sur la même page, nous effectuons également un appel API pour obtenir l'équipe associée à la mission en cours.

Deuxième itération : récupération des données des clés étrangères vers NuclearJS

Ce n'est un secret pour personne. J'adore NuclearJS à en crever. Voici une première ébauche pour abandonner Alt au profit de mon nouveau coup de cœur :

Les différences sont subtiles, et leur importance peut ne pas sauter aux yeux si vous n'avez pas [encore] utilisé React en situation réelle.

  • Le conteneur parent s'appuie désormais sur les getters de Nuclear pour récupérer des listes de données immuables, le filtrage et le tri étant entièrement pris en charge.
  • Les seuls paramètres que le composant reçoit concernent l'état de l'interface utilisateur (par exemple, « récupération des offres d'emploi »), mais il gère en interne la récupération de ses propres données, ce qui signifie que le composant et son parent peuvent être refactorisés ou déplacés en toute sécurité.
  • Le module « Job » est un module commun que nous utilisons dans deux applications différentes, mais « currentJobId » est spécifique à l'application WebEx (Web Execution — veuillez excuser ce jargon interne) et, par conséquent, à son propre module.
  • Nous nous contentons d'utiliser la méthode getIn() , bien plus sûre, pour la récupération en profondeur.

Le résultat est beaucoup moins verbeux, beaucoup plus facile à comprendre, à mettre en forme et à adapter pour nos designers. C'est parfait, mais qu'en est-il de la récupération des données ?

Gérer les données des clés étrangères comme le ferait Jason Bourne

La documentation Nuclear propose une excellente stratégie pour créer des actions API modèle par modèle. Étant donné que le wrapper que nous avons écrit autour de Thrift renvoie des Promises, bon nombre de nos implémentations de modèles se résument à de simples affectations :

exports.fetchAll = Thrift.enhanced.JobRole.index ;

Mais lorsque nous souhaitons gérer les données des clés étrangères des résultats renvoyés, nous pouvons rapidement venir à bout de nos adversaires, à la manière de Jason Bourne, comme suit :

Remarque : le code ci-dessus comporte quelques passages où l'on pourrait être plus concis, mais au moins, ce qui se passe est parfaitement clair pour le prochain ingénieur (je l'espère !).

Interception de la réponse de l'API

L'astuce réside dans le fait que, selon les paramètres envoyés par l'interface utilisateur (c'est-à-dire un composant React), l'action utilise différents points de terminaison de l'API. Comme les fonctions Thrift et Nuclear renvoient toutes deux des Promises, nous pouvons intervenir au milieu du processus et transmettre le tableau de tâches obtenu à la fonction `processForeignKeys`.

Demande de données relatives aux clés étrangères obligatoires

La deuxième astuce consiste à extraire de la liste des tâches une liste unique d’identifiants de modèles de tâches (pour plus de 50 tâches, vous n’aurez peut-être que 3 ou 4 modèles). Une requête d’index sur les modèles devrait être paginée ; ainsi, au lieu de tout récupérer, nous pouvons envoyer des requêtes uniquement pour ce dont nous avons besoin. Si le point de terminaison de l’index venait à prendre en charge la réception d’un tableau d’identifiants, nous pourrions transmettre ces derniers en un seul appel.

Bien sûr, ces opérations sont du type « fire-and-forget ». Et ce, gratuitement : nos getters mettront à jour les composants React dès que les données de la clé étrangère seront disponibles. C'est génial.

Chargement des données des clés étrangères intégrées

La troisième astuce consiste à récupérer les données relatives aux utilisateurs, aux postes et aux équipes pour l'ensemble des missions, puis à les charger à l'aide d'une simple fonction `receiveAll` que nous avons créée :

Pour chaque modèle, la fonction `receiveAll` simule simplement le traitement d'une requête de récupération d'API qu'elle a elle-même lancée. Les résultats sont identiques.

Vous remarquerez peut-être également ci-dessus que, lors du traitement de données de clés étrangères en double entre différentes tâches, nous détournons la fonction `uniq()` pour nous contenter de comparer les identifiants. C'est beaucoup plus simple que d'effectuer une comparaison approfondie, même si cela repose sur notre certitude que le serveur ne nous enverra pas de données différentes avec les mêmes identifiants dans une même requête. Si nous ne pouvons pas compter là-dessus, nous avons des problèmes bien plus graves.

La dernière étape : les « Getters »

Tu es arrivé jusqu'ici. Allez, mettons le ballon dans la zone d'en-but.

À première vue, le format des getters de Nuclear peut paraître un peu « sucré », mais il est génial.

  • The raw job data itself is stored as an Immutable Map of String<jobId> to Map<job>
  • La classe `jobList` surveille les données relatives aux tâches et aux modèles de tâches, effectue des recherches dès que des modèles deviennent disponibles et renvoie les résultats sous la forme d'une liste immuable.
  • Les exportations `activeJobList` et `completedJobList` appliquent simplement des filtres et des tris, comme c'est le cas dans n'importe quel langage ou framework. Vous bénéficiez ainsi gratuitement d'une liaison de données unidirectionnelle.

Au final, nous avons…

  • L'intégralité de notre code API dans un wrapper Thrift
  • Toutes nos données API sont stockées sous forme d'objets immuables, gérées de bout en bout par Nuclear, y compris les dépendances liées aux clés étrangères.
  • L'ensemble de notre interface utilisateur est développée en React, avec la récupération et la liaison des données localisées autant que possible.

… ce qui est tellement simple que ça me laisse le temps d'écrire un article sur Medium à ce sujet.


Si vous avez trouvé cet article utile, n'hésitez pas à le recommander.

Ah oui, et on recrute notre troisième ingénieur front-end : si ça vous tente d'être payé pour vous débrouiller avec React, Nuclear et ImmutableJS au sein d'une start-up ayant levé des fonds en série A et soutenue, entre autres, par Andreessen Horowitz, First Round, Kleiner Perkins et Google Ventures, envoyez-moi un message.