Initial commit
This commit is contained in:
65
docs/adr/001-langage-backend.md
Normal file
65
docs/adr/001-langage-backend.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# ADR-001 : Langage Backend
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2025-01-17
|
||||
|
||||
## Contexte
|
||||
|
||||
RoadWave doit gérer 10M d'utilisateurs avec des connexions concurrentes massives pour le streaming audio géolocalisé.
|
||||
|
||||
## Décision
|
||||
|
||||
**Go** avec le framework **Fiber**.
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
### Comparatif synthétique
|
||||
|
||||
| Option | Conn/serveur | P99 latency | Simplicité | Écosystème RoadWave |
|
||||
|--------|-------------|------------|------------|------------|
|
||||
| **Go + Fiber** | 1M+ | 5-50ms | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
|
||||
| Rust + Tokio | 2M+ | 2-20ms | ⭐⭐ | ⭐⭐⭐ |
|
||||
| Node.js | 100-500K | 10-100ms | ⭐⭐⭐⭐ | ⭐⭐⭐ |
|
||||
| Elixir/Phoenix | 2M+ | 5-50ms | ⭐⭐⭐ | ⭐⭐ |
|
||||
|
||||
## Justification
|
||||
|
||||
### Pourquoi Go plutôt que Rust ?
|
||||
|
||||
Rust offre meilleures performances absolues (2M conn/serveur vs 1M, 0 GC pauses) mais **Go gagne sur le plan startup** :
|
||||
|
||||
1. **Time-to-market critique** : MVP en 8 semaines vs 12+ pour Rust
|
||||
- Courbe d'apprentissage borrow checker = grosse friction pour juniors
|
||||
- Temps compilation + refactoring : 30-60s vs 1-2s Go
|
||||
- Recrutement moins onéreux (€35-50K junior Go vs €50-70K Rust)
|
||||
|
||||
2. **Écosystème production-ready pour RoadWave** :
|
||||
- **WebRTC** : pion/webrtc (mature) vs webrtc.rs (naissant)
|
||||
- **Tests BDD** : Godog/Gherkin natif en Go, pas d'équivalent Rust
|
||||
- **PostgreSQL + PostGIS** : pgx (excellent) vs sqlx (bon mais moins mature)
|
||||
- **Zitadel/OAuth2** : clients Go stables vs Rust émergents
|
||||
|
||||
3. **Performance suffisante pour 10M users distribués** :
|
||||
- 1M conn/serveur = 10 serveurs max pour pics
|
||||
- GC pauses (10-100ms) acceptable avec stratégie multi-région
|
||||
- Scaling horizontal plus simple que vertical
|
||||
|
||||
4. **Tooling natif** :
|
||||
- pprof intégré (CPU, mémoire)
|
||||
- race detector systématique
|
||||
- Kubernetes first-class
|
||||
- Cold start ~10ms (vs ~50ms Rust)
|
||||
|
||||
### Quand Rust aurait du sens
|
||||
|
||||
- Si concentrations d'1M+ connexions sur serveur unique (cas rare RoadWave)
|
||||
- Si p99 latencies en prod > 100ms deviennent bottleneck (après Growth phase)
|
||||
- Si refonte majeure planifiée anyway
|
||||
- Stratégie possible : réécrire services hot (WebRTC, HLS streaming) en Rust à phase Scale
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Formation équipe sur Go si nécessaire
|
||||
- Utilisation des bibliothèques : Fiber (HTTP), pgx (PostgreSQL), go-redis
|
||||
- Monitoring GC pauses en production (cibler < 20ms p95)
|
||||
- Potential migration partielle à Rust pour services critiques post-Series A
|
||||
182
docs/adr/002-protocole-streaming.md
Normal file
182
docs/adr/002-protocole-streaming.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# ADR-002 : Protocole de Streaming
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2025-01-17
|
||||
|
||||
## Contexte
|
||||
|
||||
Streaming audio vers des utilisateurs mobiles en voiture, avec réseaux instables (tunnels, zones rurales, handoff cellulaire).
|
||||
|
||||
## Décision
|
||||
|
||||
**HLS** (HTTP Live Streaming) pour le contenu à la demande.
|
||||
**WebRTC** réservé à la radio live.
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Option | Latence | Fiabilité mobile | Cache CDN | Complexité |
|
||||
|--------|---------|------------------|-----------|------------|
|
||||
| **HLS** | 5-30s | Excellente | Oui | Faible |
|
||||
| DASH | 5-30s | Bonne | Oui | Moyenne |
|
||||
| WebRTC | <500ms | Moyenne | Non | Élevée |
|
||||
| UDP brut | Minimale | Faible | Non | Très élevée |
|
||||
|
||||
## Justification
|
||||
|
||||
- **Réseaux mobiles** : HLS gère les coupures et changements de cellule nativement
|
||||
- **Cache CDN** : Segments .ts cachables = réduction des coûts
|
||||
- **Compatibilité** : Support natif iOS/Android
|
||||
- **Bitrate adaptatif** : Ajustement automatique selon la qualité réseau
|
||||
|
||||
## Pourquoi pas UDP ?
|
||||
|
||||
- Problèmes NAT/firewall sur réseaux mobiles
|
||||
- Perte de paquets = artefacts audio
|
||||
- Impossible à cacher sur CDN
|
||||
- Complexité sans bénéfice pour du contenu non-interactif
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Latence de 5-30s acceptable pour podcasts/audio-guides (avec pré-buffering, voir section 4.3)
|
||||
- WebRTC à implémenter séparément pour la radio live
|
||||
|
||||
## Gestion de la Latence et Synchronisation Géolocalisée
|
||||
|
||||
### Problème Identifié
|
||||
|
||||
La latence HLS (5-30s) entre en conflit avec les notifications géolocalisées qui doivent déclencher l'audio **au moment précis** où l'utilisateur atteint un point d'intérêt.
|
||||
|
||||
**Exemple critique** :
|
||||
- Utilisateur en voiture à 90 km/h (25 m/s)
|
||||
- ETA de 7 secondes avant le point → notification affichée
|
||||
- Latence HLS de 15 secondes
|
||||
- Résultat : audio démarre **200 mètres après** le point d'intérêt ❌
|
||||
|
||||
### Solution : Pre-buffering Anticipé + ETA Adaptatif
|
||||
|
||||
#### 4.3.1 Pre-buffering Automatique
|
||||
|
||||
**Déclenchement** : À ETA = 30 secondes du prochain point d'intérêt
|
||||
|
||||
```
|
||||
[App Mobile]
|
||||
↓ (ETA=30s, position GPS détectée)
|
||||
[Cache Manager]
|
||||
↓ (télécharge en arrière-plan)
|
||||
[CDN NGINX] → /audio/poi-{id}/intro.m4a (10-15s d'audio, ~5-8 MB)
|
||||
↓
|
||||
[Cache Local Mobile] (max 3 POI simultanés)
|
||||
```
|
||||
|
||||
**Stratégie de cache** :
|
||||
- Télécharge les **15 premières secondes** de chaque POI à proximité
|
||||
- Limite : 3 POI simultanés en cache (max ~25 MB)
|
||||
- Purge automatique après 200m de distance passée
|
||||
- Format : M4A haute qualité (128 kbps) pour intro, puis bascule HLS pour la suite
|
||||
|
||||
#### 4.3.2 ETA de Notification Adaptatif
|
||||
|
||||
**Algorithme** :
|
||||
|
||||
```python
|
||||
def calculate_notification_eta(poi, user_position, user_speed):
|
||||
distance_to_poi = haversine(user_position, poi.position)
|
||||
is_cached = cache.has(poi.audio_id)
|
||||
hls_latency = metrics.get_average_latency(user_id) # 8-18s typique
|
||||
|
||||
if is_cached:
|
||||
# Cache prêt → notification courte (temps de réaction)
|
||||
notification_eta = 5 # secondes
|
||||
else:
|
||||
# Pas de cache → compenser latence HLS + marge
|
||||
notification_eta = hls_latency + 3 + 2 # latence + marge + réaction
|
||||
# Typique: 10s + 3s + 2s = 15s
|
||||
|
||||
time_to_poi = distance_to_poi / user_speed
|
||||
|
||||
if time_to_poi <= notification_eta:
|
||||
send_notification(poi)
|
||||
|
||||
if time_to_poi <= 30 and not is_cached:
|
||||
cache.preload_async(poi.audio_id)
|
||||
```
|
||||
|
||||
**Résultat** :
|
||||
|
||||
| Situation | ETA Notification | Distance (à 90 km/h) | Expérience |
|
||||
|-----------|------------------|----------------------|------------|
|
||||
| Cache prêt | 5s | 125m avant | ✅ Lecture instantanée |
|
||||
| Cache en cours | 15s | 375m avant | ✅ Lecture à temps |
|
||||
| 3G lent | 20s | 500m avant | ✅ Compensation latence |
|
||||
|
||||
#### 4.3.3 Mesure Dynamique de Latence
|
||||
|
||||
**Tracking par utilisateur** :
|
||||
|
||||
```go
|
||||
type HLSMetrics struct {
|
||||
UserID uuid.UUID
|
||||
AvgLatency time.Duration // Moyenne glissante 10 lectures
|
||||
NetworkType string // "4G", "5G", "3G", "wifi"
|
||||
LastMeasured time.Time
|
||||
}
|
||||
|
||||
// Mesure lors de chaque lecture
|
||||
func (m *HLSMetrics) RecordPlaybackStart(requestTime, firstByteTime time.Time) {
|
||||
latency := firstByteTime.Sub(requestTime)
|
||||
m.AvgLatency = (m.AvgLatency*9 + latency) / 10 // Moyenne glissante
|
||||
}
|
||||
```
|
||||
|
||||
**Utilisation** : L'ETA adaptatif utilise `AvgLatency` personnalisé par utilisateur au lieu d'une valeur fixe.
|
||||
|
||||
#### 4.3.4 Fallback et Indicateurs Visuels
|
||||
|
||||
Si le pre-buffer échoue (réseau faible, pas de cache), afficher un **loader avec progression** :
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 🏰 Château de Fontainebleau │
|
||||
│ │
|
||||
│ [████████░░] 80% │
|
||||
│ Chargement de l'audio... │
|
||||
│ │
|
||||
│ 📍 Vous arrivez dans 12s │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **Feedback visuel** : Utilisateur comprend que ça charge
|
||||
- **ETA affiché** : Maintient l'attention et réduit la frustration perçue
|
||||
- **Timeout** : Si > 30s sans succès, proposer mode dégradé (texte seul)
|
||||
|
||||
### Impact sur l'Infrastructure
|
||||
|
||||
#### Backend (Go)
|
||||
- **Nouveau service** : `audiocache.Service` pour préparer les extraits M4A
|
||||
- **Endpoint** : `GET /api/v1/audio/poi/:id/intro` (retourne 15s d'audio)
|
||||
- **CDN** : Cache NGINX avec TTL 7 jours sur `/audio/*/intro.m4a`
|
||||
|
||||
#### Mobile (Flutter)
|
||||
- **Package** : `just_audio` avec cache local (`flutter_cache_manager`)
|
||||
- **Stockage** : Max 100 MB de cache audio (auto-purge LRU)
|
||||
- **Logique** : `PreBufferService` avec scoring de priorité POI
|
||||
|
||||
#### Coûts
|
||||
- **Bande passante** : +10-15 MB/utilisateur/session (vs streaming pur)
|
||||
- **Stockage CDN** : +500 MB pour 1000 POI × 5 MB intro (négligeable)
|
||||
- **Économie** : Cache CDN réduit les requêtes origin (-60% selon tests)
|
||||
|
||||
### Métriques de Succès
|
||||
|
||||
- **Latence perçue** : < 1 seconde dans 95% des cas (cache hit)
|
||||
- **Synchronisation** : Audio démarre à ±10 mètres du POI (objectif ±50m)
|
||||
- **Cache hit rate** : > 90% pour utilisateurs en mode navigation
|
||||
- **Consommation data** : +15% vs HLS pur, mais acceptable pour UX
|
||||
|
||||
### Références
|
||||
|
||||
- [HLS Authoring Specification](https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices)
|
||||
- [Low-Latency HLS (LL-HLS)](https://developer.apple.com/documentation/http_live_streaming/enabling_low-latency_hls)
|
||||
- Règle Métier 05 : Section 5.2 (Mode Voiture, lignes 16-84)
|
||||
- Règle Métier 17 : Section 17.2 (ETA Géolocalisé, lignes 25-65)
|
||||
- **ADR-019** : Architecture des Notifications Géolocalisées
|
||||
41
docs/adr/003-codec-audio.md
Normal file
41
docs/adr/003-codec-audio.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# ADR-003 : Codec Audio
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2025-01-17
|
||||
|
||||
## Contexte
|
||||
|
||||
Audio diffusé en voiture : environnement bruyant, réseau mobile variable, qualité studio non nécessaire.
|
||||
|
||||
## Décision
|
||||
|
||||
**Opus** comme codec principal, **AAC-LC** en fallback.
|
||||
|
||||
## Profils d'encodage
|
||||
|
||||
| Qualité | Bitrate | Usage |
|
||||
|---------|---------|-------|
|
||||
| Basse | 24 kbps | 2G/Edge |
|
||||
| Standard | 48 kbps | 3G |
|
||||
| Haute | 64 kbps | 4G/5G |
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Codec | Bitrate | Qualité voix | Support mobile |
|
||||
|-------|---------|--------------|----------------|
|
||||
| **Opus** | 24-64 kbps | Excellente | Android natif, iOS via libs |
|
||||
| AAC-LC | 64-128 kbps | Bonne | Universel |
|
||||
| AAC-HE v2 | 32-64 kbps | Très bonne | Bon |
|
||||
| MP3 | 128-320 kbps | Correcte | Universel (legacy) |
|
||||
|
||||
## Justification
|
||||
|
||||
- **Environnement bruyant** : Opus intègre des algorithmes de résilience au bruit
|
||||
- **Bande passante** : 48 kbps Opus ≈ qualité 96 kbps AAC pour la voix
|
||||
- **Consommation data** : ~20 MB/heure à 48 kbps
|
||||
- **Latence** : 2.5-60ms, idéal pour streaming adaptatif
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Fallback AAC-LC pour appareils legacy
|
||||
- Pipeline d'encodage à prévoir côté ingestion
|
||||
56
docs/adr/004-cdn.md
Normal file
56
docs/adr/004-cdn.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# ADR-004 : CDN
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2025-01-25
|
||||
|
||||
## Contexte
|
||||
|
||||
Distribution audio HLS à 10M d'utilisateurs, besoin de performance, coût maîtrisé, et **souveraineté** : pas de dépendance à un service commercial tiers.
|
||||
|
||||
## Décision
|
||||
|
||||
**NGINX auto-hébergé** sur OVH comme cache HLS, avec OVH Object Storage en origin.
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Solution | Coût MVP/mois | Souveraineté | Setup | Dépendance |
|
||||
|----------|---------------|--------------|-------|------------|
|
||||
| **NGINX self-hosted** | ~14€ | 100% FR | 2h | Aucune |
|
||||
| CDN commercial externe | ~1 000€ | Slovénie | 15 min | Forte |
|
||||
| OVHcloud CDN | ~50-200€ | 100% FR | 1h | Moyenne |
|
||||
| Cloudflare | 0-5 000€ | US | 5 min | Forte |
|
||||
| CloudFront | ~9 750€ | US (AWS) | 1h | Très forte |
|
||||
|
||||
## Justification
|
||||
|
||||
- **Souveraineté** : 100% français (OVH), 100% contrôlé, zéro dépendance commerciale
|
||||
- **Open-source** : NGINX sous licence BSD, stack entièrement libre
|
||||
- **Coût** : ~14€/mois MVP (VPS inclus), scaling linéaire et prévisible
|
||||
- **Performance** : Cache multiplexing 1→1000 (1 Mbps origin → 1 Gbps clients)
|
||||
- **Conformité RGPD** : Données hébergées en France
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Clients
|
||||
↓
|
||||
NGINX Cache Proxy (OVH VPS Essential)
|
||||
├─ Cache RAM disk (2GB)
|
||||
├─ TTL .m3u8: 5s
|
||||
└─ TTL .ts: 7 jours
|
||||
↓
|
||||
OVH Object Storage (origin)
|
||||
```
|
||||
|
||||
## Évolution prévue
|
||||
|
||||
1. **Phase 1** (0-20K users) : NGINX mono-région
|
||||
2. **Phase 2** (20-100K users) : NGINX multi-région (Gravelines + Strasbourg) + GeoDNS
|
||||
3. **Phase 3** (100K+) : Évaluation CDN managé européen si ROI justifié
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Configuration NGINX avec `proxy_cache_path` et règles de TTL
|
||||
- RAM disk `/mnt/ramdisk` pour performance maximale
|
||||
- Monitoring bande passante et taux de cache hit
|
||||
- Sécurité : token authentication pour protéger les segments HLS
|
||||
67
docs/adr/005-base-de-donnees.md
Normal file
67
docs/adr/005-base-de-donnees.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# ADR-005 : Base de Données
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2025-01-17
|
||||
|
||||
## Contexte
|
||||
|
||||
Requêtes géolocalisées intensives (contenus à proximité), données utilisateurs, historiques d'écoute.
|
||||
|
||||
## Décision
|
||||
|
||||
- **PostgreSQL + PostGIS** : Données persistantes et requêtes géospatiales
|
||||
- **Redis Cluster** : Cache géolocalisation et sessions
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Requête → Redis Cache → [HIT] → Réponse
|
||||
↓
|
||||
[MISS]
|
||||
↓
|
||||
PostGIS → Cache → Réponse
|
||||
```
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Usage | Option choisie | Alternatives |
|
||||
|-------|---------------|--------------|
|
||||
| Données utilisateurs | PostgreSQL | MySQL, MongoDB |
|
||||
| Géolocalisation | PostGIS | MongoDB Geo, Elasticsearch |
|
||||
| Cache | Redis | Memcached, KeyDB |
|
||||
| Analytics (futur) | ClickHouse | TimescaleDB |
|
||||
|
||||
## Justification
|
||||
|
||||
### PostgreSQL + PostGIS
|
||||
- Requêtes géospatiales complexes et précises
|
||||
- Index GIST pour performance
|
||||
- ACID, fiabilité éprouvée
|
||||
- Écosystème mature
|
||||
|
||||
### Redis
|
||||
- Cache géo natif (`GEORADIUS`) : 100K+ requêtes/sec
|
||||
- Sessions utilisateurs
|
||||
- Pub/sub pour temps réel
|
||||
|
||||
## Exemple de requête
|
||||
|
||||
```sql
|
||||
SELECT id, name,
|
||||
ST_Distance(location::geography, ST_MakePoint($lon, $lat)::geography) as distance
|
||||
FROM contents
|
||||
WHERE ST_DWithin(location::geography, ST_MakePoint($lon, $lat)::geography, 50000)
|
||||
ORDER BY distance
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
## Conséquences
|
||||
|
||||
- TTL cache Redis : 5 minutes (le contenu géolocalisé ne bouge pas)
|
||||
- Index GIST sur colonnes géométriques
|
||||
- Réplication read replicas pour scaling lecture
|
||||
|
||||
## Documentation technique détaillée
|
||||
|
||||
- [Diagramme de séquence cache géospatial](../architecture/sequences/cache-geospatial.md)
|
||||
- [Schéma base de données](../architecture/database/schema.md)
|
||||
44
docs/adr/006-chiffrement.md
Normal file
44
docs/adr/006-chiffrement.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# ADR-006 : Chiffrement
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2025-01-17
|
||||
|
||||
## Contexte
|
||||
|
||||
Streaming audio sur réseaux mobiles, conformité RGPD, protection du contenu.
|
||||
|
||||
## Décision
|
||||
|
||||
- **TLS 1.3** sur tous les endpoints
|
||||
- **DTLS-SRTP** pour WebRTC (radio live)
|
||||
- Pas de DRM au lancement
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Méthode | Overhead | Usage |
|
||||
|---------|----------|-------|
|
||||
| **TLS 1.3** | ~1-2% CPU | HTTPS streaming |
|
||||
| DTLS-SRTP | ~3-5% CPU | WebRTC temps réel |
|
||||
| AES-128-CBC | Minimal | Chiffrement segments HLS |
|
||||
| Widevine/FairPlay | Modéré | DRM (si licences l'exigent) |
|
||||
|
||||
## Justification
|
||||
|
||||
### Pourquoi chiffrer ?
|
||||
|
||||
- **RGPD** : Protection des données utilisateurs obligatoire
|
||||
- **Confiance** : Standard attendu en 2025
|
||||
- **Intégrité** : Empêche injection de contenu par opérateurs
|
||||
- **Overhead minimal** : TLS 1.3 optimisé, impact négligeable
|
||||
|
||||
### Pourquoi pas de DRM ?
|
||||
|
||||
- Contenu généré par utilisateurs (pas de licences)
|
||||
- Complexité et coût d'intégration Widevine/FairPlay
|
||||
- À reconsidérer si partenariats avec labels/éditeurs
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Certificats SSL gérés par Let's Encrypt (auto-renouvelés via Nginx)
|
||||
- Configuration TLS 1.3 sur Nginx/API
|
||||
- DTLS-SRTP à implémenter pour le module radio live
|
||||
62
docs/adr/007-tests-bdd.md
Normal file
62
docs/adr/007-tests-bdd.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# ADR-007 : Tests et Spécifications Exécutables
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2025-01-17
|
||||
|
||||
## Contexte
|
||||
|
||||
RoadWave nécessite une documentation des use cases qui soit à la fois lisible par tous les stakeholders et vérifiable automatiquement. Les scénarios utilisateurs (touriste, routier, commerçant) doivent être validés en continu.
|
||||
|
||||
## Décision
|
||||
|
||||
**Gherkin** pour les spécifications avec **Godog** comme runner de tests.
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Option | Lisibilité | Intégration Go | Maintenance |
|
||||
|--------|------------|----------------|-------------|
|
||||
| **Gherkin + Godog** | Excellente | Native | Faible |
|
||||
| Gauge (Markdown) | Bonne | Plugin | Moyenne |
|
||||
| Tests Go natifs | Faible (devs only) | Native | Faible |
|
||||
| Concordion | Bonne | Java-centric | Élevée |
|
||||
|
||||
## Justification
|
||||
|
||||
- **Living Documentation** : Les fichiers `.feature` servent de documentation ET de tests
|
||||
- **Accessibilité** : Syntaxe Given/When/Then lisible par PO, devs, testeurs
|
||||
- **Cohérence stack** : Godog est le standard BDD pour Go
|
||||
- **CI/CD** : Intégration simple dans les pipelines
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
features/
|
||||
├── recommendation/
|
||||
│ ├── geolocalisation.feature
|
||||
│ └── interets.feature
|
||||
├── streaming/
|
||||
│ ├── lecture.feature
|
||||
│ └── buffering.feature
|
||||
├── moderation/
|
||||
│ └── signalement.feature
|
||||
└── steps/
|
||||
└── steps.go
|
||||
```
|
||||
|
||||
## Exemple
|
||||
|
||||
```gherkin
|
||||
Feature: Recommandation géolocalisée
|
||||
|
||||
Scenario: Touriste près d'un monument
|
||||
Given un utilisateur avec l'intérêt "tourisme" à 80%
|
||||
And une position GPS à 100m de la Tour Eiffel
|
||||
When le système calcule les recommandations
|
||||
Then l'audio guide "Histoire de la Tour Eiffel" est en première position
|
||||
```
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Dépendance : `github.com/cucumber/godog`
|
||||
- Les use cases du README doivent être traduits en `.feature`
|
||||
- CI exécute `godog run` avant chaque merge
|
||||
119
docs/adr/008-authentification.md
Normal file
119
docs/adr/008-authentification.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# ADR-008 : Authentification et Gestion d'Identité
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2025-01-18
|
||||
|
||||
## Contexte
|
||||
|
||||
RoadWave nécessite un système d'authentification sécurisé pour mobile (iOS/Android), scalable jusqu'à 10M utilisateurs, avec contraintes de coût réduit et conformité RGPD.
|
||||
|
||||
**Exigence de souveraineté** : En cohérence avec ADR-004 (CDN 100% français), les données d'authentification doivent être hébergées en France pour garantir une souveraineté totale.
|
||||
|
||||
## Décision
|
||||
|
||||
**Zitadel self-hosted sur OVH France** pour l'IAM avec validation JWT locale côté API Go.
|
||||
|
||||
**Architecture de déploiement** :
|
||||
- Container Docker sur le même VPS OVH (Gravelines, France) que l'API
|
||||
- Base de données PostgreSQL partagée avec RoadWave (séparation logique par schéma)
|
||||
- Aucune donnée d'authentification ne transite par des serveurs tiers
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Solution | Coût (10M users) | Performance | Simplicité | Intégration Go |
|
||||
|----------|------------------|-------------|------------|----------------|
|
||||
| **Zitadel** | 200-500€/mois | Excellente | Élevée | SDK natif |
|
||||
| Supabase Auth | 32K€/mois | Excellente | Élevée | REST API |
|
||||
| Keycloak | 200-800€/mois | Bonne | Faible | Lib tierce |
|
||||
| Auth0 | 50K€+/mois | Excellente | Élevée | SDK natif |
|
||||
| JWT Custom | 0€ (dev) | Excellente | Moyenne | Natif |
|
||||
|
||||
## Justification
|
||||
|
||||
- **Souveraineté garantie** : Self-hosting sur OVH France (Gravelines) = 100% des données en France, cohérent avec ADR-004
|
||||
- **Coût maîtrisé** : 100x moins cher que Supabase/Auth0 à 10M users (pas de coût par utilisateur actif)
|
||||
- **Performance** : JWT validation locale = 0 latence auth sur chaque requête API
|
||||
- **Stack alignée** : Go + PostgreSQL + Redis (déjà dans RoadWave)
|
||||
- **Scalabilité prouvée** : Clients avec 2.3M tenants, architecture event-sourced
|
||||
- **RGPD natif** : Open source, contrôle total des données, DPA non nécessaire (pas de sous-traitant)
|
||||
- **Standards ouverts** : OpenID Connect certifié (pas de vendor lock-in, migration facile si besoin)
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Mobile Apps │ OAuth2 PKCE + Refresh tokens
|
||||
│ (iOS/Android) │
|
||||
└────────┬────────┘
|
||||
│ HTTPS
|
||||
│
|
||||
┌────▼─────────────────────────────────┐
|
||||
│ OVH VPS Essential (Gravelines, FR) │
|
||||
│ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ Zitadel IdP │ Port 8081 │
|
||||
│ │ (Docker) │ Self-hosted │
|
||||
│ └────────┬────────┘ │
|
||||
│ │ JWT token │
|
||||
│ ┌────────▼────────┐ │
|
||||
│ │ Go + Fiber API │ Port 8080 │
|
||||
│ │ (RoadWave) │ Validation │
|
||||
│ │ │ JWT locale │
|
||||
│ └────────┬────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────▼────────┐ │
|
||||
│ │ PostgreSQL │ Schémas: │
|
||||
│ │ + PostGIS │ - roadwave │
|
||||
│ │ │ - zitadel │
|
||||
│ └─────────────────┘ │
|
||||
└───────────────────────────────────────┘
|
||||
|
||||
Données 100% hébergées en France (souveraineté totale)
|
||||
```
|
||||
|
||||
## Exemple d'intégration
|
||||
|
||||
```go
|
||||
import "github.com/zitadel/zitadel-go/v3/pkg/authorization/oauth"
|
||||
|
||||
// Validation JWT locale haute performance
|
||||
verifier := oauth.WithJWT(config)
|
||||
app.Use(verifier.Middleware())
|
||||
|
||||
// Accès aux claims
|
||||
userID := ctx.Locals("sub").(string)
|
||||
```
|
||||
|
||||
## Conséquences
|
||||
|
||||
### Positives
|
||||
|
||||
- ✅ **Souveraineté totale** : Données 100% en France (OVH Gravelines), contrôle complet
|
||||
- ✅ **Coût prévisible** : Pas de surprise à la croissance (pas de facturation par utilisateur)
|
||||
- ✅ **Performance** : Latence minimale (même VPS que l'API)
|
||||
- ✅ **Fonctionnalités avancées** : MFA, passkeys, SSO disponibles gratuitement
|
||||
- ✅ **Conformité RGPD** : Pas de DPA nécessaire (pas de sous-traitant externe)
|
||||
- ✅ **Standards ouverts** : Migration facile vers autre solution si besoin
|
||||
|
||||
### Négatives
|
||||
|
||||
- ❌ **Maintenance** : Nécessite monitoring et mises à jour régulières
|
||||
- ❌ **Complexité initiale** : Configuration PostgreSQL schema partagé
|
||||
- ❌ **Backup** : Responsabilité de sauvegarder les données utilisateurs
|
||||
- ❌ **Scaling** : Migration Kubernetes nécessaire au-delà de 500K utilisateurs
|
||||
|
||||
### Déploiement
|
||||
|
||||
- **MVP (0-20K)** : Docker sur VPS OVH Essential (coût inclus)
|
||||
- **Growth (20K-500K)** : Même architecture, VPS plus puissant si besoin
|
||||
- **Scale (500K+)** : Migration Kubernetes managé avec haute disponibilité
|
||||
|
||||
### Coût Estimé
|
||||
|
||||
| Phase | Utilisateurs | Coût Zitadel/mois |
|
||||
|-------|--------------|-------------------|
|
||||
| MVP | 0-20K | 0€ (inclus VPS) |
|
||||
| Growth | 20K-500K | 0€ (inclus VPS) |
|
||||
| Scale | 500K+ | 50-100€ (instance K8s dédiée) |
|
||||
|
||||
**Comparaison** : Auth0 coûterait 50K€/mois pour 10M utilisateurs vs 100€/mois en self-hosted.
|
||||
61
docs/adr/009-solution-paiement.md
Normal file
61
docs/adr/009-solution-paiement.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# ADR-009 : Solution de Paiement et Gestion des Abonnements
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2025-01-19
|
||||
|
||||
## Contexte
|
||||
|
||||
RoadWave nécessite une solution de paiement pour gérer les abonnements Premium (4.99€/mois) et reverser 70% des revenus aux créateurs de contenu. Besoin de marketplace natif (split payments), KYC automatique, conformité RGPD, et coûts maîtrisés.
|
||||
|
||||
## Décision
|
||||
|
||||
**Mangopay** (France/Luxembourg) comme solution unique pour paiements, marketplace et abonnements.
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Solution | Coût transaction | Marketplace | KYC | Souveraineté |
|
||||
|----------|-----------------|-------------|-----|--------------|
|
||||
| **Mangopay** | 1.8% + 0.18€ | ✅ Natif | ✅ Gratuit | 🇪🇺 France/LU |
|
||||
| Stripe Connect | 2.9% + 0.30€ | ✅ Natif | ❌ 1.20€ | 🇺🇸 USA |
|
||||
| Mollie | 2.9% + 0.29€ | ❌ Non | ❌ Non | 🇪🇺 Pays-Bas |
|
||||
| Paddle | 5% + 0.50€ | ✅ Natif | ✅ Inclus | 🇬🇧 UK |
|
||||
|
||||
## Justification
|
||||
|
||||
- **38% moins cher** que Stripe (1.8% vs 2.9%)
|
||||
- **Marketplace natif** : E-wallets automatiques, split payments 70/30, payouts SEPA gratuits
|
||||
- **KYC gratuit** : vérification d'identité incluse (vs 1.20€/créateur chez Stripe)
|
||||
- **Souveraineté EU** : France/Luxembourg, régulé ACPR, RGPD natif
|
||||
- **Conformité DAC7** : reporting fiscal automatique
|
||||
- **Spécialisé marketplace** : utilisé par Vinted, Ulule, ManoMano
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌────────────────────────┐
|
||||
│ Utilisateurs Premium │ 4.99€/mois
|
||||
└───────────┬────────────┘
|
||||
│
|
||||
┌───────▼───────┐
|
||||
│ Mangopay │ - Abonnements récurrents
|
||||
│ │ - KYC créateurs (gratuit)
|
||||
│ │ - E-wallets automatiques
|
||||
└───────┬───────┘ - Payouts SEPA (gratuits)
|
||||
│
|
||||
┌─────────┼─────────┐
|
||||
│ │ │
|
||||
┌─▼───┐ ┌─▼───┐ ┌─▼────┐
|
||||
│Créa │ │Créa │ │Plate-│
|
||||
│teur │ │teur │ │forme │
|
||||
│ A │ │ B │ │(30%) │
|
||||
│(70%)│ │(70%)│ │ │
|
||||
└─────┘ └─────┘ └──────┘
|
||||
```
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Solution tout-en-un : 1 seul prestataire vs 2-3
|
||||
- Économie de 2160€/an sur 1000 abonnés (vs Stripe)
|
||||
- Délai activation compte : 2-5 jours
|
||||
- Intégration Go via REST API (pas de SDK Go officiel)
|
||||
- Apple/Google IAP gérés séparément (comme toute solution de paiement)
|
||||
46
docs/adr/010-commandes-volant.md
Normal file
46
docs/adr/010-commandes-volant.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# ADR-010 : Commandes au volant et likes
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2026-01-20
|
||||
|
||||
## Contexte
|
||||
|
||||
RoadWave est utilisée en conduisant. Les utilisateurs doivent pouvoir liker du contenu pour améliorer les recommandations, mais les commandes au volant ont des limitations :
|
||||
- 40% des véhicules n'ont que Suivant/Précédent/Mute
|
||||
- iOS/Android ne supportent pas nativement les appuis longs ou doubles-appuis
|
||||
- La sécurité impose des interactions minimales
|
||||
|
||||
## Décision
|
||||
|
||||
**Like automatique basé sur le temps d'écoute**.
|
||||
|
||||
Règles :
|
||||
- ≥80% d'écoute → Like renforcé (+2 points)
|
||||
- 30-79% d'écoute → Like standard (+1 point)
|
||||
- <30% d'écoute → Pas de like
|
||||
- Skip <10s → Signal négatif (-0.5 point)
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Option | Compatibilité | Sécurité | Complexité |
|
||||
|--------|---------------|----------|------------|
|
||||
| **Like automatique** | 100% | Maximale | Faible |
|
||||
| Double-tap Pause | ~80% | Moyenne | Moyenne |
|
||||
| Appui long Suivant | ~95% | Faible | Élevée |
|
||||
| Configuration paramétrable | 100% | Variable | Très élevée |
|
||||
|
||||
## Justification
|
||||
|
||||
- **Sécurité maximale** : Aucune action complexe en conduite
|
||||
- **Compatibilité universelle** : Fonctionne sur 100% des véhicules
|
||||
- **UX intuitive** : Comportement standard (Spotify, YouTube Music)
|
||||
- **Engagement** : Tous les contenus génèrent des signaux
|
||||
- **Simplicité** : Une seule logique à implémenter et maintenir
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Tracking du temps d'écoute via le player audio
|
||||
- Calcul du score côté backend basé sur `completion_rate`
|
||||
- Communication onboarding : "Vos likes sont automatiques selon votre temps d'écoute"
|
||||
- Possibilité de like manuel depuis l'app (à l'arrêt)
|
||||
- Métriques à suivre : taux de complétion, distribution des scores, feedbacks utilisateurs
|
||||
137
docs/adr/011-conformite-stores-carplay-android-auto.md
Normal file
137
docs/adr/011-conformite-stores-carplay-android-auto.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# ADR-011 : Conformité App Stores et Plateformes Auto
|
||||
|
||||
**Statut** : Accepté avec actions requises
|
||||
**Date** : 2026-01-20
|
||||
|
||||
## Contexte
|
||||
|
||||
RoadWave est une app audio géolocalisée utilisée en conduite (CarPlay/Android Auto) avec :
|
||||
- Contenu généré par utilisateurs (UGC)
|
||||
- Monétisation : publicités géolocalisées + Premium (4.99€ web / 5.99€ IAP)
|
||||
- GPS en arrière-plan
|
||||
- Partage de revenus avec créateurs (70/30)
|
||||
|
||||
## Décision
|
||||
|
||||
**Stratégie de conformité multi-plateforme** avec :
|
||||
- Modération UGC robuste (IA + humain)
|
||||
- Prix différenciés selon région (US/EU/Monde)
|
||||
- GPS avec disclosure complète
|
||||
- Paiements créateurs externes (Mangopay)
|
||||
|
||||
## Plateformes analysées
|
||||
|
||||
| Plateforme | Conformité | Points critiques |
|
||||
|------------|------------|------------------|
|
||||
| **Android Auto** | ✅ Conforme | API Level 35+ (Android 15+) |
|
||||
| **CarPlay** | ✅ Conforme | Entitlement audio à demander |
|
||||
| **Google Play** | ⚠️ Actions requises | Déclaration GPS + UGC modération |
|
||||
| **App Store** | ⚠️ Actions requises | Prix différenciés US/EU |
|
||||
|
||||
## Conformité détaillée
|
||||
|
||||
### Android Auto / CarPlay ✅
|
||||
- 100% audio (pas de vidéo)
|
||||
- Commandes standard au volant
|
||||
- Aucun achat in-car
|
||||
- Like automatique = sécurité maximale
|
||||
- **Notifications géolocalisées** : sonore uniquement en mode CarPlay/Android Auto (pas d'overlay visuel)
|
||||
- **Action** : Demander CarPlay Audio Entitlement (Apple)
|
||||
|
||||
### Google Play ⚠️
|
||||
|
||||
**UGC (critique)** :
|
||||
- Modération hybride IA + humain ✅
|
||||
- 3 premiers contenus validés manuellement ✅
|
||||
- Système de strikes (4 = ban) ✅
|
||||
- Signalement + blocage utilisateurs ✅
|
||||
|
||||
**GPS Background (critique)** :
|
||||
- Permission "Always Location" = **OPTIONNELLE**
|
||||
- Demandée uniquement pour mode piéton (notifications arrière-plan audio-guides)
|
||||
- Justification Play Console :
|
||||
> "RoadWave permet aux utilisateurs de recevoir des alertes audio-guides lorsqu'ils passent à pied près de monuments/musées, même quand l'app est en arrière-plan. Cette fonctionnalité est optionnelle et peut être désactivée dans les paramètres."
|
||||
- In-app disclosure obligatoire (écran dédié avant demande permission)
|
||||
- Si refusée : app fonctionne en mode voiture uniquement
|
||||
- **Action** : Remplir formulaire background location Play Console avec justification
|
||||
|
||||
**Réponses formulaire Play Console** :
|
||||
|
||||
| Question | Réponse |
|
||||
|----------|---------|
|
||||
| Why does your app need background location? | "RoadWave offers optional pedestrian mode: users receive push notifications when passing near audio-guide points (museums, monuments) even when app is in background. This feature is opt-in and can be disabled in settings." |
|
||||
| Is this feature core to your app? | "No. This is an optional feature. Users can use RoadWave without background location permission (in-car mode works with foreground location only)." |
|
||||
| What user value does this provide? | "Pedestrian users (tourists, museum visitors) can keep phone in pocket and receive audio-guide alerts automatically without opening the app." |
|
||||
| Does a less invasive alternative exist? | "Yes. Users can use manual navigation (open app, select audio-guide). Background location is a convenience feature for hands-free experience." |
|
||||
|
||||
### App Store ⚠️
|
||||
|
||||
**Prix différenciés (légaux depuis 2025-2026)** :
|
||||
- 🇺🇸 US : Lien externe autorisé (0% commission)
|
||||
- 🇪🇺 EU : Paiement externe DMA (7-20% commission réduite)
|
||||
- 🌍 Monde : IAP obligatoire (30% commission)
|
||||
|
||||
**UGC** :
|
||||
- Mode Kids obligatoire (filtrage selon âge) ✅
|
||||
- Système de modération + signalement ✅
|
||||
|
||||
**GPS Background (critique)** :
|
||||
- Permission "Always Location" = **OPTIONNELLE**
|
||||
- Deux strings Info.plist requises :
|
||||
- `NSLocationWhenInUseUsageDescription` : explication mode voiture
|
||||
- `NSLocationAlwaysAndWhenInUseUsageDescription` : explication mode piéton (optionnel)
|
||||
- In-app disclosure obligatoire avant demande "Always"
|
||||
- Flux two-step : When In Use → Always (si user active mode piéton)
|
||||
- Si refusée : app fonctionne en mode voiture uniquement
|
||||
- **Action** : Voir strings détaillés dans [05-interactions-navigation.md](../regles-metier/05-interactions-navigation.md#512-mode-piéton-audio-guides)
|
||||
|
||||
### Revenus créateurs
|
||||
|
||||
**Position** : Paiements créateurs = "services" (comme YouTube/Uber), pas IAP
|
||||
- Paiement via Mangopay Connect (externe)
|
||||
- Commission stores uniquement sur Premium (IAP)
|
||||
- Comparables : YouTube AdSense, TikTok Creator Fund, Uber
|
||||
|
||||
## Actions bloquantes avant soumission
|
||||
|
||||
| Action | Plateforme | Deadline | Complexité |
|
||||
|--------|-----------|----------|------------|
|
||||
| Demander CarPlay Audio Entitlement | Apple | Avant soumission iOS | Faible |
|
||||
| Remplir formulaire background location avec justification | Google Play | Avant soumission Android | Faible |
|
||||
| Implémenter disclosure GPS (écran dédié mode piéton) | iOS + Android | MVP | Moyenne |
|
||||
| Rendre permission "Always Location" optionnelle | iOS + Android | MVP | Moyenne |
|
||||
| Désactiver overlay visuel notification en CarPlay/Android Auto | iOS + Android | MVP | Moyenne |
|
||||
| Mettre à jour strings Info.plist avec justifications détaillées | iOS | MVP | Faible |
|
||||
| Finaliser système modération UGC | Google + Apple | MVP | Élevée |
|
||||
|
||||
**Estimation totale** : +5 jours développement avant soumission stores
|
||||
|
||||
## Stratégie de lancement
|
||||
|
||||
**Phase 1 - MVP** :
|
||||
- IAP uniquement (5.99€/mois mondial)
|
||||
- Modération UGC active
|
||||
- GPS avec disclosure
|
||||
- CarPlay/Android Auto basique
|
||||
|
||||
**Phase 2 - Post-validation** :
|
||||
- Prix différenciés US (lien externe 4.99€)
|
||||
- Paiement externe EU (DMA)
|
||||
- Monétisation créateurs (Mangopay)
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Formation équipe sur politiques stores
|
||||
- Suivi des métriques modération (% rejet, SLA)
|
||||
- Migration iOS 26 SDK (Avril 2026)
|
||||
- API Level 35 Android (2026)
|
||||
- Communication transparente GPS/publicités
|
||||
|
||||
## Sources
|
||||
|
||||
- [Android Auto Media Apps](https://developer.android.com/training/cars/media)
|
||||
- [CarPlay Developer Guide](https://developer.apple.com/carplay)
|
||||
- [Google Play UGC Policy](https://support.google.com/googleplay/android-developer/answer/9876937)
|
||||
- [App Store Guidelines](https://developer.apple.com/app-store/review/guidelines/)
|
||||
- [Apple DMA Update EU](https://www.revenuecat.com/blog/growth/apple-eu-dma-update-june-2025/)
|
||||
- [Google Background Location 2026](https://support.google.com/googleplay/android-developer/answer/9799150)
|
||||
50
docs/adr/012-architecture-backend.md
Normal file
50
docs/adr/012-architecture-backend.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# ADR-012 : Architecture Backend
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2025-01-20
|
||||
|
||||
## Contexte
|
||||
|
||||
RoadWave nécessite une architecture backend évolutive tout en gardant la simplicité opérationnelle pour un MVP. Le système doit supporter une croissance progressive de 0 à 10M utilisateurs.
|
||||
|
||||
## Décision
|
||||
|
||||
**Monolithe modulaire** avec séparation claire en modules internes.
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Architecture | Complexité | Coûts infra | Time to market | Évolutivité |
|
||||
|--------------|------------|-------------|----------------|-------------|
|
||||
| **Monolithe modulaire** | Faible | Faible | Rapide | 0-1M users |
|
||||
| Microservices | Élevée | Élevée | Lent | 1M+ users |
|
||||
| Hybrid (Mono + Workers) | Moyenne | Moyenne | Moyen | 100K-5M users |
|
||||
|
||||
## Justification
|
||||
|
||||
- **Simplicité** : 1 seul binaire Go, déploiement trivial
|
||||
- **Transactions** : Communications inter-modules en mémoire (pas de latence réseau)
|
||||
- **Debugging** : Stack traces complètes, profiling unifié
|
||||
- **Coûts** : 1 serveur suffit pour 100K users (vs N services)
|
||||
- **Refactoring** : Modules internes bien séparés facilitent migration vers microservices si nécessaire
|
||||
|
||||
## Structure modulaire
|
||||
|
||||
```
|
||||
internal/
|
||||
├── auth/ # Validation JWT, intégration Zitadel
|
||||
├── user/ # Profils, centres d'intérêt
|
||||
├── content/ # CRUD contenus, métadonnées
|
||||
├── geo/ # Recherche géospatiale, algorithme
|
||||
├── streaming/ # Génération HLS, transcoding
|
||||
├── moderation/ # Signalements, workflow
|
||||
├── payment/ # Intégration Mangopay
|
||||
└── analytics/ # Métriques écoute, jauges
|
||||
```
|
||||
|
||||
Chaque module suit : `handler.go` → `service.go` → `repository.go`.
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Scaling horizontal : réplication complète du binaire (acceptable jusqu'à 1M users)
|
||||
- Transition vers microservices possible en phase 2 (extraction progressive des modules)
|
||||
- Importance de maintenir découplage fort entre modules (interfaces claires)
|
||||
57
docs/adr/013-orm-acces-donnees.md
Normal file
57
docs/adr/013-orm-acces-donnees.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# ADR-013 : ORM et Accès Données
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2025-01-20
|
||||
|
||||
## Contexte
|
||||
|
||||
RoadWave nécessite des requêtes SQL complexes (PostGIS géospatiales) avec performance optimale et type safety. Le choix entre ORM, query builder ou SQL brut impacte maintenabilité et performance.
|
||||
|
||||
## Décision
|
||||
|
||||
**sqlc** pour génération de code Go type-safe depuis SQL.
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Solution | Performance | Type Safety | Contrôle SQL | Courbe apprentissage |
|
||||
|----------|-------------|-------------|--------------|----------------------|
|
||||
| **sqlc** | Excellente | Très haute | Total | Faible |
|
||||
| GORM | Moyenne | Moyenne | Limité | Faible |
|
||||
| pgx + SQL brut | Excellente | Faible | Total | Moyenne |
|
||||
| sqlx | Bonne | Faible | Total | Faible |
|
||||
|
||||
## Justification
|
||||
|
||||
- **Performance** : Génération compile-time, zero overhead runtime
|
||||
- **Type safety** : Structs Go générées automatiquement, erreurs détectées à la compilation
|
||||
- **Contrôle SQL** : Requêtes PostGIS complexes écrites en pur SQL (pas de limitations ORM)
|
||||
- **Maintenabilité** : Modifications SQL → `sqlc generate` → code mis à jour
|
||||
- **Simplicité** : Pas de magic, code généré lisible et debuggable
|
||||
|
||||
## Workflow
|
||||
|
||||
```sql
|
||||
-- queries/content.sql
|
||||
-- name: GetContentNearby :many
|
||||
SELECT id, title, ST_Distance(location, $1::geography) as distance
|
||||
FROM contents
|
||||
WHERE ST_DWithin(location, $1::geography, $2)
|
||||
ORDER BY distance
|
||||
LIMIT $3;
|
||||
```
|
||||
|
||||
```bash
|
||||
sqlc generate
|
||||
```
|
||||
|
||||
```go
|
||||
// Code Go type-safe généré automatiquement
|
||||
contents, err := q.GetContentNearby(ctx, location, radius, limit)
|
||||
```
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Dépendance : `github.com/sqlc-dev/sqlc`
|
||||
- Fichier `sqlc.yaml` à la racine pour configuration
|
||||
- Migrations gérées séparément avec `golang-migrate`
|
||||
- CI doit exécuter `sqlc generate` pour valider cohérence SQL/Go
|
||||
74
docs/adr/014-frontend-mobile.md
Normal file
74
docs/adr/014-frontend-mobile.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# ADR-014 : Frontend Mobile
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2025-01-20
|
||||
|
||||
## Contexte
|
||||
|
||||
RoadWave nécessite applications iOS et Android avec support CarPlay/Android Auto, lecture audio HLS avancée, géolocalisation temps réel. Le choix du framework impacte vélocité développement et performances.
|
||||
|
||||
## Décision
|
||||
|
||||
**Flutter** pour iOS et Android avec codebase unique.
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Framework | Codebase | Performance | Audio/CarPlay | Communauté |
|
||||
|-----------|----------|-------------|---------------|------------|
|
||||
| **Flutter** | Unique | Native | Excellente | Large |
|
||||
| React Native | Unique | Bonne | Modules natifs requis | Très large |
|
||||
| Native (Swift+Kotlin) | Double | Excellente | Native | Large |
|
||||
| Ionic/Capacitor | Unique | Moyenne | Limitée | Moyenne |
|
||||
|
||||
## Justification
|
||||
|
||||
- **Codebase unique** : iOS + Android maintenus ensemble, vélocité développement x2
|
||||
- **Performance** : Dart compilé en code natif (pas de bridge JS)
|
||||
- **Audio HLS** : Package `just_audio` mature avec support HLS, buffering adaptatif
|
||||
- **CarPlay/Android Auto** : Support via packages communautaires (`flutter_carplay`, `android_auto_flutter`)
|
||||
- **Géolocalisation** : `geolocator` robuste avec gestion permissions
|
||||
- **Écosystème** : Widgets riches (Material/Cupertino), state management mature (Bloc, Riverpod)
|
||||
|
||||
## Packages clés
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
flutter_bloc: ^8.1.3 # State management
|
||||
just_audio: ^0.9.36 # Lecture audio HLS
|
||||
geolocator: ^11.0.0 # GPS temps réel (mode voiture)
|
||||
geofence_service: ^5.2.0 # Geofencing arrière-plan (mode piéton)
|
||||
flutter_local_notifications: ^17.0.0 # Notifications géolocalisées
|
||||
dio: ^5.4.0 # HTTP client
|
||||
flutter_secure_storage: ^9.0.0 # Tokens JWT
|
||||
cached_network_image: ^3.3.1 # Cache images
|
||||
```
|
||||
|
||||
**Nouveaux packages (contenus géolocalisés)** :
|
||||
|
||||
- **`geofence_service`** : Détection entrée/sortie rayon 200m en arrière-plan (mode piéton)
|
||||
- Geofencing natif iOS/Android
|
||||
- Minimise consommation batterie
|
||||
- Supporte notifications push même app fermée
|
||||
|
||||
- **`flutter_local_notifications`** : Notifications locales avec compteur dynamique
|
||||
- Notification avec compteur décroissant (7→1) en mode voiture
|
||||
- Icônes personnalisées selon type contenu
|
||||
- Désactivation overlay en mode CarPlay/Android Auto (conformité)
|
||||
|
||||
## Structure application
|
||||
|
||||
```
|
||||
lib/
|
||||
├── core/ # Config, DI, routes
|
||||
├── data/ # Repositories, API clients
|
||||
├── domain/ # Models, business logic
|
||||
├── presentation/ # UI (screens, widgets, blocs)
|
||||
└── main.dart
|
||||
```
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Équipe doit apprendre Dart (syntaxe proche Java/TypeScript)
|
||||
- Taille binaire : 8-15 MB (acceptable)
|
||||
- Tests : `flutter_test` pour widgets, `integration_test` pour E2E
|
||||
- CI/CD : Fastlane pour déploiement stores
|
||||
84
docs/adr/015-strategie-tests.md
Normal file
84
docs/adr/015-strategie-tests.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# ADR-015 : Stratégie Tests
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2025-01-20
|
||||
|
||||
## Contexte
|
||||
|
||||
RoadWave nécessite une couverture tests robuste avec documentation vivante des use cases. La stratégie doit équilibrer vélocité développement et qualité.
|
||||
|
||||
## Décision
|
||||
|
||||
Approche **multi-niveaux** : unitaires, intégration, BDD (Gherkin), E2E, load testing.
|
||||
|
||||
## Stratégie par type
|
||||
|
||||
| Type | Framework | Cible | Fréquence |
|
||||
|------|-----------|-------|-----------|
|
||||
| **Unitaires** | Testify | 80%+ couverture | Chaque commit |
|
||||
| **Intégration DB** | Testify + Testcontainers | Repositories critiques | Avant merge PR |
|
||||
| **BDD (Gherkin)** | Godog | User stories | Avant release |
|
||||
| **E2E Mobile** | Flutter integration_test | Parcours critiques | Nightly |
|
||||
| **Load** | k6 | N/A | Avant mise en prod |
|
||||
|
||||
## Tests unitaires (Testify)
|
||||
|
||||
- **Framework** : `github.com/stretchr/testify`
|
||||
- **Couverture minimale** : 80% sur packages `internal/*/service.go`
|
||||
- **Exécution** : Chaque commit (CI rapide ~30s)
|
||||
|
||||
## Tests BDD (Gherkin + Godog)
|
||||
|
||||
- **Framework** : `github.com/cucumber/godog`
|
||||
- **Couverture** : Tous les cas d'usage du [README.md](../../README.md) traduits en `.feature`
|
||||
- **Exécution** : Avant release
|
||||
- **Détails** : Voir [ADR-007](007-tests-bdd.md) pour contexte complet
|
||||
|
||||
## Tests intégration (Testcontainers)
|
||||
|
||||
- **Framework** : `github.com/testcontainers/testcontainers-go`
|
||||
- **Scope** : Repositories avec PostGIS/Redis (requêtes spatiales complexes)
|
||||
- **Exécution** : Avant merge PR
|
||||
- **Justification** : Validation des requêtes PostGIS impossibles à mocker
|
||||
|
||||
## Tests E2E Mobile (Flutter)
|
||||
|
||||
- **Framework** : `integration_test` (Flutter officiel)
|
||||
- **Scope** : Parcours critiques (authentification, lecture audio, géolocalisation)
|
||||
- **Exécution** : Nightly builds
|
||||
- **Justification** : Validation de l'intégration complète mobile + backend
|
||||
|
||||
## Load testing (k6)
|
||||
|
||||
- **Framework** : `grafana/k6`
|
||||
- **Objectif** : API p99 < 100ms à 10K RPS
|
||||
- **Scénarios** : Recommandations géolocalisées, streaming HLS, authentification
|
||||
- **Exécution** : Avant mise en production
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Approche | Avantages | Inconvénients | Décision |
|
||||
|----------|-----------|---------------|----------|
|
||||
| **Tests unitaires uniquement** | Rapides, simples | Pas de validation intégration | ❌ Insuffisant pour PostGIS |
|
||||
| **TDD strict (100% couverture)** | Qualité maximale | Vélocité réduite (-40%) | ❌ Trop coûteux pour MVP |
|
||||
| **BDD sans tests unitaires** | Documentation vivante | Feedback lent (3-5min/run) | ❌ Cycle développement trop lent |
|
||||
| **Stratégie multi-niveaux** | Équilibre qualité/vitesse | Complexité CI/CD | ✅ **Choisi** |
|
||||
|
||||
## Justification
|
||||
|
||||
- **80% unitaires** : Feedback rapide (<30s), détection précoce des régressions
|
||||
- **BDD** : Documentation vivante alignée avec les règles métier
|
||||
- **Testcontainers** : Seule façon fiable de tester PostGIS `ST_DWithin`, `ST_Distance`
|
||||
- **E2E ciblés** : Validation des parcours critiques sans ralentir le développement
|
||||
- **k6** : Détection des régressions de performance avant production
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Dépendances :
|
||||
- `github.com/stretchr/testify`
|
||||
- `github.com/cucumber/godog`
|
||||
- `github.com/testcontainers/testcontainers-go`
|
||||
- `grafana/k6`
|
||||
- Temps CI : ~3-5 min (tests unitaires + BDD)
|
||||
- Tests intégration/E2E : nightly builds (15-30 min)
|
||||
- Load tests : avant chaque release majeure
|
||||
87
docs/adr/016-organisation-monorepo.md
Normal file
87
docs/adr/016-organisation-monorepo.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# ADR-016 : Organisation en Monorepo
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2025-01-24
|
||||
|
||||
## Contexte
|
||||
|
||||
RoadWave comprend plusieurs composants (backend Go, mobile React Native/Flutter, documentation, tests BDD). Il faut décider de l'organisation des repositories : monorepo unique, multirepo avec submodules, ou repos totalement séparés.
|
||||
|
||||
Les tests Gherkin existants décrivent des **fonctionnalités bout-en-bout** (front + mobile + back) et doivent rester cohérents entre tous les composants.
|
||||
|
||||
## Décision
|
||||
|
||||
**Monorepo unique** contenant backend, mobile, documentation et features Gherkin.
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Option | Cohérence | Complexité | Onboarding | Déploiements |
|
||||
|--------|-----------|------------|------------|--------------|
|
||||
| **Monorepo** | Excellente | Faible | Simple (1 clone) | Couplés |
|
||||
| Multirepo + submodules | Moyenne | Élevée | Complexe | Indépendants |
|
||||
| Multirepo séparés | Faible | Moyenne | Moyen | Indépendants |
|
||||
|
||||
## Justification
|
||||
|
||||
- **Source de vérité unique** : documentation et Gherkin partagés, pas de désynchronisation
|
||||
- **Versionning cohérent** : un tag Git = release complète front + back compatible
|
||||
- **Tests e2e simplifiés** : tout le code disponible localement pour tests intégrés
|
||||
- **Expérience développeur** : un seul `git clone`, historique unifié, changements cross-stack facilités
|
||||
- **Stade du projet** : début de projet où agilité et changements rapides sont critiques
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
roadwave/
|
||||
├── docs/ # Documentation commune (ADR, règles métier)
|
||||
├── features/ # Tests BDD Gherkin partagés
|
||||
│ ├── authentication/
|
||||
│ ├── navigation/
|
||||
│ └── ...
|
||||
├── backend/ # Application Go
|
||||
│ ├── cmd/
|
||||
│ ├── internal/
|
||||
│ ├── pkg/
|
||||
│ ├── migrations/
|
||||
│ ├── tests/
|
||||
│ │ └── bdd/ # Step definitions Go (API)
|
||||
│ └── go.mod
|
||||
├── mobile/ # Application React Native/Flutter
|
||||
│ ├── src/
|
||||
│ ├── tests/
|
||||
│ │ └── bdd/ # Step definitions mobile (UI)
|
||||
│ └── package.json / pubspec.yaml
|
||||
├── shared/ # Code partagé (types, contrats API)
|
||||
├── docker-compose.yml # Stack complète locale
|
||||
└── Makefile # Commandes communes
|
||||
```
|
||||
|
||||
## Gestion des tests Gherkin
|
||||
|
||||
**Principe** : Les fichiers `.feature` restent **partagés** dans `/features`, mais chaque projet implémente ses **step definitions**.
|
||||
|
||||
```
|
||||
features/authentication/inscription.feature → Scénario fonctionnel
|
||||
|
||||
backend/tests/bdd/inscription_steps.go → Teste l'API
|
||||
mobile/tests/bdd/inscription_steps.dart → Teste l'UI mobile
|
||||
```
|
||||
|
||||
Cela garantit que :
|
||||
- Les spécifications métier sont uniques et cohérentes
|
||||
- Chaque couche teste sa responsabilité
|
||||
- Les tests valident le contrat entre front et back
|
||||
|
||||
## Outillage
|
||||
|
||||
- **Turborepo** ou **Nx** : orchestration des builds/tests, cache intelligent
|
||||
- **Docker Compose** : environnement de dev local (PostgreSQL, Redis, backend, etc.)
|
||||
- **Make** : commandes communes (`make test`, `make build`, `make dev`)
|
||||
- **CI/CD** : GitHub Actions avec path filters (rebuild seulement ce qui change)
|
||||
|
||||
## Conséquences
|
||||
|
||||
- **Positif** : cohérence maximale, onboarding simplifié, changements cross-stack facilités
|
||||
- **Négatif** : repo plus volumineux, nécessite CI intelligente pour éviter builds lents
|
||||
- **Migration future** : si le monorepo devient ingérable (>100k LOC), split possible mais improbable à moyen terme
|
||||
- **Permissions** : tout le monde voit tout (transparence encouragée en phase startup)
|
||||
59
docs/adr/017-hebergement.md
Normal file
59
docs/adr/017-hebergement.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# ADR-017 : Hébergement
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2025-01-25
|
||||
|
||||
## Contexte
|
||||
|
||||
RoadWave nécessite une infrastructure cloud hébergée dans l'UE (conformité RGPD) avec PostgreSQL+PostGIS, Redis et stockage objet. L'objectif est de minimiser les coûts en phase MVP tout en gardant une évolutivité vers des services managés.
|
||||
|
||||
## Décision
|
||||
|
||||
**OVH VPS** avec services self-hosted via Docker Compose pour le MVP.
|
||||
Migration vers **Scaleway** (services managés) prévue en phase de croissance.
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Solution | Coût MVP/mois | Simplicité ops | PostGIS | Redis managé | RGPD |
|
||||
|----------|---------------|----------------|---------|--------------|------|
|
||||
| **OVH VPS** | ~14€ | Moyenne | Self-hosted | Self-hosted | Oui |
|
||||
| Scaleway managé | ~67€ | Élevée | Oui | Oui | Oui |
|
||||
| Clever Cloud | ~120€ | Très élevée | Oui | Oui | Oui |
|
||||
| Infomaniak | ~50€ | Élevée | Non | Non | Suisse |
|
||||
|
||||
## Justification
|
||||
|
||||
- **Économie** : 5x moins cher que les services managés (~14€ vs ~67€)
|
||||
- **Suffisant pour MVP** : VPS 4GB RAM supporte facilement 10-20K users
|
||||
- **Docker Compose** : Stack reproductible, backups automatisables
|
||||
- **RGPD** : Datacenters OVH en France
|
||||
- **Migration simple** : Passage vers Scaleway managé sans refonte
|
||||
|
||||
## Stack MVP
|
||||
|
||||
```
|
||||
OVH VPS Essential (12.50€/mois)
|
||||
├── Go + Fiber (app)
|
||||
├── PostgreSQL 16 + PostGIS 3.4 (Docker)
|
||||
├── Redis 7 (Docker)
|
||||
├── NGINX Cache (distribution HLS)
|
||||
└── Backups quotidiens (cron + pg_dump)
|
||||
|
||||
OVH Object Storage (~1.20€/100GB)
|
||||
└── Fichiers audio source
|
||||
```
|
||||
|
||||
## Évolution prévue
|
||||
|
||||
| Phase | Users | Infrastructure | Coût estimé |
|
||||
|-------|-------|----------------|-------------|
|
||||
| MVP | 0-20K | OVH VPS Essential | ~14€/mois |
|
||||
| Croissance | 20-100K | Scaleway + PostgreSQL managé | ~100€/mois |
|
||||
| Scale | 100K+ | Scaleway Kubernetes | ~500€/mois |
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Backups PostgreSQL à automatiser (cron + pg_dump)
|
||||
- Monitoring basique requis (Uptime Kuma ou alertes OVH)
|
||||
- Sécurité serveur à configurer (UFW, Fail2ban)
|
||||
- Migration vers services managés à planifier dès revenus stables
|
||||
62
docs/adr/018-service-emailing.md
Normal file
62
docs/adr/018-service-emailing.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# ADR-018 : Service d'Emailing Transactionnel
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2026-01-26
|
||||
|
||||
## Contexte
|
||||
|
||||
RoadWave nécessite un service d'envoi d'emails transactionnels pour notifications utilisateurs, modération (strikes, suspensions), authentification (Zitadel), conformité RGPD et communications créateurs.
|
||||
|
||||
**Volume estimé MVP** : 2000-5000 emails/mois.
|
||||
|
||||
**Contraintes** : Souveraineté préférée (France/UE), RGPD natif, coût maîtrisé, API simple pour Go, capacité SMS future (post-MVP).
|
||||
|
||||
## Décision
|
||||
|
||||
**Brevo (ex-Sendinblue)** comme service d'emailing transactionnel et SMS.
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Service | Localisation | Prix gratuit | Prix 100K emails/mois | SMS disponible |
|
||||
|---------|--------------|--------------|----------------------|----------------|
|
||||
| **Brevo** | 🇫🇷 France | 300 emails/jour (~9K/mois) | ~49€ | ✅ Oui |
|
||||
| Scaleway TEM | 🇫🇷 France | 300 emails/mois | ~25€ | ❌ Non |
|
||||
| Mailjet | 🇫🇷 France | 200 emails/jour (~6K/mois) | ~50-100€ | ❌ Non |
|
||||
| Postal | 🏠 Self-hosted | Illimité (coût VPS) | ~20-50€ | ❌ Non |
|
||||
|
||||
## Justification
|
||||
|
||||
- **Plan gratuit généreux** : 300 emails/jour (9000/mois) vs 300/mois chez Scaleway → coût 0 en MVP
|
||||
- **SMS natif** : Vérification anti-spam post-MVP (emails temporaires)
|
||||
- **Souverain français** : Hébergé France/UE, RGPD natif
|
||||
- **CRM intégré** : Gestion contacts, segmentation pour newsletters futures
|
||||
- **Simplicité** : API REST standard, SDK disponibles, documentation complète
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Backend Go → Brevo API REST → Utilisateurs
|
||||
├── Emails transactionnels
|
||||
├── Templates HTML
|
||||
├── Webhooks (bounces, opens)
|
||||
└── [Post-MVP] SMS
|
||||
```
|
||||
|
||||
## Estimation coûts
|
||||
|
||||
| Phase | Utilisateurs | Emails/mois | Coût Brevo |
|
||||
|-------|--------------|-------------|------------|
|
||||
| MVP | 0-10K | ~5K/jour | **Gratuit** (< 300/jour) |
|
||||
| Growth | 10K-50K | ~10K/mois | Gratuit ou 19€/mois |
|
||||
| Scale | 50K-100K | ~100K/mois | 49€/mois |
|
||||
|
||||
**SMS** (post-MVP) : ~0.04€/SMS France, soit ~400€/mois pour 10K inscriptions/mois.
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Coût 0 en MVP (9000 emails/mois gratuits)
|
||||
- SMS intégré pour vérification anti-spam (post-MVP)
|
||||
- API REST simple, pas de SDK Go officiel
|
||||
- Limites quotidiennes strictes (300/jour en gratuit)
|
||||
- Migration possible vers Scaleway TEM si volume >100K emails/mois (coût optimisé)
|
||||
- Configuration DNS requise (SPF, DKIM, DMARC)
|
||||
144
docs/adr/019-notifications-geolocalisees.md
Normal file
144
docs/adr/019-notifications-geolocalisees.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# ADR-019 : Architecture des Notifications Géolocalisées
|
||||
|
||||
**Statut** : Accepté
|
||||
**Date** : 2026-01-28
|
||||
**Supersède** : Résout l'incohérence identifiée entre ADR-002 et Règle Métier 05 (Mode Piéton)
|
||||
|
||||
## Contexte
|
||||
|
||||
Le mode piéton exige des notifications push en temps réel lorsque l'utilisateur approche d'un point d'intérêt (rayon de 200m), **même si l'application est fermée ou en arrière-plan**.
|
||||
|
||||
ADR-002 spécifie HLS pour tout le streaming audio, mais HLS est un protocole unidirectionnel (serveur → client) qui ne permet pas au serveur d'envoyer des notifications push vers un client inactif.
|
||||
|
||||
## Décision
|
||||
|
||||
Architecture hybride en **2 phases** :
|
||||
|
||||
### Phase 1 (MVP) : WebSocket + Firebase Cloud Messaging
|
||||
|
||||
```
|
||||
[App Mobile] → [WebSocket] → [Backend Go]
|
||||
↓
|
||||
[PostGIS Worker]
|
||||
↓
|
||||
[Firebase FCM / APNS]
|
||||
↓
|
||||
[Push Notification]
|
||||
```
|
||||
|
||||
**Fonctionnement** :
|
||||
1. L'utilisateur ouvre l'app → connexion WebSocket établie
|
||||
2. L'app envoie sa position GPS toutes les 30s via WebSocket
|
||||
3. Un worker backend (goroutine) interroge PostGIS toutes les 30s :
|
||||
```sql
|
||||
SELECT poi.*, users.fcm_token
|
||||
FROM points_of_interest poi
|
||||
JOIN user_locations users ON ST_DWithin(
|
||||
poi.geom,
|
||||
users.last_position,
|
||||
200 -- rayon en mètres
|
||||
)
|
||||
WHERE users.notifications_enabled = true
|
||||
AND users.last_update > NOW() - INTERVAL '5 minutes'
|
||||
```
|
||||
4. Si proximité détectée → envoi de push notification via Firebase (Android) ou APNS (iOS)
|
||||
5. Utilisateur clique → app s'ouvre → HLS démarre l'audio (ADR-002)
|
||||
|
||||
**Limitations MVP** :
|
||||
- Fonctionne uniquement si l'utilisateur a envoyé sa position < 5 minutes
|
||||
- En voiture rapide (>80 km/h), possible de "manquer" un POI si position pas mise à jour
|
||||
|
||||
### Phase 2 (Post-MVP) : Ajout du Geofencing Local
|
||||
|
||||
```
|
||||
[Mode Connecté] → WebSocket + Push serveur (Phase 1)
|
||||
[Mode Offline] → Geofencing natif iOS/Android
|
||||
[Mode Économie] → Geofencing natif (batterie < 20%)
|
||||
```
|
||||
|
||||
**Fonctionnement additionnel** :
|
||||
1. Quand l'utilisateur télécharge du contenu pour mode offline → synchronisation des POI proches (rayon 10 km)
|
||||
2. Configuration de **geofences locales** sur iOS/Android (limite : 20 sur iOS, 100 sur Android)
|
||||
3. Sélection intelligente des 20 POI les plus pertinents selon les jauges d'intérêt
|
||||
4. Système d'exploitation surveille les geofences même app fermée
|
||||
5. Entrée dans geofence → notification locale (pas de serveur)
|
||||
|
||||
## Alternatives considérées
|
||||
|
||||
| Option | Fonctionne offline | Batterie | Complexité | Limite POI | Précision |
|
||||
|--------|-------------------|----------|------------|------------|-----------|
|
||||
| **WebSocket + FCM (Phase 1)** | ❌ Non | ⭐ Optimale | ⭐ Faible | ∞ | ⭐⭐ Bonne |
|
||||
| Geofencing local seul | ⭐ Oui | ⚠️ Élevée | ⚠️ Moyenne | 20 (iOS) | ⭐⭐⭐ Excellente |
|
||||
| Polling GPS continu | ⭐ Oui | ❌ Critique | ⭐ Faible | ∞ | ⭐⭐⭐ Excellente |
|
||||
| **Hybride (Phase 1+2)** | ⭐ Oui | ⭐ Adaptative | ⚠️ Moyenne | ∞/20 | ⭐⭐⭐ Excellente |
|
||||
|
||||
## Justification
|
||||
|
||||
### Pourquoi WebSocket et pas HTTP long-polling ?
|
||||
|
||||
- **Efficacité** : 1 connexion TCP vs multiples requêtes HTTP
|
||||
- **Batterie** : Connexion persistante optimisée par l'OS mobile
|
||||
- **Bi-directionnel** : Backend peut envoyer des mises à jour instantanées (ex: "nouveau POI créé par un créateur que tu suis")
|
||||
|
||||
### Pourquoi Firebase FCM et pas implémentation custom ?
|
||||
|
||||
- **Gratuit** : 10M notifications/mois (largement suffisant jusqu'à 100K utilisateurs)
|
||||
- **Fiabilité** : Infrastructure Google avec 99.95% uptime
|
||||
- **Batterie** : Utilise les mécanismes système (Google Play Services)
|
||||
- **Cross-platform** : API unifiée iOS/Android
|
||||
|
||||
### Pourquoi limiter le geofencing local à Phase 2 ?
|
||||
|
||||
- **Complexité** : Permissions "Always Location" difficiles à obtenir (taux d'acceptation ~30%)
|
||||
- **ROI** : 80% des utilisateurs auront un réseau mobile disponible
|
||||
- **Priorité** : Livrer le MVP rapidement avec la solution serveur
|
||||
|
||||
## Conséquences
|
||||
|
||||
### Positives
|
||||
|
||||
- ✅ Notifications temps réel en mode piéton (< 1 minute de latence)
|
||||
- ✅ Fonctionne avec HLS pour l'audio (pas de conflit avec ADR-002)
|
||||
- ✅ Scalable : Worker backend peut gérer 10K utilisateurs/seconde avec PostGIS indexé
|
||||
- ✅ Mode offline disponible en Phase 2 sans refonte
|
||||
|
||||
### Négatives
|
||||
|
||||
- ❌ Dépendance à Firebase (vendor lock-in) - mitigée par l'utilisation de l'interface FCM standard
|
||||
- ❌ WebSocket nécessite maintien de connexion (charge serveur +10-20%)
|
||||
- ❌ Mode offline non disponible au MVP (déception possible des early adopters)
|
||||
|
||||
### Impact sur les autres ADR
|
||||
|
||||
- **ADR-002 (Streaming)** : Aucun conflit - HLS reste pour l'audio
|
||||
- **ADR-005 (Base de données)** : Ajouter index PostGIS `GIST (geom)` sur `points_of_interest`
|
||||
- **ADR-012 (Architecture Backend)** : Ajouter un module `geofencing` avec worker dédié
|
||||
- **ADR-014 (Frontend Mobile)** : Intégrer `firebase_messaging` (Flutter) et gérer permissions
|
||||
|
||||
## Métriques de Succès
|
||||
|
||||
- Latence notification < 60s après entrée dans rayon 200m
|
||||
- Taux de livraison > 95% (hors utilisateurs avec notifications désactivées)
|
||||
- Consommation batterie < 5% / heure en mode piéton
|
||||
- Coût serveur < 0.01€ / utilisateur / mois
|
||||
|
||||
## Migration et Rollout
|
||||
|
||||
### Phase 1 (MVP - Sprint 3-4)
|
||||
1. Backend : Implémenter WebSocket endpoint `/ws/location`
|
||||
2. Backend : Worker PostGIS avec requête ST_DWithin
|
||||
3. Mobile : Intégrer Firebase SDK + gestion FCM token
|
||||
4. Test : Validation en conditions réelles (Paris, 10 testeurs)
|
||||
|
||||
### Phase 2 (Post-MVP - Sprint 8-10)
|
||||
1. Mobile : Implémenter geofencing avec `flutter_background_geolocation`
|
||||
2. Backend : API `/sync/nearby-pois?lat=X&lon=Y&radius=10km`
|
||||
3. Mobile : Algorithme de sélection des 20 POI prioritaires
|
||||
4. Test : Validation mode avion (offline complet)
|
||||
|
||||
## Références
|
||||
|
||||
- [Firebase Cloud Messaging Documentation](https://firebase.google.com/docs/cloud-messaging)
|
||||
- [PostGIS ST_DWithin Performance](https://postgis.net/docs/ST_DWithin.html)
|
||||
- [iOS Geofencing Best Practices](https://developer.apple.com/documentation/corelocation/monitoring_the_user_s_proximity_to_geographic_regions)
|
||||
- Règle Métier 05 : Section 5.1.2 (Mode Piéton, lignes 86-120)
|
||||
Reference in New Issue
Block a user