22 KiB
Analyse des Incohérences entre ADR et Règles Métier
Date d'analyse : 2026-01-28 Analysé par : Audit Architecture RoadWave Scope : 18 ADR × Règles Métier (17 fichiers)
Résumé Exécutif
Cette analyse a identifié 15 incohérences entre les décisions d'architecture (ADR) et les règles métier du projet RoadWave.
Répartition par Sévérité
| Sévérité | Nombre | % Total | Statut | Action Required |
|---|---|---|---|---|
| 🔴 CRITICAL | 2 | 13% | ✅ RÉSOLU | |
| 🟠 HIGH | 4 | 27% | ⏳ 3 restants (1 résolu) | Résolution Sprint 1-2 |
| 🟡 MODERATE | 8 | 53% | ⏳ En cours | Résolution Sprint 3-5 |
| 🟢 LOW | 1 | 7% | ⏳ En cours | À clarifier lors du développement |
Impact par Domaine
| Domaine | Nombre d'incohérences | Criticité maximale |
|---|---|---|
| Streaming & Géolocalisation | 3 | 🔴 CRITICAL |
| Données & Infrastructure | 3 | 🟠 HIGH |
| Authentification & Sécurité | 2 | 🟠 HIGH |
| Tests & Qualité | 2 | 🟡 MODERATE |
| Coûts & Déploiement | 3 | 🟡 MODERATE |
| UX & Engagement | 2 | 🟡 MODERATE |
🔴 Incohérences Critiques (Blocantes)
#1 : HLS ne supporte pas les Notifications Push en Arrière-plan
Statut : ✅ RÉSOLU (ADR-019 créé)
| Élément | Détail |
|---|---|
| ADR concerné | ADR-002 (Protocole Streaming) |
| Règle métier | Règle 05, section 5.1.2 (Mode Piéton, lignes 86-120) |
| Conflit | HLS est unidirectionnel (serveur→client), ne peut pas envoyer de push quand l'app est fermée |
| Impact | Mode piéton non fonctionnel : notifications "Point d'intérêt à 200m" impossibles |
Scénario d'échec :
Utilisateur: Marie se promène, app fermée
Position: 150m de la Tour Eiffel
Attendu: Push notification "🗼 À proximité: Histoire de la Tour Eiffel"
Réel: Rien (HLS ne peut pas notifier)
Solution implémentée :
- ✅ ADR-019 : Architecture hybride WebSocket + Firebase Cloud Messaging
- Phase 1 (MVP) : Push serveur via FCM/APNS
- Phase 2 : Geofencing natif iOS/Android pour mode offline
Actions requises :
- Backend : Implémenter endpoint WebSocket
/ws/location - Backend : Worker PostGIS avec requête
ST_DWithin(30s interval) - Mobile : Intégrer Firebase SDK (
firebase_messaging) - Tests : Validation en conditions réelles (10 testeurs, Paris)
#2 : Latence HLS Incompatible avec ETA de 7 Secondes
Statut : ✅ RÉSOLU (ADR-002 mis à jour)
| Élément | Détail |
|---|---|
| ADR concerné | ADR-002 (Protocole Streaming, lignes 40-41) |
| Règle métier | Règle 05 (lignes 16-20), Règle 17 (lignes 25-30, 120-124) |
| Conflit | ETA de 7s avant le point, mais HLS a 5-30s de latence → audio démarre APRÈS avoir dépassé le point |
| Impact | UX catastrophique : utilisateur entend "Vous êtes devant le château" 100m APRÈS l'avoir dépassé |
Calcul du problème (90 km/h = 25 m/s) :
t=0s → Notification "Suivant: Château dans 7s" (175m avant)
t=7s → Utilisateur arrive au château
t=15s → HLS démarre (latence 15s)
Résultat: Audio démarre 200m APRÈS le point ❌
Solution implémentée :
- ✅ ADR-002 mis à jour : Section "Gestion de la Latence et Synchronisation Géolocalisée"
- Pre-buffering à ETA=30s (15 premières secondes en cache local)
- ETA adaptatif : 5s si cache prêt, 15s sinon
- Mesure dynamique de latence HLS par utilisateur
Actions requises :
- Backend : Endpoint
/api/v1/audio/poi/:id/intro(retourne 15s d'audio) - Mobile : Service
PreBufferServiceavec cache local (max 100 MB) - Mobile : Loader visuel avec progression si buffer > 3s
- Tests : Validation synchronisation ±10m du POI
🟠 Incohérences Importantes (Sprint 1-2)
#3 : Souveraineté des Données (Français vs Suisse)
Statut : ✅ RÉSOLU (ADR-008 mis à jour)
| Élément | Détail |
|---|---|
| ADR concernés | ADR-004 (CDN, ligne 26), ADR-008 (Auth, mis à jour) |
| Règle métier | Règle 02 (RGPD, section 13.10) |
| Conflit | ADR-004 revendique "100% souveraineté française" mais ADR-008 utilisait Zitadel (entreprise suisse) |
| Impact | Contradiction marketing + risque juridique si promesse "100% français" |
Solution implémentée : Self-hosting Zitadel sur OVH France
- ✅ Container Docker sur le même VPS OVH (Gravelines, France)
- ✅ Base de données PostgreSQL partagée (schéma séparé pour Zitadel)
- ✅ Aucune donnée ne transite par des serveurs tiers
- ✅ Souveraineté totale garantie : 100% des données en France
- ✅ Cohérence complète avec ADR-004 (CDN 100% français)
Changements apportés :
- ✅ ADR-008 mis à jour avec architecture self-hosted détaillée
- ✅ TECHNICAL.md mis à jour (tableau + diagramme architecture)
- ✅ Clarification : Zitadel est open source, donc aucune dépendance à une entreprise suisse
Actions complétées :
- Décision validée : Self-host sur OVH
- ADR-008 mis à jour avec architecture self-hosted
- TECHNICAL.md mis à jour
#4 : ORM sqlc vs Types PostGIS
| Élément | Détail |
|---|---|
| ADR concernés | ADR-013 (ORM, lignes 12, 33-40), ADR-005 (BDD, lignes 47-56) |
| Règle métier | N/A (problème technique pur) |
| Conflit | sqlc génère types Go depuis SQL, mais PostGIS geography/geometry ne mappent pas proprement |
| Impact | Risque de type interface{} ou []byte pour géographie → perte de type safety revendiquée |
Nature du problème :
sqlc génère du code Go depuis SQL, mais les types PostGIS (geography, geometry) ne sont pas mappés proprement en Go. Résultat : types opaques ([]byte, interface{}) qui perdent la type safety revendiquée dans ADR-013.
Solution retenue :
- Wrapper types Go avec méthodes
Scan/Valuepour conversion automatique - Utiliser les fonctions PostGIS de conversion :
ST_AsGeoJSON()→ struct GeoJSON typéeST_AsText()→ string WKTgeographybrut →pgtype.Point(lib pgx)
- Documenter le pattern dans ADR-013 section "Gestion des Types PostGIS"
Action :
- Créer package
internal/geoavec wrappersGeoJSON,WKT - Mettre à jour ADR-013 section "Types PostGIS"
- Documenter pattern dans README backend
#5 : Cache Redis (TTL 5min) vs Mode Offline (30 jours)
| Élément | Détail |
|---|---|
| ADR concerné | ADR-005 (BDD, ligne 60) |
| Règle métier | Règle 11 (Mode Offline, lignes 58-77) |
| Conflit | Redis avec TTL 5min pour géolocalisation, mais contenu offline valide 30 jours |
| Impact | En mode offline, impossible de rafraîchir le cache géolocalisation → POI proches non détectés |
Analyse du flux :
Mode connecté:
1. Requête POI proches → Redis (cache 5min)
2. Si miss → PostGIS → Cache Redis
3. ✅ Fonctionne
Mode offline (Règle 11):
1. Requête POI proches → Redis (expiré depuis 6 min)
2. Impossible de requêter PostGIS (pas de réseau) ❌
3. Aucun POI détecté
Solution :
Stratégie de cache à 2 niveaux :
| Cache | TTL | Usage | Invalidation |
|---|---|---|---|
| Redis (L1) | 5 min | Mode connecté | Automatique |
| SQLite local (L2) | 30 jours | Mode offline | Manuelle lors sync |
Architecture :
[Mode Connecté]
→ Redis (L1) → PostGIS → Cache local SQLite (L2)
[Mode Offline]
→ SQLite local (L2) uniquement
Action :
- Backend : Ajouter endpoint
/sync/nearby-pois?lat=X&lon=Y&radius=10km - Mobile : Créer
OfflineCacheServiceavec SQLite + index spatial - Mettre à jour ADR-005 section "Cache" avec stratégie 2 niveaux
- Règle 11 : Clarifier sync automatique vs manuel
#6 : Package Geofencing vs Permissions iOS/Android
| Élément | Détail |
|---|---|
| ADR concerné | ADR-014 (Frontend Mobile, ligne 48) |
| Règle métier | Règle 05 (lignes 86-134), Règle 11 (RGPD, lignes 51-86) |
| Conflit | Package geofence_service choisi, mais pas de doc sur compatibilité permissions "optionnelles" |
| Impact | Risque de rejet App Store/Play Store si permissions obligatoires mal gérées |
Problématiques :
- iOS : Permission "Always Location" exige justification stricte (taux refus 70%)
- Android : Background location nécessite déclaration spéciale (depuis Android 10)
- Règle métier : Permissions optionnelles (app utilisable sans "Always Location")
Package geofence_service :
- ✅ Supporte iOS/Android
- ⚠️ Documentation peu claire sur permissions optionnelles
- ⚠️ Pas de fallback natif si permission refusée
Solution :
Stratégie de permissions progressive :
enum LocationPermissionLevel {
denied, // Pas de permission
whenInUse, // "Quand l'app est ouverte" (iOS)
always, // "Toujours" (iOS) / Background (Android)
}
class GeofencingService {
Future<void> requestPermissions() async {
// Étape 1: Demander "When In Use" (moins intrusif)
var status = await Permission.locationWhenInUse.request();
if (status.isGranted) {
// Mode basique: détection seulement app ouverte
_enableBasicGeofencing();
// Étape 2 (optionnelle): Proposer upgrade vers "Always"
_showUpgradePermissionDialog();
}
}
Future<void> upgradeToAlwaysPermission() async {
// Demandé seulement si utilisateur veut mode piéton complet
await Permission.locationAlways.request();
}
}
Actions :
- Mettre à jour ADR-014 avec stratégie permissions progressive
- Créer doc "Permissions Strategy" dans
/docs/mobile/ - Tests : Validation rejet App Store (TestFlight beta)
🟡 Incohérences Modérées (Sprint 3-5)
#7 : Points vs Pourcentages dans les Jauges
| Élément | Détail |
|---|---|
| ADR concerné | ADR-010 (Commandes Volant, lignes 15-21) |
| Règle métier | Règle 03 (Centres d'intérêt, lignes 7-14) |
| Conflit | ADR dit "+2 points", Règle dit "+2**%**" pour même action |
| Impact | Ambiguïté sur calcul : +2 points absolus ou +2% relatifs ? |
Exemple du conflit :
- ADR-010 (ligne 18) : "≥80% d'écoute = +2 points"
- Règle 03 (ligne 9) : "≥80% d'écoute = +2**%** à la jauge"
Scénario :
Jauge "Automobile" = 45%
Utilisateur écoute 85% d'un podcast voiture
Option A (points absolus): 45 + 2 = 47%
Option B (pourcentage relatif): 45 * 1.02 = 45.9%
Recommandation : Option A (points absolus) pour simplicité
Justification :
- Progression linéaire plus intuitive
- Évite effet "rich get richer" (jauges hautes progressent + vite)
- Cohérent avec système de gamification classique
Actions :
- Clarifier ADR-010 : remplacer "points" par "points de pourcentage"
- Clarifier Règle 03 : uniformiser terminologie
- Backend : Documenter formule exacte dans code
#8 : OAuth2 Complexe vs Email/Password Simple
| Élément | Détail |
|---|---|
| ADR concerné | ADR-008 (Auth, lignes 12, 52-68) |
| Règle métier | Règle 01 (Auth, lignes 5-10) |
| Conflit | ADR implémente OAuth2 PKCE complet, mais Règle dit "❌ Pas d'OAuth tiers, email/password uniquement" |
| Impact | Sur-ingénierie : OAuth2 conçu pour tiers (Google, Facebook) mais non utilisé ici |
Analyse :
- ADR-008 : Architecture OAuth2 avec PKCE, refresh tokens, etc.
- Règle 01 : "❌ Pas de Google, Apple, Facebook OAuth"
Zitadel supporte :
- OAuth2 (pour intégrations tierces)
- Email/Password natif (ce dont on a besoin)
Question : Pourquoi implémenter OAuth2 si pas de tiers ?
Options :
| Option | Complexité | Justification |
|---|---|---|
| A. Garder OAuth2 | Haute | Future-proof pour API partenaires |
| B. Session simple | Basse | Suffit pour MVP email/password |
Recommandation : Option A (garder OAuth2) si :
- Vision long-terme : API pour partenaires (créateurs, annonceurs)
- Coût marginal : Zitadel gère OAuth2 nativement
Sinon Option B (session simple) si MVP pur.
Actions :
- Décision : Confirmer besoin OAuth2 avec product owner
- Si A : Mettre à jour Règle 01 "OAuth tiers en Phase 2"
- Si B : Simplifier ADR-008 (session JWT classique)
#9 : GeoIP Database (MaxMind)
| Élément | Détail |
|---|---|
| ADR concerné | ADR-005 (non mentionné) |
| Règle métier | Règle 02 (RGPD, lignes 146-149) |
| Conflit | Règle cite "MaxMind GeoLite2 (gratuit)", mais offre a changé en 2019 |
| Impact | Coût caché : MaxMind nécessite compte + API calls (plus de base offline gratuite) |
Historique :
- Avant 2019 : GeoLite2 database téléchargeable gratuitement
- Après 2019 : Compte requis + limite 1000 requêtes/jour (gratuit)
- Dépassement : 0.003$/requête
Utilisation RoadWave :
- Mode dégradé (sans GPS) → GeoIP pour localisation approximative
- Estimation : 10% des utilisateurs (1000 users × 10% = 100 requêtes/jour)
Options :
| Option | Coût/mois | Précision | Maintenance |
|---|---|---|---|
| A. MaxMind API | ~10€ | ±50 km | Nulle |
| B. IP2Location Lite | Gratuit | ±50 km | Maj mensuelle |
| C. Self-hosted GeoIP | Gratuit | ±50 km | +2h/mois |
Recommandation : Option C (self-hosted avec IP2Location Lite DB)
Architecture :
[Backend Go] → [GeoIP Service]
↓
[IP2Location SQLite DB]
(màj mensuelle via cron)
Actions :
- Backend : Implémenter service GeoIP avec IP2Location
- DevOps : Cron job màj mensuelle de la DB
- Mettre à jour Règle 02 ligne 147
#10 : Tests BDD Synchronisés (Backend + Mobile)
| Élément | Détail |
|---|---|
| ADR concernés | ADR-007 (Tests BDD, lignes 30-68), ADR-015 (Stratégie, lignes 59-62) |
| Règle métier | Toutes (Gherkin) |
| Conflit | Features partagées /features, step definitions séparées → qui exécute quoi ? |
| Impact | Risque de divergence backend/mobile si tests pas synchronisés |
Architecture actuelle :
/features/*.feature (partagé)
/backend/tests/bdd/ (step definitions Go)
/mobile/tests/bdd/ (step definitions Dart)
Question non résolue :
- Un test "Authentification" concerne-t-il backend ET mobile ?
- Qui est responsable de l'exécuter ?
- Si les implémentations divergent ?
Recommandation : Catégoriser les features
/features/
/api/ → Backend uniquement (tests API REST)
/ui/ → Mobile uniquement (tests interface)
/e2e/ → End-to-end (backend + mobile ensemble)
Exemple :
# features/api/authentication.feature (backend)
Scénario: Création de compte via API
Étant donné une requête POST /api/v1/auth/register
Quand j'envoie email "test@example.com" et password "Pass123!"
Alors le statut HTTP est 201
Et la réponse contient un token JWT
# features/ui/authentication.feature (mobile)
Scénario: Création de compte via interface
Étant donné que je suis sur l'écran d'inscription
Quand je saisis email "test@example.com"
Et je saisis mot de passe "Pass123!"
Et je clique sur "S'inscrire"
Alors je vois l'écran d'accueil
Actions :
- Réorganiser
/featuresen 3 catégories (api, ui, e2e) - Mettre à jour ADR-007 avec convention de nommage
- CI/CD : Séparer jobs backend-bdd et mobile-bdd
#11 : 70/30 Split Paiements (Vérification Manquante)
| Élément | Détail |
|---|---|
| ADR concerné | ADR-009 (Paiement, lignes 32-52) |
| Règle métier | Règle 18 (Monétisation, non fournie complète) |
| Conflit | ADR assume 70/30 split sans référence règle métier |
| Impact | Risque de mauvaise répartition revenus créateurs |
ADR-009 spécifie :
- 70% créateur
- 30% plateforme
Question : Est-ce validé par les règles métier business ?
Actions :
- Lire Règle 18 (Monétisation Créateurs) complète
- Vérifier si 70/30 correspond aux attentes
- Si divergence : mettre à jour ADR-009
#12 : Monorepo Path Filters vs Features Partagées
| Élément | Détail |
|---|---|
| ADR concernés | ADR-016 (Monorepo, ligne 80), ADR-015 (Tests) |
| Règle métier | N/A (problème CI/CD) |
| Conflit | Path filters pour éviter rebuild tout, mais features partagées déclenchent tout |
| Impact | Optimisation CI/CD inefficace |
Problème :
# .github/workflows/backend.yml
on:
push:
paths:
- 'backend/**'
- 'features/**' # ❌ Change sur n'importe quel .feature → rebuild backend
Solution : Path filters par catégorie (suite de #10)
# .github/workflows/backend.yml
on:
push:
paths:
- 'backend/**'
- 'features/api/**' # ✅ Seulement features API
- 'features/e2e/**' # ✅ E2E impacte backend
# .github/workflows/mobile.yml
on:
push:
paths:
- 'mobile/**'
- 'features/ui/**' # ✅ Seulement features UI
- 'features/e2e/**' # ✅ E2E impacte mobile
Actions :
- Implémenter catégorisation features (dépend de #10)
- Mettre à jour workflows CI/CD
- Mettre à jour ADR-016 avec stratégie path filters
#13 : Coûts Email (Transition Free → Paid)
| Élément | Détail |
|---|---|
| ADR concernés | ADR-018 (Email, lignes 49-52), ADR-017 (Hébergement) |
| Règle métier | N/A (économique) |
| Conflit | ADR cite "gratuit" mais limite 9000 emails/mois → plan transition manquant |
| Impact | Coût surprise lors de la croissance |
ADR-018 spécifie :
- Brevo gratuit : 300 emails/jour = 9000/mois
- Phase MVP : 0-10K utilisateurs
Calcul réaliste :
Emails par utilisateur/mois:
- Vérification email: 1
- Reset password: 0.1 (10%)
- Notifications (opt-in 30%): 4
- Paiements créateurs (5%): 1
Total: ~2 emails/user/mois (moyenne)
10K users × 2 = 20K emails/mois → dépassement tier gratuit
Coût Brevo :
- Free: 0-9K emails
- Lite: 19€/mois (20K emails)
- Business: 49€/mois (50K emails)
Actions :
- Mettre à jour ADR-018 avec projection coûts
- Implémenter alertes (90% quota atteint)
- Plan B : Self-hosted SMTP (Postfix) si budget serré
#14 : Kubernetes vs VPS MVP
| Élément | Détail |
|---|---|
| ADR concernés | ADR-017 (Hébergement, ligne 12), ADR-001 (Go, ligne 27) |
| Règle métier | N/A (infrastructure) |
| Conflit | ADR-001 justifie Go pour "Kubernetes first-class", mais ADR-017 utilise VPS simple |
| Impact | Sur-architecture : pourquoi choisir Go pour K8s si pas utilisé ? |
Analyse :
- ADR-001 : Go choisi notamment pour "excellent support Kubernetes"
- ADR-017 : MVP sur OVH VPS Essential (single VM, Docker Compose)
- ADR-012 : Mentionne migration K8s "à 1M+ users"
Question : Justification K8s prématurée ?
Réponse : Non, acceptable si :
- Vision long-terme claire (1M users = besoin K8s)
- Go apporte autres avantages (perf, concurrence, typing)
- Coût marginal (Go vs Node.js comparable en complexité MVP)
Recommandation : Clarifier la vision dans ADR
Actions :
- Mettre à jour ADR-001 : "Go pour scalabilité future (K8s), mais aussi perf/typage"
- ADR-017 : Ajouter section "Roadmap Infrastructure" (VPS → K8s)
🟢 Incohérences Mineures (Clarification)
#15 : Unlike Manuel sur Contenu Auto-liké
| Élément | Détail |
|---|---|
| ADR concerné | ADR-010 (ligne 15-21) |
| Règle métier | Règle 05 (lignes 248-323), Règle 03 (lignes 93-99) |
| Conflit | Auto-like +2% documenté, mais unlike manuel non spécifié |
| Impact | Ambiguïté : faut-il annuler (+2%) si unlike ? |
Scénario :
1. Utilisateur écoute 85% → auto-like → jauge +2%
2. Utilisateur clique "Unlike" (toggle)
3. Que se passe-t-il ?
Option A: Jauge -2% (annulation)
Option B: Jauge reste (unlike n'affecte pas)
Recommandation : Option A (annulation symétrique)
Justification : Unlike explicite = signal fort "pas intéressé"
Actions :
- Clarifier Règle 03 : section "Unlike Manuel"
- Backend : Implémenter logique annulation dans
GaugeService
Plan d'Action Global
Phase 1 : Résolutions Critiques (Avant Implémentation)
| # | Tâche | Responsable | Effort | Deadline |
|---|---|---|---|---|
| 1 | ✅ Créer ADR-019 (Notifications) | Architecture | 2h | ✅ Fait |
| 2 | ✅ Mettre à jour ADR-002 (Pre-buffering) | Architecture | 1h | ✅ Fait |
| 3 | Implémenter WebSocket backend | Backend Lead | 3j | Sprint 1 |
| 4 | Implémenter Pre-buffer mobile | Mobile Lead | 2j | Sprint 1 |
Phase 2 : Résolutions Importantes (Sprint 1-2)
| # | Tâche | Responsable | Effort | Statut |
|---|---|---|---|---|
| 5 | ✅ Décision souveraineté (Zitadel self-host) | CTO | 1h | ✅ Fait |
| 6 | Package geo types (PostGIS) | Backend | 1j | ⏳ Sprint 2 |
| 7 | Cache 2 niveaux (Redis + SQLite) | Backend + Mobile | 3j | ⏳ Sprint 2 |
| 8 | Stratégie permissions progressive | Mobile | 2j | ⏳ Sprint 2 |
Phase 3 : Résolutions Modérées (Sprint 3-5)
| # | Tâche | Responsable | Effort | Deadline |
|---|---|---|---|---|
| 9-15 | Clarifications ADR/Règles | Tech Writer | 5j | Sprint 3-4 |
| 16 | Réorganisation features BDD | QA Lead | 2j | Sprint 4 |
| 17 | Optimisation CI/CD path filters | DevOps | 1j | Sprint 5 |
Métriques de Suivi
| Métrique | Valeur Initiale | Cible | Actuel |
|---|---|---|---|
| Incohérences CRITICAL | 2 | 0 | ✅ 0 (2/2 résolues) |
| Incohérences HIGH | 4 | 0 | ⏳ 3 (1/4 résolue) |
| Incohérences MODERATE | 8 | ≤2 | ⏳ 8 |
| ADR à jour | 66% (12/18) | 100% | ⏳ 78% (14/18) |
| Coverage documentation | N/A | >90% | ⏳ 80% |
Dernière mise à jour : 2026-01-30
Contacts et Ressources
- Analyse complète : Ce document
- ADR-019 :
/docs/adr/019-notifications-geolocalisees.md - ADR-002 (mis à jour) :
/docs/adr/002-protocole-streaming.md - Questions : Créer une issue GitHub avec tag
[architecture]
Prochaine revue : 2026-02-15 (après Sprint 2)