Initial commit

This commit is contained in:
jpgiannetti
2026-01-31 11:45:11 +01:00
commit f99fb3c614
166 changed files with 115155 additions and 0 deletions

View 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

View 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

View 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
View 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

View 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)

View 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
View 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

View 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.

View 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)

View 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

View 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)

View 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)

View 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

View 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

View 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

View 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)

View 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

View 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)

View 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)