/ / conception système

COMMENT JE CONÇOIS

Des architectures que j'ai conçues, les décisions qui les sous-tendent, et ce que coûte chacune.

Conception d'architecture

SaaS multi-tenant de gestion de salles de sport

Une seule plateforme au service de nombreuses salles indépendantes, chacune avec ses adhérents, ses coachs et son planning de cours. Le planning est la fonctionnalité centrale ; la facturation et les rappels s'appuient dessus.

  • Node.js
  • Express
  • MongoDB
  • Redis
  • React
ClientsAPIModulesDonnéesExterneApp adhérentPWATableau de bordpar salleAPI Node.jsmonolithe modulaireAuth & tenantsPlanningcours · coachsAbonnementsforfaits · paiementsNotificationsMongoDBtenantId sur chaque documentFile Redisrappels · renouvellementsSMS / WhatsAppPasserellede paiement
  • App adhérent → API Node.js
  • Tableau de bord → API Node.js
  • API Node.js → Auth & tenants
  • API Node.js → Planning
  • API Node.js → Abonnements
  • API Node.js → Notifications
  • Auth & tenants → MongoDB
  • Planning → MongoDB
  • Abonnements → MongoDB
  • Notifications → File Redis
  • File Redis → SMS / WhatsApp
  • Abonnements → Passerelle de paiement

Faites défiler horizontalement pour voir tout le schéma.

Décisions clés

  1. 01Une base partagée, tenantId sur chaque document

    Pourquoi
    Des centaines de petites salles imposent un onboarding peu coûteux, un seul schéma à migrer et une seule sauvegarde.
    Compromis
    L'isolation dépend du code. Toutes les requêtes passent par un repository limité au tenant et sont couvertes par des tests ; une grande chaîne pourra plus tard avoir sa propre base.
  2. 02Monolithe modulaire, pas de microservices

    Pourquoi
    Une petite équipe livre plus vite avec un seul déploiement. Les modules (planning, facturation, notifications) ont des frontières claires pour être extraits plus tard.
    Compromis
    Tout évolue ensemble. C'est acceptable pour cette charge, et moins cher à exploiter jusqu'à ce que ça ne le soit plus.
  3. 03Planning d'abord, avec des réservations atomiques

    Pourquoi
    Le planning des cours et des coachs est la fonctionnalité d'entrée. Une réservation décrémente la capacité en une seule mise à jour conditionnelle : deux adhérents ne peuvent jamais prendre la dernière place.
    Compromis
    La promotion de la liste d'attente ajoute de la logique (qui obtient une place libérée, et quand). Ça en vaut la peine : les cours complets sont là où les salles perdent de l'argent.
  4. 04File d'attente pour les rappels et renouvellements

    Pourquoi
    Les rappels et les renouvellements ne doivent ni ralentir les requêtes ni se perdre lors d'un redémarrage.
    Compromis
    Une brique d'infrastructure de plus à exploiter et à surveiller.
  5. 05Paiements derrière une interface

    Pourquoi
    Les salles en Tunisie encaissent souvent en espèces ou via des moyens locaux. Le module d'abonnements enregistre d'abord les paiements et parle à une passerelle via une interface : on peut ajouter un prestataire local sans toucher au reste.
    Compromis
    Rapprocher espèces et paiements en ligne demande des états clairs par facture.

Conception d'architecture

Plateforme de paris : sportsbook et casino

Un exercice de conception pour une plateforme avec cotes sportives en direct et jeux de casino. Les difficultés sont la justesse de l'argent et les données en direct, pas les écrans. Pas un produit en ligne.

  • Node.js
  • PostgreSQL
  • Redis
  • WebSocket
  • React
ClientAccèsServicesCœur & intégrationsStockageApp webticket · cotes livePasserelle APIREST + WebSocketSportsbookmarchés · règlementCasinosessions de jeuFlux cotes& résultatsRediscache cotes · pub/subFile de jobsrèglementPortefeuille& registrechaque mouvementFournisseurde jeuxPostgreSQLregistre en partie double
  • App web → Passerelle API
  • Passerelle API → Sportsbook
  • Passerelle API → Casino
  • Sportsbook → Flux cotes & résultats
  • Sportsbook → Redis
  • Sportsbook → File de jobs
  • Sportsbook → Portefeuille & registre
  • Casino → Portefeuille & registre
  • Casino → Fournisseur de jeux
  • Portefeuille & registre → PostgreSQL

Faites défiler horizontalement pour voir tout le schéma.

