DEV Community

Schat Carino
Schat Carino

Posted on

De la défense d'architecture aux scénarios de tests

Architecture hexagonale

l'approche hexagonale est confirmé comme le bon choix pour ce projet. L'argument qui a fait mouche : l'intégration IoT devient triviale. Chaque technologie (broker MQTT, PostgreSQL, Redis, bases de données et iot existantent) n'est qu'un adaptateur branché sur un port. Besoin d'intégrer la base de données parking existante d'une autre ville ? On ajoute un adaptateur. Besoin de remplacer EMQX par un autre broker MQTT ? On change l'adaptateur, la logique métier reste intacte.

Gestion de la charge serveur : La discussion qui m'a fait creuser plus loin

Le jury m'a poussé sur comment le système gère les pics de charge. Mon rapport mentionnait le problème (500+ trames capteurs/seconde, requêtes citoyens aux heures de pointe), mais le jury voulait voir la chaîne complète de défense :

Couche 1 - Message Broker (EMQX) : Les capteurs ne tapent pas le backend directement. Ils publient sur des topics MQTT. EMQX met les messages en file d'attente. Si le backend est lent ou redémarre, les messages attendent - rien n'est perdu. Le broker est l'amortisseur entre le trafic IoT imprévisible et la capacité de traitement finie.

Couche 2 - Cache (Redis) : Le statut d'une place de parking est lu des milliers de fois par seconde par les citoyens qui vérifient la disponibilité. Mais il ne change que lorsqu'un capteur envoie un signal. Redis sert les lectures en <1ms. La base de données n'est sollicitée que pour les écritures. À lui seul, ce mécanisme élimine plus de 90% de la charge sur la BDD.

