Un chat temps réel, sans serveur
Une messagerie instantanée, ça paraît banal — jusqu'à ce qu'on réalise que le bon vieux « la page demande, le serveur répond » ne tient plus du tout la route. Voyons pourquoi, et ce qu'on met à la place.
Pense à la dernière fois que tu as discuté en ligne. Tu tapes un message, et il apparaît sur l'écran de l'autre presque avant que tu aies relâché la touche Entrée. Cette immédiateté, on la tient pour acquise — mais elle casse complètement le modèle sur lequel repose la plupart des sites web.
Une application web classique fonctionne en question-réponse : ton navigateur demande quelque chose, le serveur répond, et la conversation s'arrête là jusqu'à la prochaine demande. C'est parfait pour afficher une page. Mais dans un chat, le serveur doit pouvoir te parler de lui-même, au moment où un message arrive, sans que tu aies rien demandé. Il faut donc un canal qui reste ouvert dans les deux sens, et un backend qui réagit à des événements plutôt qu'à des requêtes.
Un système où l'information est poussée vers toi à l'instant où elle naît, au lieu d'attendre que tu la réclames. Pour un chat, ça veut dire une connexion persistante et bidirectionnelle : le canal reste branché, et chacun peut parler quand il le souhaite.
On va bâtir ce chat entièrement en serverless, c'est-à-dire sans la moindre machine à administrer. Cinq services AWS se répartissent le travail, et le plus simple est de voir tout de suite à quoi sert chacun :
| Service | Son rôle dans le chat |
|---|---|
| API Gateway (WebSocket) | Tient les connexions ouvertes et achemine chaque message |
| Lambda | Le cerveau : trouve les présents, diffuse, déclenche l'archivage |
| DynamoDB | La mémoire : qui est en ligne, et tout l'historique des messages |
| SQS | L'amortisseur : encaisse les pics avant l'écriture en base |
| IAM | Le vigile : ouvre aux services les seuls accès dont ils ont besoin |
Le cœur de l'affaire se résume à une boucle : un message entre par la porte, le cerveau le redistribue à tout le monde et en range une copie.
Avant d'ouvrir chaque service, le simulateur ci-dessous montre pourquoi le temps réel change tout. Compare ce qui se passe quand un client doit sans cesse demander « du nouveau ? » et quand le serveur peut simplement pousser le message.
Envoie un message et compare les deux approches :
DynamoDB : la mémoire partagée
Un calcul serverless n'a aucune mémoire : il se réveille, s'exécute, et oublie tout. Alors comment savoir, à la milliseconde près, qui est en ligne pour lui transmettre un message ?
Un salon de discussion ne peut pas fonctionner sans mémoire, et c'est là qu'un paradoxe nous attend. Le calcul serverless, par nature, n'a aucune mémoire à lui : il s'éveille, exécute son code, et se rendort en oubliant tout. Or, quand quelqu'un envoie un message, le système doit savoir qui est présent à cette milliseconde précise pour le leur transmettre — et conserver ailleurs la trace durable de la conversation.
Il nous faut donc un cerveau externe, partagé par tous les calculs : quelque chose de très rapide, toujours disponible, et taillé pour des données qui changent en permanence. C'est exactement le terrain de jeu de DynamoDB.
Une base NoSQL entièrement gérée, conçue pour les charges très sollicitées : elle répond en quelques millisecondes quelle que soit sa taille. Comme elle est managée, on ne provisionne aucun serveur et on ne maintient aucune infrastructure — elle devient la couche de mémoire commune de l'application, sans le poids d'exploiter une base soi-même.
Pour notre chat, on lui confie deux rôles bien distincts, donc deux tables.
| Table | Contient | Durée de vie |
|---|---|---|
| connexions actives | L'identifiant de connexion de chaque personne présente | Éphémère : on ajoute à l'arrivée, on retire au départ |
| historique | Chaque message envoyé | Permanente : c'est l'archive du salon |
La première est un tableau de présence qui se remplit et se vide au gré des allées et venues ; la seconde est un grand registre où chaque message s'ajoute pour de bon. Deux usages opposés, une seule technologie.
Le simulateur ci-dessous fait vivre les deux tables côte à côte. Fais entrer et sortir des participants, envoie quelques messages, et observe : la table de présence frémit sans cesse, tandis que l'historique ne fait que s'allonger.
Fais entrer des participants et envoie des messages :
SQS : l'amortisseur du système
Cinq cents personnes écrivent au même instant. Si chaque message file droit dans la base, elle plie sous le choc et le chat devient poussif. Il faut un tampon entre la vitesse et la durée.
On a maintenant où ranger l'historique. Mais écrire en base n'est jamais gratuit : ça prend du temps et ça consomme des ressources. Imagine cinq cents personnes qui appuient sur Entrée au même instant. Si l'application tente d'écrire chaque message directement dans la table d'historique, ce pic soudain d'écritures peut saturer le système — et le salon se met à ramer, avec des messages qui traînent et une impression de lenteur générale.
Le vrai problème, c'est qu'on mélange deux travaux de natures opposées : un travail rapide, diffuser le message en temps réel à ceux qui sont là, et un travail plus lent, l'archiver durablement. Les forcer à avancer au même rythme, c'est laisser le plus lent freiner le plus rapide.
Une file de messages entièrement gérée. Imagine une salle d'attente — ou mieux, un amortisseur. Plutôt que d'imposer à l'application une écriture lourde en base sur-le-champ, elle dépose simplement le message dans la file et repart aussitôt servir le salon. La file conserve le message en sécurité et patiente jusqu'à ce qu'un processus de fond soit prêt à le traiter.
Ce geste a un nom, et c'est l'une des idées les plus importantes des systèmes distribués : le découplage. En glissant la file entre la diffusion et l'archivage, on rend les deux indépendants — l'un peut accélérer sans attendre l'autre, et si l'archivage prend du retard, la file absorbe le surplus au lieu de tout bloquer.
Une file SQS standard privilégie le débit et la simplicité ; elle ne garantit pas un ordre strict ni l'absence de doublon. Si un jour ton besoin réclame un ordre rigoureux, il existe des files FIFO pour ça — mais pour archiver des messages de chat, la version standard fait parfaitement l'affaire. Le cours Files de messages creuse ces compromis — ordre, doublons, files de lettres mortes — bien au-delà d'AWS.
Le simulateur ci-dessous oppose les deux mondes. Déclenche une rafale et regarde la file gonfler puis se vider à rythme constant, pendant que l'écriture en base reste calme — là où l'écriture directe, elle, explose.
Provoque un pic de trafic et observe l'amortisseur :
IAM : franchir le mur de sécurité
Par défaut, dans AWS, rien ne se parle. Ton code ne peut ni lire la base, ni écrire dans la file : tout est verrouillé. Reste à ouvrir exactement les bonnes portes — pas une de plus.
La base est prête, la file aussi. Avant d'écrire la moindre ligne de logique, on se cogne pourtant à un mur — et c'est une bonne nouvelle. Par défaut, dans AWS, les services sont totalement isolés les uns des autres. Notre futur code ne peut pas lire dans DynamoDB, ne peut pas déposer dans SQS : tout est verrouillé d'office.
On pourrait débloquer ça à la hache, en collant des clés d'accès directement dans le code. Mauvaise idée : une clé écrite en dur peut fuiter, être exposée, être détournée — et elle ouvre alors bien plus que ce salon de chat. Ce qu'on cherche, c'est l'inverse : accorder à l'application exactement les permissions dont elle a besoin, et rien de plus.
Le service qui décide qui — et quoi — peut accéder à tes ressources cloud. Pour une application, on ne crée pas un utilisateur avec un mot de passe : on crée un rôle. Pense à un badge de sécurité. Ce n'est pas une personne, c'est un jeu de permissions précis. Quand ton code s'exécute, il porte ce badge le temps d'agir, et n'obtient que l'autorité qu'on y a définie.
Le grand avantage, c'est qu'aucun mot de passe ne traîne dans le code, et qu'il n'y a aucun identifiant durable à gérer : le badge est temporaire par construction. Dans le TP, on crée un rôle dédié à la logique du chat, et on y attache précisément trois permissions.
| Permission | Pourquoi le chat en a besoin |
|---|---|
| lire/écrire DynamoDB | Connaître les présents et archiver les messages |
| envoyer vers SQS | Déposer une copie du message pour l'archivage différé |
| pousser via API Gateway | Renvoyer le message vers les connexions ouvertes |
Le simulateur ci-dessous met ce principe à l'épreuve. Coupe une permission dans la policy, puis demande au cerveau du chat d'accomplir l'action correspondante : sans le bon droit, l'action échoue net.
Règle la policy du rôle, puis lance une action du chat :
Lambda : le cerveau qui diffuse
La base se souvient, la file amortit, le rôle autorise. Il manque celui qui décide : quand un message arrive, trouver tous les présents, le leur envoyer, puis en garder une trace.
Les fondations sont posées : la base retient l'état, la file amortit les pics, le rôle ouvre les bonnes portes. Il manque maintenant celui qui orchestre tout ça — le cerveau. C'est lui qui, dès qu'un message arrive, doit trouver toutes les personnes en ligne, l'afficher sur leur écran, et en glisser une copie dans les archives.
On pourrait faire tourner un serveur dédié vingt-quatre heures sur vingt-quatre à guetter les messages. Mais ce serait coûteux, et le faire grossir pendant les pics deviendrait vite un casse-tête. On ne veut pas d'une machine qui veille toute la journée ; on veut du code qui s'éveille uniquement quand il se passe quelque chose, s'exécute dans l'instant, et disparaît une fois le travail fait.
Le service de calcul serverless d'AWS. Tu déposes ton code, tu définis un déclencheur, et Lambda s'occupe du reste : il n'exécute ta fonction que lorsqu'un événement survient. Si dix messages arrivent en même temps, AWS lance aussitôt dix copies isolées de ton code, qui traitent chacune leur événement puis s'éteignent. Aucun serveur à gérer, aucune règle de montée en charge à régler.
Pour le chat, on déploie deux fonctions, et toutes deux portent le rôle IAM construit juste avant. Leur séparation n'est pas un détail : c'est elle qui garde la diffusion rapide et l'archivage tranquille.
Déclenché à chaque message, il interroge la table des connexions pour trouver tous les présents, diffuse le message à chacun, puis dépose une copie dans la file SQS. Son obsession : aller vite.
Déclenché non par l'utilisateur mais par la file SQS, il travaille en arrière-plan : il lit les messages de la file et les écrit dans la table d'historique, à son rythme, sans jamais ralentir la diffusion.
Ce partage des rôles est la clé de voûte de toute l'architecture. La diffusion ne s'occupe que du présent et reste véloce ; l'archivage avance en coulisses ; et la base, alimentée par la file, ne voit jamais passer les rafales.
Le simulateur ci-dessous montre le cerveau à l'œuvre. Fais arriver un message : observe-le interroger les présents, se diffuser à chacun, puis filer dans la file vers l'archiviste.
Fais arriver un message et suis le travail du cerveau :
Le réseau du temps réel
Derrière le mot « WebSocket » se cache une petite bascule réseau. Avant de voir comment AWS nous la sert clé en main, ouvrons le capot : qu'est-ce qui, côté réseau, rend une conversation vraiment instantanée ?
On a un backend complet, fin prêt à diffuser des messages. Mais entre lui et les utilisateurs, il y a le réseau — et c'est lui, au fond, qui décide si une conversation peut être réellement instantanée. Prenons un moment pour comprendre ce qui s'y joue, parce que c'est précisément le problème qu'API Gateway résoudra pour nous à la section suivante.
Le web ordinaire repose sur un échange poli, mais où l'initiative ne va que dans un sens : le client demande, le serveur répond, et tout le monde raccroche. Le serveur n'a aucun moyen de reprendre la parole de lui-même. Pour un chat, c'est rédhibitoire — comment te livrer le message d'un autre si tu n'as rien demandé ?
Avant HTTP, avant WebSocket, il y a TCP : une connexion fiable et ordonnée entre deux machines, un tuyau d'octets qui circule dans les deux sens. HTTP comme WebSocket ne sont que deux manières différentes de se servir de ce même tuyau.
Si la plomberie des sockets t'intrigue — comment ce tuyau s'ouvre, se ferme et transporte les octets —, c'est tout l'objet du cours TCP & Sockets.
Le coup de génie de WebSocket, c'est qu'il ne réinvente rien. Une connexion WebSocket commence comme une requête HTTP des plus banales : le client y ajoute simplement un en-tête qui propose « et si on passait en WebSocket ? » (Upgrade: websocket). Si le serveur accepte, il répond par un code de statut un peu spécial — 101 Switching Protocols — et, à partir de cet instant, la même connexion TCP cesse de parler HTTP pour parler WebSocket.
Cette montée en gamme du protocole, depuis le HTTP/1.1 jusqu'à HTTP/2 et HTTP/3, a tout un cours dédié : HTTP · De 1.1 à 3.
Une fois le canal ouvert, on récolte le full-duplex : les deux extrémités peuvent parler et écouter en même temps, sans jamais attendre leur tour.
Un mégaphone, c'est du simplex : ça ne va que dans un sens. Un talkie-walkie, c'est du half-duplex : chacun parle à son tour. Un téléphone, c'est du full-duplex : on peut se couper la parole, justement parce que les deux voix passent en même temps. Le WebSocket, c'est le téléphone.
WebSocket n'est d'ailleurs pas la seule façon de faire du temps réel : c'est l'aboutissement d'une petite famille de techniques, chacune un cran moins bricolée que la précédente.
| Technique | Principe | Sens |
|---|---|---|
| Polling | Le client redemande à intervalle fixe | Client → serveur, en boucle |
| Long-polling | Le serveur garde la requête ouverte jusqu'à avoir du neuf | Une requête par message |
| SSE | Un flux permanent du serveur vers le client | Serveur → client seulement |
| WebSocket | Un canal permanent, ouvert dans les deux sens | Bidirectionnel, full-duplex |
Le cousin à sens unique, les Server-Sent Events et le streaming HTTP en général, méritent le détour quand le serveur n'a qu'à pousser sans rien recevoir : le cours Streaming HTTP y est consacré.
Reste la latence, et c'est l'argument décisif. Rouvrir une connexion à chaque message coûte cher : il faut repayer la poignée de main TCP, et le chiffrement par-dessus. Dans un chat où les messages fusent, ces allers-retours répétés se transforment vite en saccades. La connexion persistante du WebSocket, elle, ne paie cette mise en place qu'une seule fois ; ensuite, chaque message file sans cérémonie.
wss:// — du WebSocket par-dessus TLS, exactement comme https:// est du HTTP par-dessus TLS. Le cours TLS/HTTPS explique cette couche de sécurité, valable ici à l'identique.
Une dernière question, qu'on oublie presque toujours : comment une connexion se ferme ? Proprement, comme elle s'ouvre. L'une des deux extrémités envoie une trame de fermeture — un close frame — avec un petit code qui dit pourquoi : 1000 pour une fermeture normale, 1001 quand un serveur s'arrête, 1009 si un message dépasse la taille permise. L'autre côté accuse réception, et le tuyau TCP se démonte. Entre-temps, pour vérifier que le voisin est toujours là, chacun peut envoyer de petites trames ping auxquelles l'autre répond par un pong.
Voilà le décor réseau planté. La bonne nouvelle, c'est que tu ne géreras rien de tout cela à la main : API Gateway tient les connexions, orchestre le handshake et te livre les événements tout prêts. C'est l'objet de la section suivante.
Le simulateur ci-dessous rejoue l'établissement d'une connexion : la poignée de main TCP, puis la bascule en WebSocket. Une fois le canal ouvert, fais parler le client et le serveur dans n'importe quel ordre — sans jamais rouvrir quoi que ce soit.
Établis la connexion, puis discute dans les deux sens :
API Gateway : le standard téléphonique
Tout le backend tourne, mais les utilisateurs restent dehors. Leur faire interroger le serveur en boucle épuiserait la batterie pour rien. Ce qu'il faut, c'est une ligne ouverte en permanence.
Le backend est complet : la base est prête, la file amortit, le cerveau traite. Et pourtant, les utilisateurs restent sur le pas de la porte — ils n'ont encore aucun moyen direct d'atteindre notre logique.
On pourrait leur ouvrir une API HTTP classique. Mais alors, pour savoir s'il y a du nouveau, l'appareil de chacun devrait interroger le serveur sans relâche : « un message ? un message ? un message ? », seconde après seconde. Ça vide les batteries, ça génère un trafic inutile, et ça introduit malgré tout un décalage. Ce qu'il nous faut est d'une autre nature : une seule porte ouverte en permanence, une ligne continue où les messages circulent dans les deux sens, à l'instant.
Là où le HTTP classique raccroche après chaque échange, une connexion WebSocket reste ouverte et permet une communication bidirectionnelle : les deux extrémités peuvent parler et écouter en même temps. Pense à un appel téléphonique — une fois la ligne établie, personne n'a besoin de recomposer le numéro à chaque phrase.
Toute la mécanique réseau de ce canal — le handshake d'upgrade, le full-duplex, la différence avec le polling — est détaillée juste avant, à la section Le réseau du temps réel. Ici, on se contente de profiter du fait qu'API Gateway s'en charge à notre place.
Dans ce rôle, API Gateway devient le standardiste du système. Il maintient pour nous des milliers de connexions ouvertes, et dès qu'un message arrive sur l'une d'elles, il l'achemine vers notre fonction — sans qu'on ait à gérer le moindre serveur ni la moindre infrastructure de connexion.
Concrètement, on crée une API WebSocket, puis on définit des routes pour les trois moments de la vie d'une connexion :
On branche notre chat handler sur ces routes : à chaque événement — quelqu'un se connecte, se déconnecte ou parle — API Gateway déclenche aussitôt notre logique. C'est la dernière pièce, la porte d'entrée publique de toute l'architecture.
$disconnect est déclenchée au mieux : lors d'une coupure réseau brutale, API Gateway n'a pas toujours l'occasion de la signaler. Ta table des connexions peut donc conserver des entrées mortes. Le réflexe est d'y poser un TTL — une date d'expiration automatique — dans DynamoDB, pour que les connexions oubliées s'effacent d'elles-mêmes.
Le simulateur ci-dessous est ce standard téléphonique. Connecte quelques clients, fais-en parler un, et regarde le message remonter jusqu'à la logique avant d'être renvoyé, en un instant, à toutes les lignes ouvertes.
Connecte des clients, puis fais-en parler un :
Le tableau complet
Cinq services, chacun avec une responsabilité nette. Assemblés, ils forment un système temps réel qui ne dort jamais tout à fait et ne coûte rien quand le salon est vide.
On a posé les pièces une à une ; reculons d'un pas pour voir le tableau complet. Chacune a une responsabilité que tu peux désormais résumer d'une phrase :
| Service | Sa responsabilité |
|---|---|
| API Gateway (WebSocket) | Gère les connexions et achemine chaque événement |
| Lambda | Traite les événements : diffuse en direct, déclenche l'archivage |
| IAM | Sécurise la communication entre les services |
| SQS | Amortit les pics de trafic avant la base |
| DynamoDB | Conserve l'état (qui est en ligne) et l'historique |
Le trajet d'un message les fait travailler de concert : il entre par le standard, réveille le cerveau qui le diffuse à tous les présents, et en dépose une copie qui finira tranquillement archivée.
Ce qui rend ce système solide, ce n'est pas une brique en particulier, mais la manière dont elles sont reliées. La connexion persistante donne l'immédiateté ; le découplage par la file donne la résistance aux rafales ; la séparation des deux fonctions garde la diffusion rapide ; et tout cela monte ou descend en charge tout seul, sans jamais coûter quoi que ce soit quand le salon est désert.
Tu ne sais pas seulement comment marche un chat. Tu sais comment on architecture un système temps réel dans le cloud : une connexion bidirectionnelle persistante, un état partagé en mémoire rapide, un découplage par file pour encaisser les pics, une logique événementielle qui se réveille à la demande, et une sécurité accordée au plus juste.
La dernière étape ne se lit pas, elle se construit : c'est le capstone. Reprends ces cinq services et câble-les toi-même, de zéro. Parce que lire une architecture, c'est une chose ; la bâtir de ses mains, c'est là qu'on en saisit vraiment l'idée.