Créer un chat d’assistance appuyé par Slack en ColdFusion - Partie 2 : Pourquoi j’ai utilisé les événements envoyés par le serveur plutôt que les WebSockets

Parce que parfois, la solution plate gagne

Si vous demandez à dix développeurs comment bâtir une application en temps réel, environ onze d’entre eux vous diront d’utiliser WebSockets. C’est parce que les WebSockets, c’est cool. C’est bidirectionnel. C’est rapide. C’est efficace. C’est conçu précisément pour la communication en temps réel.

C’est aussi la réponse à un nombre surprenant de problèmes que personne n’a réellement.

Ne me faites pas dire ce que je n’ai pas dit. Les WebSockets sont une technologie fantastique. Si votre application a vraiment besoin d’une communication en duplex intégral entre le navigateur et le serveur, c’est exactement le bon outil. La mienne n’en avait pas besoin. Comme pour beaucoup de projets logiciels, j’ai failli opter pour les WebSockets simplement parce que le mot « chat » apparaissait quelque part dans les exigences. Heureusement, je me suis arrêté avant de transformer accidentellement une fonctionnalité simple en projet de fin de semaine... suivi de trois mois de débogage.

Parlons des exigences réelles

Avant de choisir une technologie, notez ce que votre application doit réellement faire. Dans mon cas, le navigateur devait :

  • Recevoir de nouveaux messages de clavardage.
  • Recevoir des mises à jour d’état.
  • Recevoir des notifications indiquant que quelqu’un avait rejoint ou quitté la conversation.
  • Se reconnecter automatiquement si la connexion était interrompue.

C’est à peu près tout. Vous remarquez ce qui manque? Le navigateur n’avait jamais besoin d’envoyer des données sur une connexion persistante. Chaque fois que l’utilisateur envoie un message, il fait déjà un simple POST HTTP.

Browser
    │
    │ POST /sendMessage
    ▼
ColdFusion

Le navigateur sait déjà comment envoyer des données. Tous les navigateurs sur Terre le font depuis approximativement la période jurassique. Et par période jurassique, je veux dire les <frameset>s et les boutons « Best Viewed with IE ».

La seule chose que le navigateur ne pouvait pas faire, c’était recevoir des mises à jour non sollicitées du serveur. C’est un problème beaucoup plus petit.

Le sens unique suffit

La plupart des clavardages de soutien sont étonnamment asymétriques. Le navigateur envoie un message. La requête se termine. Un peu plus tard, le serveur a quelque chose de nouveau à dire.

Ce n’est pas une conversation. C’est une série de requêtes HTTP indépendantes suivies de notifications occasionnelles. Les événements envoyés par le serveur ont été conçus presque précisément pour ce problème.

Browser
    │
    │ EventSource
    ▼
ColdFusion

Server
    │
    │ event: message
    ▼
Browser

Une seule réponse HTTP persistante. Le serveur écrit des événements chaque fois que quelque chose d’intéressant se produit. Le navigateur écoute tranquillement. Tout le monde rentre chez soi content.

Le navigateur sait déjà comment faire

L’une de mes fonctionnalités préférées des événements envoyés par le serveur, c’est la très faible quantité de JavaScript requise.

const events = new EventSource("/chat/events");

events.onmessage = (event) => {
	const message = JSON.parse(event.data);
	addMessage(message);
};

C’est essentiellement tout. Pas de bibliothèques client. Pas de négociation de connexion. Pas de gestion de socket. Pas de « la connexion s’est-elle fermée? » Pas de « pourquoi Brave fait-il quelque chose de différent de Firefox? » Le navigateur gère la reconnexion automatiquement.

Honnêtement, la partie la plus difficile, c’est de se souvenir que l’API existe, parce que tout le monde est trop occupé à parler de WebSockets.

ColdFusion (et moi) aimons ce qui est ennuyeux

ColdFusion sert des réponses HTTP depuis des décennies. Les événements envoyés par le serveur sont... simplement une réponse HTTP. Ça veut dire aucune mise à niveau de protocole. Aucune bibliothèque spéciale. Aucune infrastructure WebSocket dédiée. Aucun numéro d’acrobate avec le proxy inverse. Pas besoin de se demander si votre équilibreur de charge prend en charge ce projet GitHub astucieux avec 17 étoiles et un seul mainteneur disparu en 2021.

Pour ColdFusion, vous écrivez simplement du texte dans une réponse HTTP qui reste ouverte. C’est rafraîchissant d’ennui. L’ennui, c’est bien. L’ennui paie mon hypothèque.

La reconnexion automatique est étonnamment agréable

Les réseaux tombent en panne. Le Wi-Fi coupe. Quelqu’un ferme son portable. Un pare-feu d’entreprise décide qu’aujourd’hui est le jour où il déteste le bonheur.

