Aller au contenu principal
Paris / --:--:--

Hey , Je m'appelle Clément.

Stage ingénieur informatique · juin 2027 Recrutez-moi
Retour

Réseau social en microservices avec messagerie temps réel

Rôle: Tech lead (architecture, gateway, observabilité, majorité des services) Équipe: Équipe de 4 Durée: 4 semaines

Résultats

  • Huit unités déployables (six microservices, gateway Nginx, web Next.js) avec une base par service ; toutes les fonctionnalités du backlog MoSCoW livrées, y compris le tiers « could have »
  • Messagerie temps réel (1-à-1 et groupes, accusés de lecture, fils de réponse) en WebSocket, notifications live en SSE et Web Push, streaming vidéo avec requêtes HTTP range
  • Logs, métriques et traces câblés de bout en bout (Pino → Promtail → Loki, Prometheus, OpenTelemetry → Jaeger, dashboards Grafana) ; la CI lance les tests avec couverture sur Codecov et génère une spec OpenAPI agrégée
TypeScript Node.js Next.js gRPC RabbitMQ PostgreSQL MongoDB Redis Nginx Docker

Breezy est un réseau social de messages courts : posts, commentaires imbriqués, likes, graphe d’abonnements avec fil personnalisé, upload de médias, messagerie privée et notifications temps réel. C’était le livrable du cours de Développement d’Application Distribuée en troisième année au CESI. La consigne : construire le produit comme un ensemble de services indépendants plutôt qu’un monolithe, et justifier chaque choix de communication entre eux.

J’étais tech lead d’une équipe de quatre et j’ai écrit environ 300 des ~480 commits : l’application web, la majorité des services, la configuration de la gateway, la stack d’observabilité et le Docker Compose de production.

Architecture

Le dépôt est un monorepo Turborepo (npm workspaces). Les packages partagés (@breezy/logger, @breezy/observability, @breezy/emails, configs ESLint et TypeScript) vivent à côté des apps.

ServiceStockageResponsabilité
auth-servicePostgreSQL + RedisInscription, connexion, vérification d’email, 2FA, Google OAuth, émission des JWT
profile-servicePostgreSQLProfils, avatars, graphe de follow ; serveur gRPC pour les autres services
post-serviceMongoDBPosts, commentaires, likes, fil, recherche
media-serviceMongoDB (GridFS)Traitement d’images, streaming vidéo avec range requests ; serveur gRPC
message-serviceMongoDBConversations 1-à-1 et de groupe, WebSocket Socket.io
notifications-serviceMongoDBConsomme les événements RabbitMQ, pousse en SSE et Web Push, envoie les emails

La persistance est volontairement polyglotte : PostgreSQL là où l’intégrité référentielle compte (comptes, relations de follow), MongoDB là où les documents varient (posts avec médias optionnels et réponses imbriquées), GridFS pour les gros fichiers, Redis pour tout ce qui a un TTL court (refresh tokens, codes 2FA, tokens de vérification). Aucune base n’est partagée entre services.

Quatre modes de communication

REST via la gateway. Chaque requête du navigateur passe par Nginx, qui termine le TLS, valide le JWT via auth_request auprès du service d’auth, applique le rate limiting et route par préfixe de chemin vers le bon service. Deux zones limit_req sont définies : une pour les endpoints d’auth indexée par IP client, une pour l’API indexée par le header Authorization, pour qu’un token abusif ne puisse pas pénaliser les autres utilisateurs.

gRPC entre services. Appels synchrones à faible latence avec un contrat strict : le post-service demande au profile-service les auteurs et le graphe de follow, le notifications-service résout les destinataires, l’auth-service vérifie si un profil est complet pour encoder l’état d’onboarding dans le JWT.

RabbitMQ pour les événements. Les services auth, profile, post et message publient sur un exchange breezy.events ; le notifications-service consomme tout, et les événements de bannissement sont aussi consommés par post et profile pour nettoyer leurs propres données. Le producteur ne sait jamais qui écoute.