Décisions clés

  1. 01Un registre en partie double, pas une colonne de solde

    Pourquoi
    Chaque pari, gain et remboursement est une paire d'écritures dont la somme est toujours nulle. Tout solde peut être reconstruit et audité, et l'argent ne peut ni apparaître ni disparaître en silence.
    Compromis
    Plus de tables et des requêtes plus lourdes qu'un simple solde. Les transactions PostgreSQL le rendent sûr, et un solde en cache garde les lectures rapides.
  2. 02Clés d'idempotence sur chaque mouvement d'argent

    Pourquoi
    Les réseaux réessaient. Une requête répétée avec la même clé renvoie le premier résultat au lieu de débiter deux fois.
    Compromis
    Les clés doivent être stockées et expirées, et chaque client doit les envoyer.
  3. 03Cotes issues d'un flux, en cache Redis, poussées par WebSocket

    Pourquoi
    Les cotes en direct changent toutes les quelques secondes. Pousser vaut mieux que sonder, et le cache protège le flux et la base.
    Compromis
    Un ticket peut contenir des cotes périmées. Chaque cote porte une version : si elle a changé, le pari est refusé avec le nouveau prix au lieu d'être accepté à l'ancien.
  4. 04Jeux de casino via un fournisseur, par webhooks signés

    Pourquoi
    Un moteur aléatoire équitable nécessite une certification. Intégrer un fournisseur signifie des callbacks de mise, gain et annulation que le portefeuille traite de façon idempotente.
    Compromis
    Dépendance au fournisseur et à ses frais. Les signatures des webhooks sont vérifiées et les rejeux ignorés.
  5. 05Règlement par jobs asynchrones

    Pourquoi
    Quand un match se termine, des milliers de paris se règlent en même temps. Une file répartit la charge et permet à un job en échec de réessayer sans payer deux fois.
    Compromis
    Les gains apparaissent quelques secondes après le résultat, pas instantanément.

Projet client, anonymisé

Pipeline d'extraction de documents

Transforme des contrats et factures téléversés en données structurées fiables pour d'autres contrôles, quelle que soit la langue ou le moteur d'extraction.

  • Node.js
  • Azure Document Intelligence
  • IPFS
  • LLM
ClientRéceptionPréparationExtracteursNormalisationRésultatApp clienteAPI de dépôtvalide · crée un jobStockage+ pin IPFSDétectionde langueAzure DocumentIntelligenceExtracteur LLMcontratsNormaliseurvers le schémaStockage résultatstatut · données
  • App cliente → API de dépôt
  • API de dépôt → Stockage
  • API de dépôt → Détection de langue
  • Stockage → Azure Document Intelligence
  • Détection de langue → Azure Document Intelligence
  • Détection de langue → Extracteur LLM
  • Azure Document Intelligence → Normaliseur
  • Extracteur LLM → Normaliseur
  • Normaliseur → Stockage résultat

Faites défiler horizontalement pour voir tout le schéma.

Décisions clés

  1. 01Extracteurs derrière une seule interface

    Pourquoi
    Azure gère bien les factures ; les contrats profitent d'un LLM. Une interface commune permet à chaque type de document d'utiliser le meilleur moteur, et au client de les comparer ou de les remplacer.
    Compromis
    L'interface ne peut exposer que ce que tous les moteurs fournissent : les options propres à un moteur demandent une porte de sortie explicite.
  2. 02Normaliser juste après l'extraction

    Pourquoi
    Chaque moteur renvoie une forme différente. Les convertir tout de suite vers le schéma de document convenu évite que le reste du système voie un format propre à un fournisseur.
    Compromis
    Un mapping à écrire et à maintenir par moteur et par type de document.
  3. 03Conserver l'original, référencé par son hash

    Pourquoi
    Épingler le fichier sur IPFS lui donne une adresse de contenu : on peut prouver que le fichier derrière un résultat n'a pas été modifié ensuite.
    Compromis
    Le contenu IPFS est public par défaut : les fichiers sensibles doivent être chiffrés avant d'être épinglés. L'épinglage a aussi un coût.
  4. 04Détecter la langue avant d'extraire

    Pourquoi
    Les documents arrivent en plusieurs langues. Connaître la langue d'abord permet de choisir les bons réglages de modèle.
    Compromis
    Une erreur de détection se propage à l'extraction : les résultats peu fiables basculent vers un réglage multilingue.
  5. 05Jobs asynchrones avec un statut

    Pourquoi
    L'OCR prend plusieurs secondes. Le dépôt renvoie tout de suite un identifiant de job et le client interroge ou est notifié : les gros fichiers ne bloquent jamais une requête.
    Compromis
    Plus d'états à modéliser : en attente, en cours, terminé, échoué.