Node.js
Node.js

Follow

24 février 2017 – 14 min de lecture

Cet article provient de Tomislav Capan, consultant technique et passionné de Node.js. Tomislav a initialement publié ceci en août 2013 dans le blog Toptal – vous pouvez trouver le billet original ici ; le blog a été légèrement mis à jour. Le sujet suivant est basé sur l’opinion et les expériences de cet auteur.

La popularité croissante de JavaScript a apporté avec elle beaucoup de changements, et le visage du développement web aujourd’hui est dramatiquement différent. Les choses que nous pouvons faire sur le web aujourd’hui avec JavaScript s’exécutant sur le serveur, ainsi que dans le navigateur, étaient difficiles à imaginer il y a seulement quelques années, ou étaient encapsulées dans des environnements sandboxed comme Flash ou Java Applets.

Avant de creuser dans Node.js, vous pourriez vouloir lire les avantages de l’utilisation de JavaScript à travers la pile, qui unifie le langage et le format de données (JSON), vous permettant de réutiliser de manière optimale les ressources du développeur. Comme il s’agit davantage d’un avantage de JavaScript que de Node.js en particulier, nous n’en parlerons pas beaucoup ici. Mais c’est un avantage clé pour incorporer Node.js dans votre pile.

Node.js est un environnement d’exécution JavaScript construit sur le moteur JavaScript V8 de Chrome. Il convient de noter que Ryan Dahl, le créateur de Node.js, avait pour objectif de créer des sites Web en temps réel avec une capacité de push,  » inspirée par des applications comme Gmail « . Dans Node.js, il a donné aux développeurs un outil pour travailler dans le paradigme d’E/S non bloquantes et pilotées par les événements.

En une phrase : Node.js brille dans les applications web en temps réel employant la technologie push sur les websockets. Qu’est-ce qui est si révolutionnaire dans tout cela ? Eh bien, après plus de 20 ans de web sans état basé sur le paradigme demande-réponse sans état, nous avons enfin des applications web avec des connexions bidirectionnelles en temps réel, où le client et le serveur peuvent tous deux initier la communication, ce qui leur permet d’échanger des données librement.

Ceci contraste fortement avec le paradigme de réponse web typique, où le client initie toujours la communication. En outre, tout est basé sur la pile web ouverte (HTML, CSS et JS) fonctionnant sur le port standard 80.

On pourrait faire valoir que nous avons cela depuis des années sous la forme de Flash et d’applets Java – mais en réalité, ce n’étaient que des environnements sandboxed utilisant le web comme protocole de transport à livrer au client. De plus, ils étaient exécutés de manière isolée et fonctionnaient souvent sur des ports non standard, ce qui pouvait nécessiter des autorisations supplémentaires et autres.

Avec tous ses avantages, Node.js joue désormais un rôle essentiel dans la pile technologique de nombreuses entreprises de haut niveau qui dépendent de ses avantages uniques. La Fondation Node.js a consolidé toutes les meilleures réflexions autour de la raison pour laquelle les entreprises devraient considérer Node.js dans une courte présentation qui peut être trouvée sur la page des études de cas de la Fondation Node.js.

Dans ce post, je vais discuter non seulement comment ces avantages sont accomplis, mais aussi pourquoi vous pourriez vouloir utiliser Node.js – et pourquoi pas – en utilisant certains des modèles d’applications web classiques comme exemples.

Comment cela fonctionne-t-il ?

L’idée principale de Node.js : utiliser des E/S non bloquantes et événementielles pour rester léger et efficace face à des applications en temps réel gourmandes en données qui s’exécutent sur des périphériques distribués.

C’est une mise en bouche.

Ce que cela signifie vraiment, c’est que Node.js n’est pas une nouvelle plateforme à la bulle d’argent qui va dominer le monde du développement web. Au lieu de cela, c’est une plate-forme qui remplit un besoin particulier. Et comprendre cela est absolument essentiel. Vous ne voulez certainement pas utiliser Node.js pour des opérations gourmandes en CPU ; en fait, l’utiliser pour des calculs lourds annulera presque tous ses avantages. Là où Node.js brille vraiment, c’est dans la construction d’applications réseau rapides et évolutives, car il est capable de gérer un nombre énorme de connexions simultanées avec un débit élevé, ce qui équivaut à une grande évolutivité.

