Étant un gros consommateur de livres qui traitent de près ou de loin à notre métier de développeur, je me suis dit que ça pourrait être sympa de vous faire un article où je donne mon avis sur l’un d’eux. Le dernier en date que j’ai lu est « Distributed systems with Node.js » de Thomas Hunter ll. Voyons donc voir ce que j’en ai pensé.
Comme vous le savez si vous lisez ce blog, j’affectionne tout particulièrement Node.js, j’ai pu lire de nombreux livres à son sujet, mais la plupart du temps ceux-ci nous font uniquement un tour d’horizon de Node.js. On y découvre les bases, un ou plusieurs frameworks pour créer un serveur HTTP (Express, hapi, koa, etc.) et généralement une introduction à MongoDB. Cela peut suffire quand on est un débutant et que l’on souhaite apprendre Node.js, mais lorsque l’on a un niveau intermédiaire ou avancé, c’est un peu le vide, on trouve très peu de livres intéressants.
Thomas Hunter II vient tenter de remplir ce vide en proposant un livre destiné aux développeurs Node.js confirmés souhaitant mettre en production des applications robustes.
Qu’y a-t-il dedans ?
Bref voyons voir un peu le sommaire du livre et faisons un tour d’horizon de chacun des chapitres :
Protocols
Scaling
Observability
Containers
Deployments
Container Orchestration
Resilience
Distributed Primitives
Why Distributed ?
Comme vous le savez sûrement tous, JavaScript est monothreadé (bien que cela ne soit pas à 100% vrai), c’est à la fois sa force, mais également sa faiblesse. Il est donc parfois nécessaire de faire fonctionner nos applications Node.js de façon distribuée pour pallier à cette nature monothreadée.
Ce premier chapitre fait donc un rappel du fonctionnement de Node.js et plus particulièrement de l’event loop. Bien que court celui-ci fait le taf en présentant des exemples simples permettant de bien comprendre le fonctionnement de l’event loop ainsi que quelques petites astuces sur celle-ci, permettant de ne pas bloquer votre application.
Protocols
Lorsque l’on développe des microservices, il est souvent nécessaire de les faire communiquer entre eux. La méthode traditionnelle depuis plusieurs années est d’utiliser le protocole HTTP en développant des APIs REST, mais il existe d’autres moyens. Ce chapitre présente donc trois solutions à savoir HTTP, GraphQL et gRPC.
La présentation des trois solutions de communication est très succincte, mais permet facilement de comprendre leur fonctionnement. Un petit tableau récapitulatif des différences entre les trois solutions n’aurait pas été de trop.
Scaling
Développer un service, c’est bien, mais qu’il puisse répondre à la charge c’est encore mieux. Il existe bon nombre de solutions permettant de rendre un service Node.js scalable (en dehors de ne pas développer avec les pieds). Dans ce chapitre l’auteur nous présente deux solutions : l’utilisation du module natif cluster
et l’utilisation d’un reverse proxy à savoir HAProxy.
La première partie s’intéresse donc à la mise en place du module cluster
. J’ai apprécié le fait que l’auteur nous explique que ce module n’est pas une solution magique et que dans certains cas, il peut faire plus de mal que de bien contrairement à la plupart des articles sur le sujet que l’on peut trouver sur les internets.
La partie sur la mise en place d’un reverse proxy avec HAProxy est très bien expliquée et simple à mettre en place, peut-être due au fait que HAProxy est facilement abordable (mais pas pour autant facile à maîtriser). L’auteur nous donne également les raisons d’utiliser un reverse proxy. La première raison concerne la mise en place d’une terminaison SSL qui est généralement plus performante avec un reverse proxy que de laisser Node.js gérer cette partie. De plus nos services Node.js ne devraient pas avoir à gérer les différents certificats. J’en avais d’ailleurs rapidement parlé sur mon article sur les API REST. La deuxième raison concerne la mise en place d’une solution de load balancing, permettant comme son nom l’indique de répartir la charge entre plusieurs instances de nos services se trouvant sur différents serveurs.
La dernière partie s’attarde sur la comparaison des performances entre les deux solutions en « benchmarkant » celles-ci comme nous avons pu le voir dans mon article sur l’analyse des performances à l’aide d’autocannon. Il aurait peut-être été intéressant dans cette dernière partie de comparer les performances entre HAProxy et Node.js pour la mise en place de TLS.
Point intéressant que l’on retrouve tout au long du livre, à la fin chaque fin de partie, un petit encadré nous présente des alternatives aux outils présentés dans le livre. Dans le cas du reverse proxy, on retrouve Nginx (je vous vois galérer à le prononcer !), ELB (Elastic Load Balancing) d’AWS, ou encore des solutions d’API Gateway comme Kong.
Observability
Savoir ce qu’il se passe à l’instant T dans notre système est primordial que ce soit la remontée des logs ou bien des métriques telles que la latence et le nombre de requêtes HTTP ou encore la consommation CPU et mémoire de nos services. Ce chapitre nous présente donc plusieurs outils open source permettant de répondre à la problématique d’observabilité.
La première partie est consacrée à la suite ELK (Elasticsearch, Logstash et Kibana) pour ce qui est de la remontée des logs. L’auteur a fait le choix d’écrire un logger « from scratch » utilisant le protocole UDP pour transmettre les logs sur le service Logstash. Il aurait été selon moi plus pertinent d’utiliser un module comme Pino ou Winston qui propose des stratégies de logs pour Elastic ainsi qu’une gestion des niveaux de gravité des logs.
La seconde partie nous présente des outils pour la collecte et de l’affichage des métriques à l’aide de Graphite, StatsD et Grafana. Personnellement je ne connaissais ni Graphite ni StatsD ayant pour habitude d’utiliser Prometheus, mais cela n’en reste pas moins intéressant.
La partie suivante concerne le tracing distribué (merci à ce genre d’outil d’exister !) . Dans une architecture distribuée lorsque l’utilisateur effectue une requête, il n’est pas rare que plusieurs dizaines de services interagissent entre eux. Vient alors la problématique suivante : comment savoir d’où provient le problème en cas de panne ou lorsque la requête est lente ? C’est là qu’entrent en jeu les outils de tracing distribués. Ceux-ci permettent de suivre les différents échanges entre les services avec le temps passé dans chacun d’eux et savoir dans quel service les opérations ont échoué ou non. Ils peuvent également permettre de visualiser les dépendances entre chaque service sous forme de graphes. Dans le cadre du livre, l’auteur a fait le choix de présenter Zipkin, mais celui-ci évoque d’autres solutions comme Jaeger.
La dernière partie est quant à elle consacrée au système d’alerte avec l’utilisation de Cabot et de Twilio pour l’envoi des messages d’alerte par SMS.
Ce chapitre nous présente pas mal d’outils et bien qu’il survole tout de même certaines parties, je le trouve assez complet. Son but est de donner envie aux développeurs, non familiers à ce genre d’outils, d’aller y regarder de plus près, ce que je vous encourage d’ailleurs à faire.
Containers
De nos jours Docker est partout, il aurait été inconcevable de ne pas retrouver un chapitre qui lui soit dédié. Après une rapide présentation de Docker, on passe aux choses sérieuses avec la conteneurisation d’un service Node.js suivi de l’orchestration basique avec Docker-compose.
Point positif, l’auteur ne se contente pas de simplement conteneuriser une application Node.js, mais insiste longuement sur le concept de couches (layers) propre aux images Docker et au concept de multi-stage permettant de réduire la taille de nos conteneurs (si vous n’avez rien compris, vous n’inquiétez pas, on en parle bientôt dans un prochain article). Petit point négatif, il manque selon moi une partie consacrée aux bonnes pratiques notamment sur le fait ne pas utiliser l’utilisateur root au sein d’un conteneur ou encore de faire appel à un système d’init « light » (tel que tini ou dumb-init) pour que le service conteneurisé puisse correctement recevoir et transmettre les signaux systèmes (SIGTERM, SIGINT, etc.).
Deployments
Lorsque l’on développe un service vient alors le jour où celui-ci va quitter son petit dépôt Git et prendre son envol pour rejoindre les services en production. Mais hors de question de faire ça manuellement via une connexion FTP ou SSH. Aujourd’hui, on est plus dans une approche d’automatisation en soumettant à chaque changement notre code à des tests pour s’assurer de son fonctionnement et que celui-ci n’entre pas en conflit avec l’existant avant de l’envoyer vers la production. C’est ce que l’on appelle l’intégration et le déploiement continue (CI/CD).
La première partie du chapitre nous présente l’outil TravisCI qui est une solution CI/CD en ligne (gratuite pour les projets open source). On retrouve donc la mise en place d’un pipeline comportant une phase de build, de tests unitaires et de déploiement sur la plateforme Heroku.
La deuxième partie du chapitre est quant à elle consacrée à la publication de module sur npm ainsi que la mise en place d’un dépôt npm privé via Verdaccio.
Comme pour le reste, le chapitre fait le taf. Alors certes, on ne rentre pas dans les détails, mais encore une fois le lecteur est invité à approfondir le sujet en se renseignant de son côté. Sinon, j’ai trouvé fort utile le conseil que donne l’auteur sur les tests d’intégrations, suite à un retour d’expérience, de chose à ne pas faire et qui peut s’avérer être problématique.
Container Orchestration
Quand on se retrouve avec plusieurs dizaines de microservices conteneurisés, il devient vite difficile d’orchestrer tout ce joyeux petit monde et cela peut vite devenir le bordel. Mais heureusement Kubernetes est arrivé et a changé la vie de pas mal de monde. Je ne vais pas m’éterniser sur ce chapitre, il s’agit d’une bonne introduction à Kubernetes, on y découvre les principaux concepts (pods, services, deployments, etc.). Par contre, il aurait peut-être été intéressant de présenter un déploiement d’un service conteneurisé sur Kubernetes via TravisCI même si je comprends le choix de l’auteur puisqu’il aurait fallu dans ce cas avoir un cluster Kubernetes sur des serveurs pour effectuer ce déploiement.
Resilience
Quand on développe des services, il faut partir du principe que tôt ou tard ceux-ci vont planter. Nous devons donc prévoir des solutions pour les rendre robustes aux différents problèmes pouvant survenir et qu’ils puissent tout de même continuer de fonctionner, c’est ce que l’on appelle la résilience. Ce chapitre est l’un des plus longs et dense du livre (56 pages).
L’auteur commence par nous expliquer comment arrêter correctement une application Node.js notamment via les signaux système (SIGTERM, SIGINT, etc.).
Puis il nous explique comment construire un service « stateless » (qui est d’ailleurs l’une des 12 bonnes pratiques lorsque l’on conçoit une application cloud). Bien qu’intéressante cette partie se concentre surtout sur la mise en place d’un système de cache via Memcached permettant de conserver le cache en cas de redémarrage du service ou de partager celui-ci entre plusieurs services.
La partie suivante est consacrée à la résilience de la connexion à la base de données. On y découvre comment mettre en place une stratégie de reconnexion en cas de déconnexion ou d’erreur.
On continue avec la base de données, mais cette fois-ci le livre s’attaque aux migrations (avec le module knex). On y retrouve des conseils pour effectuer des migrations en live sans interrompre les différents services.
L’avant-dernière partie concerne la perte de message et quelles stratégies devons-nous mettre en place pour rejouer les différentes requêtes. Je suis un petit peu déçu de cette partie, car il y avait beaucoup à dire et j’aurais aimé des exemples de codes concernant les solutions évoquées (circuit breaker, retry pattern, etc.).
On finit rapidement par ce que l’on appelle le « chaos engineering« . Pour faire simple, c’est littéralement foutre le bordel dans votre architecture et voir si celle-ci est résiliente. Cette partie est vraiment courte, mais elle a au moins le mérite d’être là et le lecteur pourra aller se renseigner sur ce concept de « chaos engineering« .
Distributed Primitives
Cet avant-dernier chapitre est consacré à la concurrence et aux opérations atomiques. Bon… ça, c’était la promesse du chapitre, en vérité c’est surtout une introduction à Redis mais qui est je dois l’avouer plutôt pas mal.
Le dernier chapitre de ce livre concerne la sécurité. Ici on ne s’intéresse pas directement à la sécurité de notre code contre les différentes attaques classiques (XSS, CSRF, injection SQL, etc.), mais plutôt à l’analyse des modules Node.js et comment maintenir ceux-ci à jour à l’aide d’outil comme dependabot ou Snyk. L’auteur y explique également comment gérer la configuration de nos services à l’aide des variables d’environnements.
Annexes
Il existe également trois courts annexes concernant l’installation de HAProxy, Docker et Kubernetes via minikube.
Ce que j’en ai pensé
Je ne vais pas y aller par quatre chemins, j’ai bien aimé ce livre même s’il n’est pas exempt de défaut à commencer par son titre. Bien qu’on y parle de systèmes distribués, il aurait été plus judicieux de le nommer « Cloud native application with Node.js » ou même garder uniquement le sous-titre « Building Enterprise-Ready Backend Services », car je m’attendais tout de même à un contenu un peu plus poussé concernant les architectures distribuées, mais bon je chipote un peu.
Le livre survole certains sujets, mais l’idée est surtout de mettre le lecteur sur les bons rails pour développer des services Node.js « modernes ». Le livre couvre énormément d’outils (en plus de présenter beaucoup d’alternatives à ceux-ci) et il est difficile, dans un livre de 350 pages, d’approfondir des sujets parfois complexes. Le lecteur souhaitant approfondir un sujet devra donc se tourner vers un livre dédié à celui-ci.
Les exemples sont peut-être un peu trop simples, mais c’est un choix que je comprends et que j’applique également pour mes articles afin de me concentrer uniquement sur les concepts clés. J’aurais par contre aimé avoir plus exemples de code notamment dans le chapitre sur la résilience.
Il manque pour moi un chapitre essentiel concernant les files de messages comme RabbitMQ ou Kafka et plus généralement un chapitre dédié aux architectures asynchrones permettant entre autres de découpler les différents services. Le livre aurait également pu parler d’outils permettant de différer certaines tâches (job queue) avec notamment l’utilisation de BullMQ.
À part ça globalement le livre est très intéressant et comme je l’ai dit en introduction celui-ci vient combler un vide concernant les livres dédiés aux développeurs Node.js intermédiaires et avancés.
Si vous n’avez jamais utilisé les différents outils évoqués dans le livre, vous pouvez y aller les yeux fermés, je ne pense pas que vous serez déçu. Certes, cela ne fera pas de vous un expert sur les sujets évoqués, mais vous aurez au moins une « roadmap » concernant les sujets à approfondir en tant que développeur back-end. Par contre si vous êtes à l’aise avec ceux-ci et que vous les utilisez au quotidien, le livre ne vous apportera sûrement pas grand-chose.
Ah oui dernier point, le livre a son dépôt Git où l’on peut retrouver les différents codes source. Et là je m’adresse à l’auteur (on ne sait jamais s’il me lit un jour…), Thomas… c’est un bordel ton dépôt Git ! Il aurait été plus simple de créer des dossiers pour chaque chapitre afin de s’y retrouver là c’est un peu le chaos. Mais t’inquiètes, j’ai bien aimé ton livre je te pardonne. 😉
Suggestions de lecture
Je suis lead developer dans une boîte spécialisée dans l’univers du streaming/gaming, et en parallèle, je m’éclate en tant que freelance. Passionné par l’écosystème JavaScript, je suis un inconditionnel de Node.js depuis 2011. J’adore échanger sur les nouvelles tendances et partager mon expérience avec les autres développeurs.
Si vous avez envie de papoter, n’hésitez pas à me retrouver sur Twitter, m’envoyer un petit email ou même laisser un commentaire.
Super article
Merci pour vos articles !
Pour 1 newbee quel livre conseilleriez vous svp ?
Personnellement j’ai appris Node.js à l’aide de la documentation officielle. Si tu es vraiment débutant, je peux te conseiller le site https://nodejs.dev/learn ou https://oncletom.io/node.js/ (même si ça date un peu)