Il y a quelques mois, j'étais en plein entretien technique pour un poste front-end de niveau intermédiaire. Les choses se déroulaient bien jusqu'à ce que je reçoive une question qui m'a légèrement pris au dépourvu.
"Imaginez que vous ayez besoin d'une forme de communication constante pour vérifier quelque chose toutes les secondes jusqu'à ce que vous récupériez quelque chose dont vous avez besoin.
Par exemple, vous souhaitez continuer à vérifier si un paiement a réussi, comme dans une configuration de commerce électronique. Comment aborderiez-vous cela ? »
J'ai répondu prudemment : « Je pense que vous pourriez implémenter des WebSockets pour gérer cela. »
L'intervieweur a souri. "C'est une bonne solution, mais il existe d'autres options, sans doute meilleures, en fonction de la situation."
Et c'est à ce moment-là que nous avons plongé dans une conversation sur diverses approches de communication en temps réel, notamment Long Polling, Short Polling, WebSockets, et enfin, Server-Sent Events (SSE), qui est sans doute le meilleur choix pour un flux de données unidirectionnel, comme dans notre exemple de paiement.
Nous avons également discuté du choix de la bonne base de données pour gérer ces requêtes constantes mais légères sans épuiser les ressources du serveur. Dans ce contexte, Redis est apparu, connu pour sa simplicité et son efficacité dans la gestion de ce type de requêtes.
Cette conversation m'a marqué. J'ai réalisé que même si les WebSockets retiennent beaucoup d'attention, il existe un large éventail de techniques qui, une fois comprises, peuvent optimiser la façon dont nous gérons la communication en temps réel. Aujourd'hui, je souhaite décomposer ces quatre approches, quand utiliser chacune d'elles, ainsi que leurs avantages et inconvénients, d'une manière claire et engageante. À la fin, vous comprendrez parfaitement pourquoi les événements envoyés par le serveur (SSE) brillent souvent pour la communication unidirectionnelle en temps réel.
Avant de commencer, un grand merci à Marcos, l'ingénieur logiciel senior expérimenté qui a mené cette discussion et m'a inspiré à écrire cet article des mois plus tard, ce que j'ai beaucoup apprécié même si je n'ai pas obtenu le poste ! :)
Avant de passer à l’exemple SSE, décomposons les quatre méthodes dont nous avons discuté lors de cet entretien :
L'interrogation courte est probablement la méthode la plus simple. Cela implique de faire une requête au serveur à intervalles réguliers, en demandant : « Avez-vous de nouvelles données ? Le serveur répond avec l'état actuel, qu'il y ait ou non quelque chose de nouveau.
Avantages :
Inconvénient :
Idéal pour : Petites mises à jour de données à basse fréquence, comme le cours de la bourse, à mettre à jour toutes les minutes environ.
Les sondages longs vont encore plus loin avec les sondages courts. Le client demande à plusieurs reprises des informations au serveur, mais au lieu que le serveur réponde immédiatement, il conserve la connexion jusqu'à ce que de nouvelles données soient disponibles. Une fois les données renvoyées, le client ouvre immédiatement une nouvelle connexion et répète le processus.
Avantages :
Inconvénient :
Idéal pour : Situations dans lesquelles une communication en temps réel est nécessaire mais où les WebSockets/SSE peuvent être excessifs (par exemple, les applications de chat).
Les WebSockets sont une solution plus moderne qui assure une communication en duplex intégral entre le client et le serveur. Une fois la connexion ouverte, les deux parties peuvent envoyer des données librement sans rétablir les connexions - ce qui définit une communication bidirectionnelle.
Avantages :
Inconvénient :
Idéal pour : Applications nécessitant une communication bidirectionnelle constante, comme les jeux multijoueurs, les outils collaboratifs, les applications de chat ou les notifications en temps réel.
Enfin, nous arrivons aux Server-Sent Events (SSE), le héros de notre exemple de paiement. SSE crée une connexion unidirectionnelle dans laquelle le serveur envoie des mises à jour au client. Contrairement aux WebSockets, c'est unidirectionnel : le client ne renvoie pas de données.
Avantages :
Inconvénient :
Idéal pour : Mises à jour en temps réel où le client n'a besoin que de recevoir des données, telles que les scores en direct, les notifications et notre exemple de statut de paiement.
Entrons dans le vif du sujet. J'ai créé une application Next.js simple pour simuler un processus de paiement en temps réel à l'aide de Server-Sent Events (SSE). Il montre exactement comment vous pouvez configurer une communication unidirectionnelle pour vérifier l'état d'un paiement et informer l'utilisateur lorsque le paiement réussit ou échoue.
C'est un peu un casse-tête de le configurer pour Next car il fonctionne un peu différemment du plain js donc vous pourrez me remercier plus tard !
Voici la configuration :
Dans le composant suivant, nous avons une interface utilisateur simple affichant des boutons pour simuler différents types de transactions qui proviendraient d'une véritable API de passerelle (Pix, Stripe et un paiement par carte de crédit défaillant). Ces boutons déclenchent des mises à jour de l'état des paiements en temps réel via SSE.
C’est ici que la magie SSE opère. Lorsqu'un paiement est simulé, le client ouvre une connexion SSE pour écouter les mises à jour du serveur. Il gère différents statuts comme en attente, en transit, payé et en échec.
"use client"; import { useState } from "react"; import { PAYMENT_STATUSES } from "../utils/payment-statuses"; const paymentButtons = [ { id: "pix", label: "Simulate payment with Pix", bg: "bg-green-200", success: true, }, { id: "stripe", label: "Simulate payment with Stripe", bg: "bg-blue-200", success: true, }, { id: "credit", label: "Simulate failing payment", bg: "bg-red-200", success: false, }, ]; type transaction = { type: string; amount: number; success: boolean; }; const DOMAIN_URL = process.env.NEXT_PUBLIC_DOMAIN_URL; export function TransactionControl() { const [status, setStatus] = useState<string>(""); const [isProcessing, setIsProcessing] = useState<boolean>(false); async function handleTransaction({ type, amount, success }: transaction) { setIsProcessing(true); setStatus("Payment is in progress..."); const eventSource = new EventSource( `${DOMAIN_URL}/payment?type=${type}&amount=${amount}&success=${success}` ); eventSource.onmessage = (e) => { const data = JSON.parse(e.data); const { status } = data; console.log(data); switch (status) { case PAYMENT_STATUSES.PENDING: setStatus("Payment is in progress..."); break; case PAYMENT_STATUSES.IN_TRANSIT: setStatus("Payment is in transit..."); break; case PAYMENT_STATUSES.PAID: setIsProcessing(false); setStatus("Payment completed!"); eventSource.close(); break; case PAYMENT_STATUSES.CANCELED: setIsProcessing(false); setStatus("Payment failed!"); eventSource.close(); break; default: setStatus(""); setIsProcessing(false); eventSource.close(); break; } }; } return ( <div> <div className="flex flex-col gap-3"> {paymentButtons.map(({ id, label, bg, success }) => ( <button key={id} className={`${bg} text-background rounded-full font-medium py-2 px-4 disabled:brightness-50 disabled:opacity-50`} onClick={() => handleTransaction({ type: id, amount: 101, success }) } disabled={isProcessing} > {label} </button> ))} </div> {status && <div className="mt-4 text-lg font-medium">{status}</div>} </div> ); }
Côté serveur, nous simulons un processus de paiement en envoyant des mises à jour périodiques du statut via SSE. Au fur et à mesure que la transaction progresse, le client recevra des informations indiquant si le paiement est toujours en attente, a été effectué ou a échoué.
import { NextRequest, NextResponse } from "next/server"; import { PAYMENT_STATUSES } from "../utils/payment-statuses"; export const runtime = "edge"; export const dynamic = "force-dynamic"; // eslint-disable-next-line @typescript-eslint/no-unused-vars export async function GET(req: NextRequest, res: NextResponse) { const { searchParams } = new URL(req.url as string); const type = searchParams.get("type") || null; const amount = parseFloat(searchParams.get("amount") || "0"); const success = searchParams.get("success") === "true"; if (!type || amount < 0) { return new Response(JSON.stringify({ error: "invalid transaction" }), { status: 400, headers: { "Content-Type": "application/json", }, }); } const responseStream = new TransformStream(); const writer = responseStream.writable.getWriter(); const encoder = new TextEncoder(); let closed = false; function sendStatus(status: string) { writer.write( encoder.encode(`data: ${JSON.stringify({ status, type, amount })}\n\n`) ); } // Payment gateway simulation async function processTransaction() { sendStatus(PAYMENT_STATUSES.PENDING); function simulateSuccess() { setTimeout(() => { if (!closed) { sendStatus(PAYMENT_STATUSES.IN_TRANSIT); } }, 3000); setTimeout(() => { if (!closed) { sendStatus(PAYMENT_STATUSES.PAID); // Close the stream and mark closed to prevent further writes writer.close(); closed = true; } }, 6000); } function simulateFailure() { setTimeout(() => { if (!closed) { sendStatus(PAYMENT_STATUSES.CANCELED); // Close the stream and mark closed to prevent further writes writer.close(); closed = true; } }, 3000); } if (success === false) { simulateFailure(); return; } simulateSuccess(); } await processTransaction(); // Return the SSE response return new Response(responseStream.readable, { headers: { "Access-Control-Allow-Origin": "*", Connection: "keep-alive", "X-Accel-Buffering": "no", "Content-Type": "text/event-stream; charset=utf-8", "Cache-Control": "no-cache, no-transform", "Content-Encoding": "none", }, }); }
Assurez-vous également d'ajouter le fichier .env.local avec ce contenu :
NEXT_PUBLIC_DOMAIN_URL='http://localhost:3000'
Maintenant que nous avons vu comment l'implémenter, vous vous demandez peut-être : pourquoi utiliser SSE plutôt que WebSockets pour cela ? Voici pourquoi :
Cette question d'entretien s'est transformée en une expérience d'apprentissage incroyable, m'ouvrant les yeux sur les différences subtiles entre les sondages longs, les sondages courts, les WebSockets et SSE. Chaque méthode a son temps et son lieu, et comprendre quand utiliser laquelle est crucial pour optimiser la communication en temps réel.
SSE n'est peut-être pas aussi glamour que WebSockets, mais lorsqu'il s'agit de communication efficace et unidirectionnelle, c'est l'outil parfait pour le travail, tout comme dans notre exemple de paiement de commerce électronique. La prochaine fois que vous créerez quelque chose qui nécessite des mises à jour en temps réel, ne vous contentez pas d'utiliser les WebSockets par défaut : pensez à SSE pour sa simplicité et son efficacité.
J'espère que cette plongée approfondie dans les techniques de communication en temps réel vous permettra de rester alerte pour votre prochain projet ou cette question délicate d'entretien !
Next.js + Dépôt d'exemples TypeScript : https://github.com/brinobruno/sse-next
Exemple de déploiement Next.js + TypeScript : https://sse-next-one.vercel.app/
voici quelques sources et références faisant autorité que vous pourriez explorer pour des informations plus approfondies :
MDN Web Docs : l'API WebSockets
MDN Web Docs : Utilisation des événements envoyés par le serveur
Next.js : Routes API
Je partagerai mes réseaux sociaux pertinents au cas où vous souhaiteriez vous connecter :
Github
LinkedIn
Portefeuille
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!