La façon dont il fonctionne sous le capot est assez intéressante. Par rapport aux techniques traditionnelles de service web, où chaque connexion (demande) génère un nouveau fil d’exécution, consommant la RAM du système et finissant par atteindre le maximum de la quantité de RAM disponible, Node.js fonctionne sur un seul thread, en utilisant des appels d’E/S non bloquants, ce qui lui permet de supporter des dizaines de milliers de connexions simultanées (maintenues dans la boucle d’événement).

*Image tirée de l’article de blog original.

Un calcul rapide : en supposant que chaque thread a potentiellement 2 Mo de mémoire avec lui, l’exécution sur un système avec 8 Go de RAM nous met à un maximum théorique de 4000 connexions simultanées (calculs tirés de l’article de Michael Abernethy « Just what is Node.js ? », publié sur IBM developerWorks en 2011 ; malheureusement, l’article n’est plus disponible), plus le coût du changement de contexte entre les threads. C’est le scénario auquel vous êtes généralement confronté dans les techniques traditionnelles de service web. En évitant tout cela, Node.js atteint des niveaux de scalabilité de plus de 1M de connexions simultanées, et plus de 600k connexions websockets simultanées.

Il y a, bien sûr, la question du partage d’un seul thread entre toutes les requêtes des clients, et c’est un piège potentiel de l’écriture d’applications Node.js. Tout d’abord, un calcul lourd pourrait étouffer le thread unique de Node et causer des problèmes pour tous les clients (plus sur ce point plus tard), car les demandes entrantes seraient bloquées jusqu’à ce que ledit calcul soit terminé. Deuxièmement, les développeurs doivent faire vraiment attention à ne pas permettre à une exception de bouillonner jusqu’au noyau (le plus haut) de la boucle d’événements de Node.js, ce qui entraînera la fin de l’instance de Node.js (plantant effectivement le programme).

La technique utilisée pour éviter que les exceptions ne bouillonnent jusqu’à la surface est de renvoyer les erreurs à l’appelant comme paramètres de callback (au lieu de les lancer, comme dans d’autres environnements). Même si une exception non gérée parvient à faire des bulles, des outils ont été développés pour surveiller le processus Node.js et effectuer la récupération nécessaire d’une instance plantée (bien que vous ne serez probablement pas en mesure de récupérer l’état actuel de la session utilisateur), le plus commun étant le module Forever, ou en utilisant une approche différente avec des outils système externes upstart et monit, ou même juste upstart.

npm : Le gestionnaire de paquets Node

Lorsque l’on parle de Node.js, une chose qui ne doit absolument pas être omise est le support intégré pour la gestion des paquets en utilisant l’outil npm qui vient par défaut avec chaque installation de Node.js. L’idée des modules npm est assez similaire à celle des Ruby Gems : un ensemble de composants réutilisables disponibles publiquement, disponibles par une installation facile via un dépôt en ligne, avec une gestion des versions et des dépendances.

Une liste complète des modules packagés peut être trouvée sur le site web npm, ou accessible en utilisant l’outil CLI npm qui est automatiquement installé avec Node.js. L’écosystème des modules est ouvert à tous, et chacun peut publier son propre module qui sera répertorié dans le dépôt npm. Une brève introduction à npm peut être trouvée dans un Guide du débutant, et des détails sur la publication de modules dans le Tutoriel de publication npm.