Les événements envoyés par le serveur se reconnectent tout simplement.

Le navigateur tente discrètement une autre connexion. Vous pouvez même inclure des identifiants d’événement afin que le navigateur indique au serveur le dernier événement qu’il a réussi à recevoir.

Last-Event-ID: 41872

Le serveur peut alors rejouer tout ce que le navigateur a manqué. Aucune synchronisation compliquée. Aucun processus de récupération élaboré. Aucun billet de soutien intitulé : « Le clavardage a cessé de fonctionner après que j’ai débranché mon portable en embarquant dans un avion. »

Même si vous recevrez quand même ce billet. Il s’avérera juste être quelque chose de complètement sans rapport.

Et la mise à l’échelle?

C’est généralement la première objection. « Mais chaque client a une connexion de longue durée! »

Oui.

Tout comme chaque WebSocket. Une connexion ouverte, c’est une connexion ouverte. Si votre application doit prendre en charge cinquante mille utilisateurs simultanés, vous devrez réfléchir soigneusement à votre architecture, quel que soit le protocole de transport.

Pour l’écrasante majorité des applications d’affaires, les événements envoyés par le serveur se comportent très bien à l’échelle. Si vous finissez par les dépasser, félicitations. Votre entreprise a des problèmes beaucoup plus agréables que de choisir entre EventSource et WebSockets.

Mais les WebSockets sont plus rapides!

Probablement. La question est : vos utilisateurs peuvent-ils le constater?

Si les réponses de soutien arrivent en 40 millisecondes plutôt qu’en 80 millisecondes, personne ne le remarque. Si les réponses arrivent en deux secondes parce que quelqu’un a écrit une couche de messagerie inutilement complexe, tout le monde le remarque. La performance n’a de valeur que lorsque les utilisateurs peuvent la percevoir. Personne n’a jamais ouvert un billet de soutien parce que sa réponse de clavardage est arrivée avec 37 millisecondes de retard. Beaucoup en ont ouvert un parce qu’elle n’est jamais arrivée du tout.

L’effort d’ingénierie coûte toujours cher. Optimisez ce que les gens vivent vraiment.

Les inconvénients

Les événements envoyés par le serveur ne sont pas magiques. Ils sont à sens unique. Si votre application a vraiment besoin d’une communication bidirectionnelle continue, les WebSockets restent le meilleur choix. Quelques exemples :

  • Jeux multijoueurs.
  • Éditeurs collaboratifs.
  • Tableaux de bord de négociation financière.
  • Logiciels de bureau à distance.
  • Applications où les deux côtés échangent constamment des données.

Ce sont de vrais problèmes pour les WebSockets. Un clavardage de service à la clientèle, généralement pas. Parfois, on entend le mot « chat » et on imagine immédiatement Discord, Microsoft Teams, Slack, Zoom et un petit centre de données d’infrastructure. La plupart des clavardages de soutien sont considérablement moins excitants.

Ils ressemblent davantage à du courriel avec un meilleur timing.

La plus grande leçon

La plus grande leçon de ce projet n’était pas de savoir comment fonctionnent les événements envoyés par le serveur. C’était de se rappeler de résoudre le problème d’aujourd’hui plutôt que le problème imaginaire de demain. Les développeurs adorent construire pour une échelle infinie, une flexibilité infinie, une extensibilité infinie et une configuration infinie.

Pendant ce temps, le responsable produit essaie simplement de répondre à la question d’un client avant qu’il n’abandonne son panier d’achat. Parfois, l’architecture la plus simple est vraiment la meilleure architecture. Pas parce qu’elle est élégante. Parce qu’elle est compréhensible. Dans six mois, quand vous aurez oublié comment ça fonctionne, vous remercierez votre moi du passé d’avoir choisi la solution qui tient sur un tableau blanc au lieu d’exiger une conférence. Le David du présent est toujours reconnaissant quand le David du passé lui laisse quelque chose qu’il peut comprendre.

Votre vous du passé est parfois plus intelligent que votre vous du présent. Pas souvent, mais parfois.

La prochaine fois

Maintenant que nous nous sommes entendus sur les événements envoyés par le serveur, il est temps de bâtir réellement la plomberie.

Dans le prochain article, nous créerons le modèle de conversation qui relie les navigateurs, les enregistrements de base de données et les fils Slack en un seul système cohérent. Nous verrons pourquoi chaque conversation a besoin d’une identité durable, comment faire correspondre cette identité aux fils Slack, et pourquoi consacrer une heure de plus à concevoir votre modèle de données peut vous éviter plusieurs week-ends de regrets plus tard.

Parce que rien ne dit mieux « j’aurais dû passer une heure de plus sur le schéma » que de découvrir que votre base de données ne peut pas répondre à la question : « Pour quel client ce message est-il vraiment? »