Accueil / Articles / 12 règles pratiques pour des API Node.js fiables

12 règles pratiques pour des API Node.js fiables

Règles pratiques de fiabilité pour les API Node.js : délais d’attente, idempotence, transactions, invalidation du cache, journalisation et latence des dépendances — avant la page de 3 h du matin.

906 mots

Seules certaines leçons de fiabilité restent gravées dans la mémoire après une panne. Les douze ci-dessous se produisent suffisamment souvent, dans suffisamment de services Node.js, pour que les formuler clairement soit moins coûteux que de devoir les redécouvrir sous pression. Considérez-les comme des paramètres par défaut lors de la conception des gestionnaires, et non comme des améliorations optionnelles après le lancement.

1. Les requêtes pouvant être réessayées le seront.

Pas « peut-être ». Les clients expirent, les connexions se coupent au milieu de la réponse, et les utilisateurs envoient des demandes sans cesse. Les balanceurs de charge et les réseaux mobiles ajoutent eux-mêmes des livraisons dupliquées. Tout gestionnaire qui crée ou modifie un état a besoin d’une réponse explicite pour la question « que se passe-t-il si ce même envoi arrive à nouveau », car sur une période suffisamment longue, il ne s’agit pas d’un cas extrême — c’est inévitable. Concevez en même temps le scénario idéal et celui des données dupliquées.

2. Imposez un délai d’expiration à chaque appel sortant.

Les requêtes de base de données, les communications HTTP avec des tiers, les passerelles de paiement : si vous omettez une date limite, un interlocuteur en panne peut laisser le socket ouvert indéfiniment. Une simple commande fetch(url) n’est pas « patiente pendant un certain temps » ; c’est une attente illimitée, et des attentes illimitées permettent à une dépendance lente de geler tout le système en amont. Préférez les paramètres par défaut des bibliothèques qui imposent une limite, et optez pour des erreurs visibles par l’utilisateur ou des tentatives de réessai en cas d’échec, plutôt que pour des blocages silencieux.

3. Les vérifications dans le navigateur relèvent de l’expérience utilisateur, pas d’une barrière de sécurité.

Les utilisateurs peuvent contourner votre SPA et appeler l’API avec du JSON arbitraire à l’aide de curl, Postman ou d’un script compromis. Les règles importantes — adresse e-mail bien formatée, prix non négatif, champs obligatoires — doivent être appliquées à nouveau sur le serveur avec la même rigueur que pour un trafic malveillant. La validation côté client ne sert qu’à aider les utilisateurs coopératifs qui n’auraient de toute façon pas enfreint les règles.

4. Un cache sans mécanisme d’invalidation est un bug différé.

Le cache crée une deuxième version de la vérité, et ces versions finissent par diverger. Lorsque vous introduisez un cache, décidez quel événement d’écriture, de webhook ou de TTL le videra, et documentez cette décision à côté de la clé du cache. Compter sur le fait que « il expire tôt ou tard » signifie souvent que cela prendra plus de temps que les clients ne sont prêts à accepter pour des soldes incorrects, surtout lorsque les TTL sont mesurés en heures au nom de la « performance ».

5. Les écritures couplées nécessitent une transaction, pas de l’optimisme.

await update1(); await update2(); semble atomique, mais ce n’est pas le cas. Entre ces deux appels, une panne, un problème temporaire ou un rejet peuvent faire en sorte qu’une seule mise à jour soit appliquée, ce qui explique pourquoi les données d’inventaire et de facturation finissent par diverger. Seule une véritable transaction de base de données — ou une saga explicite avec des actions compensatoires — permet de fermer cette fenêtre temporelle en offrant des garanties sur lesquelles on peut compter.

6. Outil pour les incidents que vous n’avez pas encore vus.

Lorsque des problèmes surviennent, il est trop tard pour ajouter la ligne de journal qui aurait pu les expliquer. Des journaux structurés, des identifiants de corrélation qui circulent entre les services, ainsi qu’un contexte suffisant pour reconstituer une requête avant sa défaillance, surpassent la plus astucieuse session de débogage a posteriori. Les données de journal que l’on peut conserver en toute sécurité, les résultats obtenus, ainsi que les latences liées aux dépendances lorsque le système fonctionne correctement.

7. La mémoire de processus est un cache, jamais le système de stockage principal.

Une valeur qui n’existe que dans un processus Node disparaît à chaque redémarrage, reste invisible pour les instances sœurs, et n’est jamais persistante. Les indicateurs fonctionnels stockés en RAM, les compteurs ou les tableaux « temporaires » partagent tous ce sort. S’il faut que ces données survivent à un déploiement, à une panne ou à un équilibrage de charge sans sessions persistantes, il faut les stocker dans une base de données ou un autre système de stockage durable, et non dans un emplacement aléatoire en RAM.

8. Prévoyez que chaque appel réseau échouera tôt ou tard.

La question pertinente n’est pas « fonctionne-t-il généralement ? », mais plutôt « que se passe-t-il en cas d’échec rare ? » Les temps d’attente, les réponses partielles et les pics de codes 5xx comptent tous. Si la réponse honnête est « incertain », comblez cette lacune grâce à des tentatives de réessai sécurisées, des disjoncteurs ou des modes dégradés avant que la production ne découvre le problème.

9. Une file d’attente réorganise le travail ; elle ne l’efface pas.

Insérer un élément dans la file donne l’impression que le travail est terminé. Un processus d’exécution effectuera néanmoins la même tâche plus tard, avec les mêmes risques d’échec, simplement différés dans le temps. Si les processus sont ralentis, la pile de tâches augmente même si l’API reste réactive — c’est mieux que de bloquer les requêtes, mais ce n’est pas moins coûteux en termes de travail. Configurez soigneusement les consommateurs de la file et les chemins pour les messages non traités, tout comme vous le faites pour les couches HTTP.

10. Donnez des noms adaptés aux lecteurs de dans six mois, et non pour un raccourci d’aujourd’hui.

data, temp et handleStuff restent précis uniquement tant que trois autres concepts ne sont pas également considérés comme des “données”. Les noms liés à l’idée commerciale fonctionnent même en l’absence de contexte—y compris pour le futur vous utilisant un service inconnu. Préférez les noms de domaine aux verbes mécaniques qui décrivent les premiers détails d’implémentation.

11. Les créateurs seront appelés deux fois.

Des réseaux instables, des clients de tentative agressifs, des actions double-tap impatientes—n’importe lequel d’eux. Les requêtes POST de création nécessitent une gestion adéquate des doublons, généralement via une clé d’idempotence stockée avec la ressource résultante, sinon les doubles facturations et commandes dupliquées deviennent une question de timing. Faites en sorte que la réponse “déjà créé” renvoie le résultat initial plutôt qu’un nouvel effet secondaire.

12. La latence de l’endpoint suit la dépendance la plus lente.

Un gestionnaire élégant qui exécute une requête non indexée ou une API distante sans délai de timeout reste lent. Analyser ses propres lignes de code tout en ignorant les fonctions appelées répond à la mauvaise question. Mesurez les valeurs p95 et p99 sur l’ensemble du graphe d’appels, puis corrigez les index, le regroupement des opérations et les timeouts là où le temps est réellement consommé.

Aucun de ces conseils n’est particulièrement ingénieux, et c’est justement le but : les solutions trouvées à 3 heures du matin le sont rarement non plus. Elles relèvent généralement de l’une de ces douze solutions, laissées de côté jusqu’à ce que la situation ne paraisse plus urgente. Mettez en place ces mesures de sécurité ennuyeuses tant que le service est encore suffisamment petit pour être modifié.