Certains des modules npm les plus utiles aujourd’hui sont :

  • express – Express.js, un cadre de développement web inspiré de Sinatra pour Node.js, et le standard de facto pour la majorité des applications Node.js qui existent aujourd’hui.
  • hapi – un framework centré sur la configuration, très modulaire et simple à utiliser, pour construire des applications web et de services
  • connect – Connect est un framework de serveur HTTP extensible pour Node.js, fournissant une collection de « plugins » de haute performance connus sous le nom de middleware ; sert de fondation de base pour Express.
  • socket.io et sockjs – Composant côté serveur des deux composants websockets les plus courants qui existent aujourd’hui.
  • pug (anciennement Jade) – L’un des moteurs de templating populaires, inspiré de HAML, par défaut dans Express.js.
  • mongodb et mongojs – wrappers MongoDB pour fournir l’API des bases de données objet MongoDB dans Node.js.
  • redis – Bibliothèque client Redis.
  • lodash (underscore, lazy.js) – La ceinture utilitaire JavaScript. Underscore a initié le jeu, mais a été renversé par l’un de ses deux homologues, principalement en raison de meilleures performances et d’une mise en œuvre modulaire.
  • forever – Probablement l’utilitaire le plus commun pour s’assurer qu’un script node donné fonctionne en continu. Maintient votre processus Node.js en production face à toute défaillance inattendue.
  • bluebird – Une implémentation complète de Promises/A+ avec des performances exceptionnellement bonnes
  • moment – Une bibliothèque de date JavaScript légère pour analyser, valider, manipuler et formater les dates.

La liste continue. Il existe des tonnes de paquets vraiment utiles, disponibles pour tous (sans vouloir offenser ceux que j’ai omis ici).

Les endroits où Node.js devrait être utilisé

Chat est l’application multi-utilisateurs en temps réel la plus typique. Depuis IRC (à l’époque), en passant par de nombreux protocoles propriétaires et ouverts fonctionnant sur des ports non standard, jusqu’à la possibilité de tout mettre en œuvre aujourd’hui dans Node.js avec des websockets fonctionnant sur le port standard 80.

L’application de chat est vraiment l’exemple du sweet-spot pour Node.js : c’est une application légère, à fort trafic, à forte intensité de données (mais à faible traitement/calcul) qui fonctionne sur des appareils distribués. C’est également un excellent cas d’utilisation pour l’apprentissage, car il est simple, mais il couvre la plupart des paradigmes que vous utiliserez jamais dans une application Node.js typique.

Tentons de dépeindre comment cela fonctionne.

Dans le scénario le plus simple, nous avons un seul salon de discussion sur notre site Web où les gens viennent et peuvent échanger des messages de manière un à plusieurs (en fait tous). Par exemple, disons que nous avons trois personnes sur le site Web, toutes connectées à notre babillard.

Côté serveur, nous avons une simple application Express.js qui met en œuvre deux choses : 1) un gestionnaire de requête GET ‘/’ qui sert la page web contenant à la fois un tableau de messages et un bouton ‘Send’ pour initialiser l’entrée de nouveaux messages, et 2) un serveur websockets qui écoute les nouveaux messages émis par les clients websockets.

Côté client, nous avons une page HTML avec un couple de gestionnaires mis en place, un pour l’événement de clic sur le bouton ‘Send’, qui récupère le message d’entrée et l’envoie dans le websocket, et un autre qui écoute les nouveaux messages entrants sur le client websockets (c’est-à-dire, messages envoyés par d’autres utilisateurs, que le serveur veut maintenant que le client affiche).

Lorsqu’un des clients poste un message, voici ce qui se passe :

  • Le navigateur capte le clic sur le bouton ‘Send’ par le biais d’un gestionnaire JavaScript, récupère la valeur du champ de saisie (c’est-à-dire, le texte du message), et émet un message websocket en utilisant le client websocket connecté à notre serveur (initialisé lors de l’initialisation de la page web).
  • Le composant côté serveur de la connexion websocket reçoit le message et le transmet à tous les autres clients connectés en utilisant la méthode de diffusion.
  • Tous les clients reçoivent le nouveau message sous forme de message push via un composant websockets côté client exécuté dans la page web. Ils reprennent ensuite le contenu du message et mettent à jour la page web en place en ajoutant le nouveau message au tableau.

Image tirée du blog original.

