Nodejs – Construire une architecture évolutive avec des plugins

Cette article est une traduction fidèle de l’article Nodejs Build scalable architecture with plugins, écrit par Iskander Samatov.
Dans ce court article de blog, Iskander passe en revue les avantages et les raisons de la séparation de votre code réutilisable en plugins.

lot de contenants par couleurs assorties. Métaphore sur l'architecture pour des projets node.js

L’architecture de rêve de tout ingénieur développeur consiste en un ensemble de composants hautement modulaires et réutilisables.
Cependant, ce type d’architecture s’avère difficile à réaliser, que ce soit pour nodejs, ou un autre outil. Cela est d’autant plus vrai que votre base de code ne cesse de croître. Une astuce qui pourrait vous aider à faire évoluer votre code consiste à séparer vos composants réutilisables en plugins.
Dans ce court article de blog, je veux passer en revue les avantages et les raisons de l’utilisation de cette approche.

Qu’est-ce qu’un plugin Nodejs ?

Un plugin est un composant qui étend les fonctionnalités de l’application principale de manière extensible et réutilisable. Une des raisons pour laquelle Nodejs est si populaire n’est pas simplement due qu’à son architecture.
C’est son grand nombre de plugins en tant que packages, créés par la communauté, qui nous fournissent presque toutes les fonctionnalités dont nous avons besoin.

Distribution du plugin pour Nodejs

Plugins publics

Comme mentionné précédemment, une grande majorité des plugins sont distribués sous forme de packages npm. pouvant être installés dans le dossier node_modules de votre projet. Tous les plugins publics sont distribués via npm et peuvent être utilisés par n’importe qui. L’un des exemples les plus marquants de ce type de plugin est Express.

Plugins privés

Cependant, les plugins ne doivent pas nécessairement être publics. Les plugins peuvent également être écrits pour un usage privé, au sein de l’organisation ou même pour un seul projet. Il existe différentes manières de distribuer et de partager des plugins privés :

  • Système de contrôle de version – vous pouvez simplement ajouter votre plugin au contrôle de version utilisé par votre équipe.
  • Paquet npm privé – de nombreuses organisations utilisent npm pour conditionner leurs plug-ins et les partager au sein de l’organisation uniquement.

Bien que les plugins internes puissent utiliser les dépendances de l’application principale, il peut également être bénéfique que le plugin ait son propre graphe de dépendances et son package.json. Voici un exemple de diagramme :

project
|_package.json
|_node_modules
     |_pluginA
         |_package.json
     |_pluginB
         |_package.json

Comme vous pouvez le constater, notre projet contient trois fichiers package.json : un pour l’application principale et deux pour chaque plugin du dossier node_modules. Voici trois raisons pour lesquelles vous voudrez peut-être utiliser cette approche :

  • NPM – Nous pouvons utiliser npm pour distribuer nos plugins en tant que packages privés.
  • Graphique de dépendance – Ces plugins disposent de leurs propres graphiques de dépendance, ce qui facilite la gestion des dépendances du projet et la résolution des conflits.
  • Plus facile à importer – Il est beaucoup plus facile d’importer ces plugins. Au lieu d’utiliser des chemins relatifs (require (‘../../ utils / moduleA’)), il suffit de fournir le nom du plugin (require (‘pluginA’)).

Pourquoi organiser votre code en plugins ?

Meilleure structure

Alors pourquoi utiliser des plugins ?
Outre les considérations précédentes, l’architecture globale est l’une des principales raisons de séparer une partie des fonctionnalités de votre application dans un plugin.
En séparant votre code en plugins, vous séparez votre code en unités qui sont à la fois réutilisables et faiblement couplées.
Cela vous oblige à envisager l’encapsulation en réfléchissant aux parties du plugin qui devraient être exposées à l’application principale.
Globalement, cela rend votre application plus évolutive.

Architecture serverless

L’architecture serverless est sans doute l’avenir du développement Web. Bien que ce type d’architecture offre une meilleure évolutivité par rapport aux serveurs traditionnels, il existe un problème de partage de composants communs entre des fonctions serverless.

Une fonction dans une architecture serverless est généralement un fragment de code relativement petit qui effectue certains calculs ou tâches. Il s’agit généralement d’un micro-service autonome et, par défaut, il ne partage pas le code avec les autres fonctions du projet.

Comme mentionné précédemment, chaque fonction étant un service autonome, il peut être gênant de partager un code commun entre les fonctions. La raison en est que, une fois déployée, chaque fonction s’exécute dans un environnement distinct.

C’est là que l’emballage de vos plugins et leur distribution en tant que modules npm peuvent être utiles. Tout ce que vous avez à faire est de publier votre plugin en tant que paquet privé et de l’installer dans vos fonctions selon vos besoins.

Ecrire un plugin

Voyons comment nous pourrions écrire un plugin qui étend les fonctionnalités de la simple fonction serverless. Imaginez ce genre de structure de projet.

serverlessF
 |_node_modules
	|_pluginA
		|_index.js
		|_package.json
 |_fetch.js
 |_app.js
 |_package.json
 
utils
 |_pluginA
	|_index.js
	|_package.json

Quelques points à noter ici :

  • Le dossier serverlessF contient notre fonction serverless. C’est une simple application Nodejs.
  • Le dossier utils est destiné au code partagé entre plusieurs fonctions de notre projet. Il se compose de différents plugins, chacun avec son propre package.json.
  • pluginA est publié sous forme de paquet privé et installé par notre fonction serverless.

Voici un exemple de code pour un plugin simple qui valide un jeton tiers :

const fetch = module.parent.require('./fetch');
 
const validateRequest = async (req) => {
  const { token } = req.body;
  const result = await fetch(token);
  // further validate token
};
 
module.exports = validateRequest;

Notre plugin expose une fonction pour valider le jeton.
Un tour intéressant ici est la première ligne. Si vous regardez le diagramme ci-dessus, vous remarquerez que notre fonction lambda contient le fichier fetch.js, qui est utilisé dans le but de créer diverses demandes d’extraction à l’API tierce.
En utilisant module.parent.require (‘./fetch’), nous pouvons utiliser ce module dans notre plugin en imitant l’appel obligatoire de l’application parente où le plugin est installé.

Bien que l’utilisation de module.parent.require puisse sembler une astuce astucieuse, il est souvent déconseillé de s’y fier. Cela crée un couplage étroit entre notre fonction serverless et le plugin. Le déplacement de fetch.js dans un autre sous-dossier endommagerai le plugin. Une meilleure approche serait d’utiliser l’injection de dépendance pour fournir à notre plugin une instance du service de récupération :

const validateRequest = async (req, fetch) => {
  const { token } = req.body;
  const result = await fetch(token);
  // further validate token
};
 
module.exports = validateRequest;

Note : lors de l’écriture de plugins, il est particulièrement important d’éviter les dépendances et de créer un couplage étroit, sinon l’objectif du plugin est annulé. Vous pouvez en savoir plus sur le câblage du module et les dépendances ici.

Conclusion

Petit avertissement, il n’est pas nécessaire de séparer chaque partie de votre application dans un plugin. Cela pourrait ajouter plus de complexité que nécessaire. Cependant, si vous avez un grand graphe de dépendances, il peut être intéressant de scinder certains d’entre eux en plugins. De plus, empaqueter votre code dans des plugins peut être un bon moyen de partager un code commun entre des fonctions sans serveur.