Couche 3 - Auto-scaling (Vertical + Horizontal) : Quand Redis et le broker ne suffisent plus (rush du vendredi soir, événement à proximité), l'auto-scaling entre en jeu. D'abord vertical (conteneurs plus puissants), puis horizontal (plus d'instances NestJS derrière un load balancer). Docker Compose pour le dev, avec un chemin clair vers l'orchestration si nécessaire.

Capteurs -> [Broker EMQX] -> Backend (NestJS) -> [Cache Redis] -> Clients
                |                    |                |
          Absorbe les pics     Scale out         Sert les lectures
Enter fullscreen mode Exit fullscreen mode

Le message du jury était clair : chaque couche résout un problème différent. Le broker gère les pics d'ingestion, le cache gère l'amplification des lectures, et l'auto-scaling gère la croissance soutenue. On a besoin des trois.


Des décisions aux tests : le tableau de scénarios TDD

Domaine 1 - Gestion du stationnement

ID Exigence source Type Catégorie Scénario Données d'entrée Résultat attendu Critère d'échec Priorité
TST-PARK-001 Occupation temps réel Unitaire Nominal Étant donné un capteur signalant une place libre, quand l'événement est reçu, alors le statut est mis à jour en BDD + Redis en <100ms { placeId: "P-042", status: "FREE" } Statut mis à jour PostgreSQL + Redis <100ms Incohérence entre Redis et BDD, ou latence >100ms Critique
TST-PARK-002 Cartographie par zone Intégration Nominal Étant donné 3 zones à 80%, 20%, 100% d'occupation, quand le superviseur charge la carte, alors les taux correspondent aux données capteurs agrégées Zone A: 40/50, B: 10/50, C: 50/50 Taux affichés = 80%, 20%, 100% Écart >1% avec les données réelles Haute
TST-PARK-003 Réservation à l'avance Unitaire Nominal Étant donné une place libre P-042, quand un usager réserve 14h-16h, alors le statut passe à RESERVED et la place est exclue du guidage { placeId: "P-042", from: "14:00", to: "16:00" } Statut = RESERVED, exclue du guidage Place encore proposée au guidage après réservation Haute
TST-PARK-004 Double réservation Unitaire Erreur Étant donné P-042 déjà réservée 14h-16h, quand un 2e usager tente le même créneau, alors 409 Conflict retourné { placeId: "P-042", from: "14:30", to: "15:30" } HTTP 409, aucune modification en BDD Réservation acceptée (double booking) Critique
TST-PARK-005 Guidage dynamique Intégration Nominal Étant donné un usager géolocalisé à l'entrée, quand il demande un guidage, alors la place libre la plus proche est retournée via PostGIS GPS usager + état temps réel des places Place la plus proche en distance géospatiale Place retournée occupée ou pas la plus proche Haute
TST-PARK-006 Filtrage places PMR Unitaire Limite Étant donné que seules des places PMR sont libres, quand un usager standard demande un guidage, alors les places PMR ne sont pas proposées 50/50 standard occupées, 3/5 PMR libres Réponse : "aucune place disponible" Places PMR proposées à un usager non-PMR Haute
TST-PARK-007 Historisation Unitaire Nominal Étant donné un changement de statut sur P-042, quand l'événement est traité, alors un enregistrement horodaté est inséré dans TimescaleDB Transition FREE→OCCUPIED Ligne insérée dans hypertable avec timestamp Pas d'insertion ou timestamp manquant Moyenne

Domaine 2 - Gestion des capteurs IoT

ID Exigence source Type Catégorie Scénario Données d'entrée Résultat attendu Critère d'échec Priorité
TST-IOT-001 Réception trames MQTT Intégration Nominal Étant donné un capteur magnétique actif, quand il publie sur parking/sensors/{id}, alors le backend consomme et persiste en <200ms { sensorId: "S-101", type: "magnetic", value: 1, battery: 85 } Donnée persistée <200ms, statut place mis à jour Message perdu ou non consommé après 5s Critique
TST-IOT-002 Détection signal perdu Unitaire Erreur Étant donné un capteur silencieux depuis >15min (seuil configuré), quand le cron de supervision s'exécute, alors alerte SIGNAL_LOST créée lastSeen: now() - 20min, seuil: 15min Alerte créée : type=SIGNAL_LOST Aucune alerte malgré dépassement du seuil Critique
TST-IOT-003 Détection batterie faible Unitaire Limite Étant donné un capteur à 10% de batterie (seuil critique = 15%), quand la trame est reçue, alors alerte BATTERY_LOW générée { sensorId: "S-101", battery: 10 } Alerte BATTERY_LOW créée Pas d'alerte, capteur reste en état normal Haute
TST-IOT-004 Valeurs aberrantes Unitaire Erreur Étant donné un capteur envoyant des valeurs hors range (battery: -5), quand la trame est parsée, alors rejetée et loguée comme anomalie { sensorId: "S-101", battery: -5, value: 999 } Trame rejetée, log d'anomalie, aucune mise à jour Valeur aberrante persistée Haute
TST-IOT-005 Dashboard santé capteurs E2E Nominal Étant donné 100 capteurs actifs, quand le superviseur consulte le dashboard santé, alors uptime, dernière communication, batterie affichés 100 capteurs avec états variés Tableau complet, filtrable par état, trié par criticité Capteurs manquants ou données périmées Haute
TST-IOT-006 Débit broker MQTT Intégration Performance Étant donné 500 capteurs envoyant 1 trame/s simultanément, quand EMQX reçoit le flux, alors zéro perte, latence <500ms 500 trames/s pendant 60s 0 message perdu, P99 <500ms Perte >0.1% ou P99 >500ms Critique

Domaine 3 - Alertes & interventions

ID Exigence source Type Catégorie Scénario Données d'entrée Résultat attendu Critère d'échec Priorité
TST-ALR-001 Alertes automatiques Unitaire Nominal Étant donné la règle "zone A >90% pendant >10min", quand le seuil est dépassé, alors alerte priorité HAUTE créée Zone A à 92% depuis 12min Alerte : type=OCCUPATION_HIGH, priority=HIGH Aucune alerte ou mauvaise priorité Critique
TST-ALR-002 Dispatch GPS-optimisé Intégration Nominal Étant donné 3 agents à 200m, 1.2km, 800m, quand une intervention est créée, alors assignée à l'agent le plus proche disponible GPS agents + localisation intervention Agent à 200m reçoit l'assignation <30s Assignation à un agent plus éloigné Haute
TST-ALR-003 Escalade automatique Unitaire Limite Étant donné une intervention assignée depuis 30min (SLA=25min) non traitée, quand le cron d'escalade s'exécute, alors escaladée au superviseur Status=ASSIGNED, age=30min, SLA=25min Statut→ESCALATED, superviseur notifié Reste en ASSIGNED sans escalade Haute
TST-ALR-004 Cycle de vie complet E2E Nominal Étant donné une alerte capteur HS, quand l'agent la traite (accepte→en cours→photo→clôture), alors chaque transition est horodatée Intervention parcourant les 4 états Timestamps pour chaque transition, final=CLOSED Transition manquante ou état final incorrect Haute
TST-ALR-005 Gestion urgence Unitaire Erreur Étant donné une alerte VANDALISME, quand elle est créée, alors bypass la file normale et notifie immédiatement tous les superviseurs { type: "VANDALISM", priority: "CRITICAL" } Notification <5s, pas de mise en file Délai >5s ou notification manquante Critique
TST-ALR-006 Agent hors ligne Unitaire Erreur Étant donné que l'agent le plus proche est hors ligne (4G coupée), quand le dispatch tente l'assignation, alors bascule sur le 2e agent Agent 1: offline, Agent 2: online à 800m Assignation à l'agent 2, log échec agent 1 Intervention reste non assignée Haute

Ce que je ferais différemment : réflexions post-soutenance

Après l'échange avec le jury, plusieurs choses sont devenues plus claires et méritent d'être intégrées :

1. La gestion de charge doit apparaître dans le diagramme d'architecture, pas seulement dans le texte

J'ai discuté de l'auto-scaling, du cache et du message broker dans la section "problématiques" de mon rapport, mais le diagramme d'architecture lui-même ne montrait pas ces couches. Le jury avait raison d'insister. Si le broker est votre première ligne de défense contre les flux IoT, il doit être un composant visible dans l'architecture, pas une note de bas de page.

2. Les compromis de monitoring méritent un tableau comparatif

Mentionner deux solutions de monitoring sans explication structuré, c'est une occasion manquée. Leçon retenue : si on mentionne une alternative, on doit au lecteur une comparaison structurée.

Top comments (0)