C’est l’exemple le plus simple. Pour une solution plus robuste, vous pourriez utiliser un simple cache basé sur le magasin Redis. Ou dans une solution encore plus avancée, une file d’attente de messages pour gérer l’acheminement des messages aux clients et un mécanisme de livraison plus robuste qui peut couvrir les pertes de connexion temporaires ou le stockage des messages pour les clients enregistrés pendant qu’ils sont hors ligne. Mais quelles que soient les améliorations que vous apportez, Node.js fonctionnera toujours selon les mêmes principes de base : réagir aux événements, gérer de nombreuses connexions simultanées et maintenir la fluidité de l’expérience utilisateur.

API AU-DESSUS D’UNE BD OBJETS

Bien que Node.js brille vraiment avec les applications en temps réel, il est tout à fait naturel d’exposer les données des BD objets (par exemple MongoDB). Les données stockées en JSON permettent à Node.js de fonctionner sans le décalage d’impédance et la conversion des données.

Par exemple, si vous utilisez Rails, vous convertiriez de JSON en modèles binaires, puis les exposeriez à nouveau en JSON sur le HTTP lorsque les données sont consommées par React.js, Angular.js, etc. ou même des appels AJAX jQuery simples. Avec Node.js, vous pouvez simplement exposer vos objets JSON avec une API REST pour que le client puisse les consommer. En outre, vous n’avez pas à vous soucier de la conversion entre JSON et tout autre format lors de la lecture ou de l’écriture dans votre base de données (si vous utilisez MongoDB). En somme, vous pouvez éviter le besoin de conversions multiples en utilisant un format de sérialisation des données uniforme sur le client, le serveur et la base de données.

ENTREES QUOTIDIQUES

Si vous recevez une grande quantité de données simultanées, votre base de données peut devenir un goulot d’étranglement. Comme illustré ci-dessus, Node.js peut facilement gérer les connexions concurrentes elles-mêmes. Mais comme l’accès à la base de données est une opération bloquante (dans ce cas), nous rencontrons des difficultés. La solution consiste à reconnaître le comportement du client avant que les données ne soient réellement écrites dans la base de données.

Avec cette approche, le système maintient sa réactivité sous une forte charge, ce qui est particulièrement utile lorsque le client n’a pas besoin d’une confirmation ferme de l’écriture réussie des données. Les exemples typiques comprennent : la journalisation ou l’écriture de données de suivi des utilisateurs, traitées par lots et qui ne sont pas utilisées avant un moment ultérieur ; ainsi que les opérations qui n’ont pas besoin d’être reflétées instantanément (comme la mise à jour d’un compte de  » J’aime  » sur Facebook) où la cohérence éventuelle (si souvent utilisée dans le monde NoSQL) est acceptable.

Les données sont mises en file d’attente par une sorte d’infrastructure de mise en cache ou de mise en file d’attente des messages (MQ) (par ex, RabbitMQ, ZeroMQ) et digérées par un processus séparé d’écriture par lots de base de données, ou des services dorsaux de traitement intensif de calcul, écrits dans une plateforme plus performante pour de telles tâches. Un comportement similaire peut être mis en œuvre avec d’autres langages/frameworks, mais pas sur le même matériel, avec le même débit élevé et maintenu.

Image tirée de l’article original.

En bref : avec Node, vous pouvez pousser les écritures de la base de données sur le côté et les traiter plus tard, en procédant comme si elles avaient réussi.

DATA STREAMING

Dans les plateformes web plus traditionnelles, les requêtes et les réponses HTTP sont traitées comme un événement isolé ; en fait, ce sont des flux. Cette observation peut être utilisée dans Node.js pour créer des fonctionnalités intéressantes. Par exemple, il est possible de traiter des fichiers alors qu’ils sont encore en cours de téléchargement, car les données arrivent par un flux et nous pouvons les traiter de manière en ligne. Cela pourrait être fait pour l’encodage audio ou vidéo en temps réel, et le proxys entre différentes sources de données (voir la section suivante).

PROXY

Node.js est facilement employé comme proxy côté serveur où il peut gérer une grande quantité de connexions simultanées de manière non bloquante. Il est particulièrement utile pour le proxy de différents services avec des temps de réponse différents, ou pour collecter des données à partir de plusieurs points sources.