SSE et WebSocket vers le navigateur. Les notifications sont unidirectionnelles, donc en Server-Sent Events sur /api/notifications/stream. La messagerie a besoin des deux sens, donc le message-service tourne sur Socket.io : chaque client authentifié rejoint une room identifiée par son userId, ce qui diffuse vers tous les appareils de l’utilisateur en même temps. Les nouveaux messages déclenchent aussi une Web Push pour les utilisateurs absents de la page.

Authentification et sécurité

Les mots de passe sont hachés avec le scrypt natif de Node et un sel aléatoire par mot de passe, stockés sous la forme salt:hash. Les access tokens sont des JWT RS256 signés uniquement par l’auth-service ; les autres services vérifient la signature localement avec la clé publique montée, donc l’authentification reste stateless sans appel à l’auth-service à chaque requête. Les refresh tokens vivent dans Redis avec rotation, exécutée dans un script Lua pour être atomique. Les rôles (visitor, user, moderator, admin) correspondent à une matrice de permissions (post:delete:own vs post:delete:any, user:suspend, user:ban) appliquée dans chaque service. helmet pose les headers de sécurité et express-rate-limit double les zones Nginx.

Observabilité

Chaque service écrit des logs JSON structurés avec Pino (champs sensibles masqués, corrélation par request id) sur un volume partagé ; Promtail les envoie dans Loki. Chaque service expose un /metrics scrapé par Prometheus et un endpoint de santé. L’instrumentation OpenTelemetry exporte les traces vers Jaeger en OTLP. Grafana est provisionné depuis le dépôt avec ses sources de données et dashboards, dont le monitoring RabbitMQ. La première version de la chaîne de logs était Filebeat → Elasticsearch → Kibana ; je l’ai remplacée par Promtail et Loki, plus légère à faire tourner et intégrée dans la même instance Grafana que les métriques.

Infrastructure et CI

En développement, seule l’infrastructure tourne dans Docker (bases, Redis, RabbitMQ, gateway), les services tournent sur la machine via Turborepo en mode watch. En production, tout est conteneurisé à partir de Dockerfiles multi-stage, réparti sur trois réseaux Docker (gateway-net, services-net, db-net) pour qu’un service ne puisse pas atteindre une base qui n’est pas la sienne. Chaque conteneur a un healthcheck qui conditionne le démarrage des dépendants. La config Nginx est un template rendu avec envsubst.

Le workflow de pull request sur GitHub Actions lint, vérifie les types, lance les suites Jest de chaque service avec couverture envoyée sur Codecov, et régénère la spec OpenAPI agrégée depuis les annotations JSDoc de tous les services. Les tâches de test et de couverture dépendent du type-check dans le graphe Turborepo, donc une erreur de type échoue vite.

Pourquoi ces choix

Nginx comme unique point d’entrée. Authentification et rate limiting vivent à un seul endroit au lieu d’être réimplémentés dans six services. Ajouter un service revient à ajouter un bloc location.

gRPC et RabbitMQ, pas l’un ou l’autre. Quand l’appelant a besoin de la réponse pour construire la sienne (les auteurs d’un post), l’appel synchrone est le modèle honnête. Quand ce n’est pas le cas (envoyer une notification), une file découple la disponibilité : le notifications-service peut être arrêté, les événements attendent.

SSE pour les notifications, WebSocket pour le chat. SSE c’est du HTTP simple, ça traverse les proxies et se reconnecte gratuitement ; ça convient à un flux à sens unique. WebSocket seulement là où le client répond vraiment.

Persistance polyglotte. Une seule base pour tout aurait imposé soit des jointures dans MongoDB, soit un schéma rigide pour les posts. Chaque service prend ce qui correspond à ses données.

La suite

Le rapport liste les évolutions identifiées : remplacer les appels gRPC synchrones restants par du request/reply sur RabbitMQ avec identifiants de corrélation pour supprimer le couplage temporel ; ajouter retries, dead-letter queues et consommateurs idempotents sur le bus d’événements ; introduire des migrations versionnées pour PostgreSQL et MongoDB ; ajouter des tests de contrat entre services et des tests end-to-end Playwright sur le web ; et passer de Docker Compose à Kubernetes pour les rolling updates et l’auto-réparation.

Contact

Parlons-en.

Pas de formulaires, pas d’agenda, envoyez-moi un email avec ce que vous avez en tête. Je lis tout.

[email protected]