refactor(docs): réorganiser ADR et règles métier pour clarté
**Changements majeurs** : 1. **Suppression ADR-010 (Commandes volant et likes)** : - Contenu consolidé dans Règle 05 (section 5.3) - Raison : ADR-010 était du métier déguisé en architecture - Section "Implémentation Technique" ajoutée à Règle 05 - Pattern correct (addition) vs incorrect (multiplication) 2. **Déplacement ADR-011 → Compliance** : - `docs/adr/011-conformite-stores.md` → `docs/compliance/stores-submission.md` - Raison : Nature opérationnelle/légale, pas architecture technique - Nouveau dossier `/docs/compliance/` créé 3. **Renumérotation ADR (010-022)** : - Combler les trous de numérotation (010 et 011) - ADR-012→010, ADR-013→011, ..., ADR-024→022 - 22 ADR numérotés en continu (001-022) - Historique Git préservé (git mv) 4. **Mise à jour références** : - Règle 03 : ADR-010 → Règle 05 (section 5.3) - Règle 09 : ADR-010 → Règle 05 (section 5.3) - INCONSISTENCIES-ANALYSIS.md : toutes références mises à jour - Incohérence #15 annulée (faux problème : modes séparés) **Résultat** : - ✅ Séparation claire ADR (technique) vs Règles métier (fonctionnel) - ✅ Documentation compliance séparée - ✅ Numérotation ADR continue sans trous - ✅ Single Source of Truth (pas de redondance) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
246
docs/adr/017-notifications-geolocalisees.md
Normal file
246
docs/adr/017-notifications-geolocalisees.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# ADR-017 : 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
|
||||
|
||||
### Architecture de délivrance (serveur vs local)
|
||||
|
||||
| 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 |
|
||||
|
||||
### Fournisseurs de push notifications
|
||||
|
||||
| Provider | Fiabilité | Coût MVP | Coût 100K users | Self-hosted | Vendor lock-in | Verdict |
|
||||
|----------|-----------|----------|-----------------|-------------|----------------|---------|
|
||||
| **Firebase (choix)** | 99.95% | **0€** | **0€** | ❌ Non | 🔴 Fort (Google) | ✅ Optimal MVP |
|
||||
| OneSignal | 99.95% | 0€ | 500€/mois | ❌ Non | 🔴 Fort | ❌ Plus cher |
|
||||
| Pusher Beams | 99.9% | 0€ | 300€/mois | ❌ Non | 🔴 Fort | ❌ Niche |
|
||||
| Custom WS + APNS/FCM | Votre charge | 5€ | 100€+ | ✅ Oui | 🟢 Aucun | ⚠️ Complexe |
|
||||
| Novu (open source) | 99.9% | 15€ | 50€ | ✅ Oui | 🟢 Aucun | 🟡 Phase 2 |
|
||||
| Brevo API | 99.9% | 0€ | 49€ | ✅ Oui | 🟢 Aucun | ❌ Email seulement |
|
||||
|
||||
## 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
|
||||
|
||||
### Incohérence acceptée : Firebase vs self-hosted (ADR-008, ADR-015)
|
||||
|
||||
**Problème** : RoadWave promeut 100% self-hosted + souveraineté française, mais Firebase = dépendance Google Cloud.
|
||||
|
||||
**Réalité technique** : Notifications natives requièrent obligatoirement Google/Apple
|
||||
- **APNS (Apple)** : Seul protocole pour notifications iOS → dépendance Apple inévitable
|
||||
- **FCM (Google)** : Meilleur protocole Android (vs Huawei HMS, Samsung)
|
||||
|
||||
**Alternatives analysées** :
|
||||
1. **Custom WebSocket** (self-hosted) :
|
||||
- ✅ Zéro dépendance externe
|
||||
- ❌ 150+ heures dev (2-3 sprints)
|
||||
- ❌ Maintien de la reliability en-house
|
||||
- ❌ Toujours besoin d'appeler APNS/FCM de toute façon
|
||||
|
||||
2. **Novu (open source self-hosted)** :
|
||||
- ✅ Self-hostable
|
||||
- ❌ Jeune (moins mature)
|
||||
- ❌ Toujours wrapper autour APNS/FCM
|
||||
- ❌ Overhead sans gain réel
|
||||
|
||||
3. **OneSignal / Pusher** :
|
||||
- ❌ Même vendor lock-in que Firebase
|
||||
- ❌ Plus cher (500€+/mois @ 100K users)
|
||||
|
||||
**Décision pragmatique** :
|
||||
- Firebase pour MVP : gratuit + fiabilité + time-to-market
|
||||
- **Mitigation vendor lock-in** : Utiliser abstraction layer (`NotificationProvider` interface)
|
||||
- **Exit path documenté** : Migration vers custom solution < 1 sprint si besoin futur
|
||||
- **Probabilité de changement** : Très basse (MVP gratuit, pas d'incitation financière)
|
||||
|
||||
### 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
|
||||
- ✅ Coût zéro jusqu'à millions de notifications (gratuit MVP + croissance)
|
||||
- ✅ Géolocalisation natif iOS/Android optimisé (moins de batterie)
|
||||
|
||||
### Négatives
|
||||
|
||||
- ⚠️ **Dépendance Google (Firebase)** : Contradictoire avec ADR-008 (self-hosted) + ADR-015 (souveraineté FR)
|
||||
- Mitigé par abstraction layer (`NotificationProvider` interface) → swap facile si besoin
|
||||
- Exit path documenté pour migration custom (< 1 sprint)
|
||||
- ⚠️ **Données utilisateur chez Google** : Tokens FCM, timestamps notifications
|
||||
- Risque RGPD : Nécessite DPA Google valide
|
||||
- À consulter avec DPO avant déploiement production
|
||||
- ❌ 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-010 (Architecture Backend)** : Ajouter un module `geofencing` avec worker dédié
|
||||
- **ADR-010 (Frontend Mobile)** : Intégrer `firebase_messaging` (Flutter) et gérer permissions
|
||||
|
||||
## Abstraction Layer (Mitigation Vendor Lock-in)
|
||||
|
||||
Pour minimiser le coût de changement future, implémenter une interface abstraite :
|
||||
|
||||
```go
|
||||
// backend/internal/notification/provider.go
|
||||
type NotificationProvider interface {
|
||||
SendNotification(ctx context.Context, token, title, body, deepLink string) error
|
||||
UpdateToken(ctx context.Context, userID, newToken string) error
|
||||
}
|
||||
|
||||
// backend/internal/notification/firebase_provider.go
|
||||
type FirebaseProvider struct {
|
||||
client *messaging.Client
|
||||
}
|
||||
|
||||
func (p *FirebaseProvider) SendNotification(ctx context.Context, token, title, body, deepLink string) error {
|
||||
message := &messaging.Message{
|
||||
Notification: &messaging.Notification{
|
||||
Title: title,
|
||||
Body: body,
|
||||
},
|
||||
Data: map[string]string{
|
||||
"deepLink": deepLink,
|
||||
},
|
||||
Token: token,
|
||||
}
|
||||
_, err := p.client.Send(ctx, message)
|
||||
return err
|
||||
}
|
||||
|
||||
// backend/internal/notification/service.go
|
||||
type NotificationService struct {
|
||||
provider NotificationProvider // ← Interface, pas concrète
|
||||
repo NotificationRepository
|
||||
}
|
||||
```
|
||||
|
||||
**Bénéfice** : Swap Firebase → Custom/Novu sans changer business logic.
|
||||
|
||||
```go
|
||||
// Futur : switch facilement
|
||||
var provider NotificationProvider
|
||||
|
||||
if config.Provider == "firebase" {
|
||||
provider = &FirebaseProvider{...}
|
||||
} else if config.Provider == "custom" {
|
||||
provider = &CustomProvider{...}
|
||||
}
|
||||
```
|
||||
|
||||
## 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