Un exemple : considérez une application côté serveur communiquant avec des ressources tierces, tirant des données de différentes sources, ou stockant des actifs comme des images et des vidéos vers des services cloud tiers.

Bien que des serveurs proxy dédiés existent, utiliser Node à la place pourrait être utile si votre infrastructure de proxy est inexistante ou si vous avez besoin d’une solution pour le développement local. Par cela, je veux dire que vous pourriez construire une application côté client avec un serveur de développement Node.js pour les actifs et le proxys/stubbing des demandes API, tandis qu’en production, vous géreriez ces interactions avec un service proxy dédié (nginx, HAProxy, etc.).

BROKERAGE – STOCK TRADER’S DASHBOARD

Revenons au niveau de l’application. Un autre exemple où les logiciels de bureau dominent, mais pourraient être facilement remplacés par une solution web en temps réel est le logiciel de trading des courtiers, utilisé pour suivre les cours des actions, effectuer des calculs/analyses techniques et créer des graphiques/tracés.

Le passage à une solution web en temps réel permettrait aux courtiers de changer facilement de poste ou de lieu de travail. Bientôt, nous pourrions commencer à les voir sur la plage en Floride… ou à Ibiza… ou à Bali.

DASHBOARD DE SURVEILLANCE DES APPLICATIONS

Un autre cas d’utilisation courant dans lequel Node-with-web-sockets s’adapte parfaitement : le suivi des visiteurs de sites Web et la visualisation de leurs interactions en temps réel. Vous pourriez recueillir des statistiques en temps réel de votre utilisateur, ou même passer au niveau supérieur en introduisant des interactions ciblées avec vos visiteurs en ouvrant un canal de communication lorsqu’ils atteignent un point spécifique dans votre entonnoir – un exemple de ceci peut être trouvé avec CANDDi.

Imaginez comment vous pourriez améliorer votre entreprise si vous saviez ce que vos visiteurs font en temps réel – si vous pouviez visualiser leurs interactions. Avec les sockets bidirectionnels en temps réel de Node.js, vous le pouvez maintenant.

SYSTEM MONITORING DASHBOARD

Maintenant, visitons le côté infrastructure des choses. Imaginons, par exemple, un fournisseur SaaS qui souhaite proposer à ses utilisateurs une page de suivi des services (par exemple, la page d’état GitHub). Avec la boucle d’événements de Node.js, nous pouvons créer un tableau de bord web puissant qui vérifie les statuts des services de manière asynchrone et pousse les données vers les clients à l’aide de websockets.

Les statuts des services internes (intra-entreprise) et publics peuvent être signalés en direct et en temps réel grâce à cette technologie. Poussez cette idée un peu plus loin et essayez d’imaginer un centre d’opérations réseau (NOC) surveillant les applications d’un opérateur de télécommunications, d’un fournisseur de cloud/réseau/hébergement ou d’une certaine institution financière, le tout exécuté sur la pile web ouverte soutenue par Node.js et les websockets au lieu de Java et/ou des applets Java.

Note : N’essayez pas de construire des systèmes en temps réel dur dans Node.js (c’est-à-dire des systèmes nécessitant des temps de réponse constants). Erlang est probablement un meilleur choix pour cette classe d’application.

Applications Web côté serveur

Node.js avec Express.js peut également être utilisé pour créer des applications Web classiques côté serveur. Cependant, bien que possible, ce paradigme requête-réponse dans lequel Node.js transporterait du HTML rendu n’est pas le cas d’utilisation le plus typique. Il y a des arguments à faire pour et contre cette approche. Voici quelques faits à considérer :

Pros:

  • Si votre application n’a pas de calcul intensif pour le CPU, vous pouvez la construire en Javascript de haut en bas, même jusqu’au niveau de la base de données si vous utilisez le stockage JSON Object DB comme MongoDB. Cela facilite le développement (y compris l’embauche) de manière significative.
  • Les crawlers reçoivent une réponse HTML entièrement rendue, ce qui est beaucoup plus favorable au référencement que, disons, une application à page unique ou une application websockets exécutée au-dessus de Node.js.

Cons:

  • Tout calcul intensif du CPU bloquera la réactivité de Node.js, de sorte qu’une plate-forme threadée est une meilleure approche. Alternativement, vous pourriez essayer de mettre à l’échelle le calcul(*).
  • Utiliser Node.js avec une base de données relationnelle est encore assez pénible (voir ci-dessous pour plus de détails). Faites-vous une faveur et prenez n’importe quel autre environnement comme Rails, Django ou ASP.Net MVC si vous essayez d’effectuer des opérations relationnelles.

(*) Une alternative aux calculs intensifs du CPU est de créer un environnement hautement évolutif adossé à MQ avec un traitement back-end pour garder Node comme un « commis » frontal pour traiter les demandes des clients de manière asynchrone.

Application WEB côté serveur avec une base de données relationnelle derrière

Comparant Node.js avec Express.js contre Ruby on Rails, par exemple, il y a une décision claire en faveur de ce dernier quand il s’agit de l’accès aux données relationnelles.

Les outils de BD relationnelle pour Node.js sont encore assez sous-développés, par rapport à la concurrence. D’autre part, Rails fournit automatiquement la configuration de l’accès aux données dès la sortie de la boîte, ainsi que des outils de prise en charge des migrations de schémas de BD et d’autres joyaux (jeu de mots). Rails et ses frameworks pairs disposent d’implémentations matures et éprouvées de la couche d’accès aux données Active Record ou Data Mapper, qui vous manqueront cruellement si vous essayez de les répliquer en pur JavaScript.(*)

Pour autant, si vous êtes vraiment enclin à rester JS jusqu’au bout, consultez Sequelize et Node ORM2.

(*) Il est possible et pas rare d’utiliser Node.js uniquement comme une façade tournée vers le public, tout en conservant votre back-end Rails et son accès facile à une BD relationnelle.

Computation/Processing côté serveur lourd

Lorsqu’il s’agit de calcul lourd, Node.js n’est pas la meilleure plateforme qui soit. Non, vous ne voulez certainement pas construire un serveur de calcul de Fibonacci en Node.js. En général, toute opération intensive du CPU annule tous les avantages de débit que Node offre avec son modèle d’E/S non bloquant et piloté par les événements, car toutes les demandes entrantes seront bloquées pendant que le thread est occupé par votre calcul.

Comme indiqué précédemment, Node.js est monofil et n’utilise qu’un seul cœur de CPU. Quand il s’agit d’ajouter la concurrence sur un serveur multi-core, il y a un certain travail fait par l’équipe de base de Node sous la forme d’un module cluster. Vous pouvez également exécuter plusieurs instances de serveur Node.js assez facilement derrière un proxy inverse via nginx.

Avec le clustering, vous devriez toujours décharger tous les calculs lourds à des processus d’arrière-plan écrits dans un environnement plus approprié pour cela, et les faire communiquer via un serveur de file d’attente de messages comme RabbitMQ.

Même si votre traitement d’arrière-plan pourrait être exécuté sur le même serveur initialement, une telle approche a le potentiel d’une très grande évolutivité. Ces services de traitement d’arrière-plan pourraient être facilement distribués sur des serveurs de travail séparés sans avoir besoin de configurer les charges des serveurs web frontaux.

Bien sûr, vous utiliseriez la même approche sur d’autres plateformes aussi, mais avec Node.js vous obtenez ce débit élevé de reqs/sec dont nous avons parlé, car chaque demande est une petite tâche traitée très rapidement et efficacement.

Conclusion

Nous avons discuté de Node.js de la théorie à la pratique, en commençant par ses objectifs et ses ambitions, et en terminant par ses sweet spots et ses pièges. Lorsque les gens rencontrent des problèmes avec Node, cela se résume presque toujours au fait que les opérations de blocage sont la racine de tous les maux – 99% des mauvaises utilisations de Node en sont la conséquence directe.

Rappellez-vous : Node.js n’a jamais été créé pour résoudre le problème de mise à l’échelle du calcul. Il a été créé pour résoudre le problème de mise à l’échelle des E/S, ce qu’il fait très bien.