refactor(docs): réorganiser la documentation selon principes DDD
Réorganise la documentation du projet selon les principes du Domain-Driven Design (DDD) pour améliorer la cohésion, la maintenabilité et l'alignement avec l'architecture modulaire du backend. **Structure cible:** ``` docs/domains/ ├── README.md (Context Map) ├── _shared/ (Core Domain) ├── recommendation/ (Supporting Subdomain) ├── content/ (Supporting Subdomain) ├── moderation/ (Supporting Subdomain) ├── advertising/ (Generic Subdomain) ├── premium/ (Generic Subdomain) └── monetization/ (Generic Subdomain) ``` **Changements effectués:** Phase 1: Création de l'arborescence des 7 bounded contexts Phase 2: Déplacement des règles métier (01-19) vers domains/*/rules/ Phase 3: Déplacement des diagrammes d'entités vers domains/*/entities/ Phase 4: Déplacement des diagrammes flux/états/séquences vers domains/*/ Phase 5: Création des README.md pour chaque domaine Phase 6: Déplacement des features Gherkin vers domains/*/features/ Phase 7: Création du Context Map (domains/README.md) Phase 8: Mise à jour de mkdocs.yml pour la nouvelle navigation Phase 9: Correction automatique des liens internes (script fix-markdown-links.sh) Phase 10: Nettoyage de l'ancienne structure (regles-metier/, diagrammes/, features/) **Configuration des tests:** - Makefile: godog run docs/domains/*/features/ - scripts/generate-bdd-docs.py: features_dir → docs/domains **Avantages:** ✅ Cohésion forte: toute la doc d'un domaine au même endroit ✅ Couplage faible: domaines indépendants, dépendances explicites ✅ Navigabilité améliorée: README par domaine = entrée claire ✅ Alignement code/docs: miroir de backend/internal/ ✅ Onboarding facilité: exploration domaine par domaine ✅ Tests BDD intégrés: features au plus près des règles métier Voir docs/REFACTOR-DDD.md pour le plan complet.
This commit is contained in:
314
docs/domains/content/features/navigation/file-attente.feature
Normal file
314
docs/domains/content/features/navigation/file-attente.feature
Normal file
@@ -0,0 +1,314 @@
|
||||
# language: fr
|
||||
Fonctionnalité: API - File d'attente et pré-calcul des contenus
|
||||
En tant qu'API backend
|
||||
Je veux pré-calculer et gérer la file d'attente de contenus
|
||||
Afin d'assurer une navigation fluide sans latence
|
||||
|
||||
Contexte:
|
||||
Étant donné que l'API RoadWave est disponible
|
||||
Et que Redis est accessible
|
||||
Et que PostgreSQL avec PostGIS est accessible
|
||||
Et qu'un utilisateur "user123" existe avec token JWT valide
|
||||
|
||||
# Pré-calcul initial
|
||||
|
||||
Scénario: API pré-calcule 5 contenus au démarrage de session
|
||||
Étant donné que l'utilisateur "user123" démarre une session
|
||||
Et qu'il est situé à Paris (48.8566, 2.3522)
|
||||
Et qu'il est en mode voiture (vitesse ≥ 5 km/h)
|
||||
Quand je POST /api/v1/queue/initialize
|
||||
"""json
|
||||
{
|
||||
"user_id": "user123",
|
||||
"latitude": 48.8566,
|
||||
"longitude": 2.3522,
|
||||
"mode": "voiture"
|
||||
}
|
||||
"""
|
||||
Alors le statut de réponse est 201
|
||||
Et la réponse contient:
|
||||
"""json
|
||||
{
|
||||
"queue_size": 5,
|
||||
"contents": [
|
||||
{"id": "content1", "title": "...", "position": 1},
|
||||
{"id": "content2", "title": "...", "position": 2},
|
||||
{"id": "content3", "title": "...", "position": 3},
|
||||
{"id": "content4", "title": "...", "position": 4},
|
||||
{"id": "content5", "title": "...", "position": 5}
|
||||
]
|
||||
}
|
||||
"""
|
||||
Et en Redis, la clé "user:user123:queue" contient 5 contenus
|
||||
Et les métadonnées incluent:
|
||||
| champ | valeur |
|
||||
| last_lat | 48.8566 |
|
||||
| last_lon | 2.3522 |
|
||||
| mode | voiture |
|
||||
| computed_at | (timestamp actuel) |
|
||||
Et le TTL est de 900 secondes (15 minutes)
|
||||
|
||||
Scénario: API GET retourne la file d'attente en cache
|
||||
Étant donné qu'une file de 5 contenus existe en cache Redis pour "user123"
|
||||
Quand je GET /api/v1/queue
|
||||
Alors le statut de réponse est 200
|
||||
Et la réponse contient les 5 contenus pré-calculés
|
||||
Et la latence de réponse est < 50ms (lecture Redis)
|
||||
|
||||
Scénario: API retire un contenu de la file après lecture
|
||||
Étant donné qu'une file de 5 contenus [C1, C2, C3, C4, C5] existe pour "user123"
|
||||
Quand je POST /api/v1/queue/consume
|
||||
"""json
|
||||
{
|
||||
"user_id": "user123",
|
||||
"content_id": "C1"
|
||||
}
|
||||
"""
|
||||
Alors le statut de réponse est 200
|
||||
Et la file d'attente devient [C2, C3, C4, C5]
|
||||
Et la taille de la file est 4
|
||||
|
||||
# Recalcul automatique
|
||||
|
||||
Scénario: API recalcule après déplacement >10km
|
||||
Étant donné qu'une file a été calculée à Paris (48.8566, 2.3522)
|
||||
Et que l'utilisateur se déplace à Versailles (48.8049, 2.1204) soit 12km
|
||||
Quand je POST /api/v1/queue/update-location
|
||||
"""json
|
||||
{
|
||||
"user_id": "user123",
|
||||
"latitude": 48.8049,
|
||||
"longitude": 2.1204
|
||||
}
|
||||
"""
|
||||
Alors le statut de réponse est 200
|
||||
Et la réponse contient:
|
||||
"""json
|
||||
{
|
||||
"queue_invalidated": true,
|
||||
"reason": "distance_threshold_exceeded",
|
||||
"distance_km": 12.1,
|
||||
"new_queue_size": 5
|
||||
}
|
||||
"""
|
||||
Et la nouvelle file est basée sur la position Versailles
|
||||
Et l'ancienne file a été supprimée de Redis
|
||||
|
||||
Scénario: API ne recalcule pas si déplacement ≤10km
|
||||
Étant donné qu'une file a été calculée à Paris (48.8566, 2.3522)
|
||||
Et que l'utilisateur se déplace de 8 km
|
||||
Quand je POST /api/v1/queue/update-location
|
||||
"""json
|
||||
{
|
||||
"user_id": "user123",
|
||||
"latitude": 48.8500,
|
||||
"longitude": 2.3600
|
||||
}
|
||||
"""
|
||||
Alors le statut de réponse est 200
|
||||
Et la réponse contient:
|
||||
"""json
|
||||
{
|
||||
"queue_invalidated": false,
|
||||
"distance_km": 8.2,
|
||||
"threshold": 10
|
||||
}
|
||||
"""
|
||||
Et la file en cache reste inchangée
|
||||
|
||||
Scénario: API recalcule après 10 minutes
|
||||
Étant donné qu'une file a été calculée à 10:00:00
|
||||
Et que l'heure actuelle est 10:10:01
|
||||
Quand je GET /api/v1/queue
|
||||
Alors le statut de réponse est 200
|
||||
Et la réponse contient:
|
||||
"""json
|
||||
{
|
||||
"queue_invalidated": true,
|
||||
"reason": "time_threshold_exceeded",
|
||||
"elapsed_minutes": 10,
|
||||
"new_queue_size": 5
|
||||
}
|
||||
"""
|
||||
Et une nouvelle file de 5 contenus est recalculée
|
||||
Et le timestamp "computed_at" est mis à jour
|
||||
|
||||
Scénario: API recalcule quand il reste <3 contenus
|
||||
Étant donné qu'il reste 3 contenus [C3, C4, C5] dans la file
|
||||
Quand je POST /api/v1/queue/consume (consomme C3)
|
||||
Alors le statut de réponse est 200
|
||||
Et la file devient [C4, C5]
|
||||
Et un recalcul asynchrone est déclenché
|
||||
Et 3 nouveaux contenus [C6, C7, C8] sont ajoutés
|
||||
Et la file finale est [C4, C5, C6, C7, C8]
|
||||
|
||||
# Invalidation immédiate
|
||||
|
||||
Scénario: API invalide après modification préférences utilisateur
|
||||
Étant donné qu'une file de 5 contenus existe pour "user123"
|
||||
Et que l'utilisateur est en mode piéton (vitesse < 5 km/h)
|
||||
Quand je PUT /api/v1/users/user123/preferences
|
||||
"""json
|
||||
{
|
||||
"geo_radius": 20,
|
||||
"discovery_factor": 0.7,
|
||||
"political_content": false
|
||||
}
|
||||
"""
|
||||
Alors le statut de réponse est 200
|
||||
Et la file d'attente est invalidée immédiatement
|
||||
Et une nouvelle file est recalculée avec les nouvelles préférences
|
||||
Et en Redis, l'ancienne file a été supprimée
|
||||
|
||||
Scénario: API refuse modification préférences si vitesse >10 km/h
|
||||
Étant donné que l'utilisateur roule à 50 km/h
|
||||
Quand je PUT /api/v1/users/user123/preferences
|
||||
"""json
|
||||
{
|
||||
"geo_radius": 30
|
||||
}
|
||||
"""
|
||||
Alors le statut de réponse est 403
|
||||
Et la réponse contient:
|
||||
"""json
|
||||
{
|
||||
"error": "MODIFICATION_BLOCKED_WHILE_DRIVING",
|
||||
"message": "Modification des préférences interdite en conduite (vitesse > 10 km/h)",
|
||||
"current_speed_kmh": 50
|
||||
}
|
||||
"""
|
||||
Et les préférences ne sont pas modifiées
|
||||
|
||||
Scénario: API invalide après démarrage live d'un créateur suivi
|
||||
Étant donné que l'utilisateur "user123" suit le créateur "creator456"
|
||||
Et qu'une file de 5 contenus existe
|
||||
Et que l'utilisateur est dans la zone du créateur
|
||||
Quand le créateur "creator456" démarre un live
|
||||
Alors une notification push est envoyée à "user123"
|
||||
Et la file d'attente est recalculée
|
||||
Et le contenu live est inséré en tête de file
|
||||
Et la nouvelle file commence par le live
|
||||
|
||||
# Métadonnées et persistence
|
||||
|
||||
Scénario: API stocke métadonnées complètes en Redis
|
||||
Étant donné qu'une file est calculée à 10:30:00
|
||||
Quand je consulte Redis avec la clé "user:user123:queue"
|
||||
Alors la structure est:
|
||||
"""json
|
||||
{
|
||||
"contents": [
|
||||
{"id": "C1", "position": 1},
|
||||
{"id": "C2", "position": 2},
|
||||
{"id": "C3", "position": 3},
|
||||
{"id": "C4", "position": 4},
|
||||
{"id": "C5", "position": 5}
|
||||
],
|
||||
"metadata": {
|
||||
"last_lat": 48.8566,
|
||||
"last_lon": 2.3522,
|
||||
"computed_at": "2026-02-02T10:30:00Z",
|
||||
"mode": "voiture"
|
||||
}
|
||||
}
|
||||
"""
|
||||
Et le TTL est exactement 900 secondes
|
||||
|
||||
Scénario: API calcule distance avec PostGIS
|
||||
Étant donné que l'ancienne position est Paris (48.8566, 2.3522)
|
||||
Et que la nouvelle position est Versailles (48.8049, 2.1204)
|
||||
Quand l'API calcule la distance
|
||||
Alors la requête SQL utilise:
|
||||
"""sql
|
||||
SELECT ST_Distance(
|
||||
ST_MakePoint(2.3522, 48.8566)::geography,
|
||||
ST_MakePoint(2.1204, 48.8049)::geography
|
||||
) / 1000 AS distance_km
|
||||
"""
|
||||
Et le résultat est 12.1 km
|
||||
|
||||
# Gestion erreurs
|
||||
|
||||
Scénario: API gère échec Redis gracieusement
|
||||
Étant donné que Redis est indisponible
|
||||
Quand je GET /api/v1/queue
|
||||
Alors le statut de réponse est 503
|
||||
Et la réponse contient:
|
||||
"""json
|
||||
{
|
||||
"error": "CACHE_UNAVAILABLE",
|
||||
"message": "Service de cache temporairement indisponible",
|
||||
"fallback": "Calcul direct sans cache"
|
||||
}
|
||||
"""
|
||||
Et une nouvelle file est calculée directement depuis PostgreSQL
|
||||
|
||||
Scénario: API gère aucun contenu disponible
|
||||
Étant donné qu'aucun contenu n'existe dans la zone de l'utilisateur
|
||||
Quand je POST /api/v1/queue/initialize
|
||||
Alors le statut de réponse est 200
|
||||
Et la réponse contient:
|
||||
"""json
|
||||
{
|
||||
"queue_size": 0,
|
||||
"contents": [],
|
||||
"message": "Aucun contenu disponible dans cette zone",
|
||||
"suggested_action": "expand_radius"
|
||||
}
|
||||
"""
|
||||
|
||||
Scénario: API élargit la zone de recherche si aucun contenu
|
||||
Étant donné qu'aucun contenu n'existe dans un rayon de 20km
|
||||
Quand je POST /api/v1/queue/expand-radius
|
||||
"""json
|
||||
{
|
||||
"user_id": "user123",
|
||||
"additional_radius_km": 50
|
||||
}
|
||||
"""
|
||||
Alors le statut de réponse est 200
|
||||
Et la recherche utilise un rayon de 70km (20 + 50)
|
||||
Et une nouvelle file est calculée avec ce rayon
|
||||
|
||||
# Performance
|
||||
|
||||
Scénario: API répond en <100ms pour lecture cache
|
||||
Étant donné qu'une file existe en Redis
|
||||
Quand je GET /api/v1/queue à 10:30:00.000
|
||||
Alors la réponse est reçue à 10:30:00.050 (50ms)
|
||||
Et la latence est < 100ms
|
||||
|
||||
Scénario: API recalcule en arrière-plan sans bloquer
|
||||
Étant donné qu'il reste 2 contenus dans la file
|
||||
Quand je POST /api/v1/queue/consume
|
||||
Alors le statut de réponse est 200 (immédiat)
|
||||
Et la réponse est retournée en < 100ms
|
||||
Et le recalcul asynchrone démarre en parallèle
|
||||
Et le client ne perçoit aucune latence
|
||||
|
||||
Plan du Scénario: Conditions de recalcul selon distance
|
||||
Étant donné qu'une file a été calculée à une position donnée
|
||||
Quand l'utilisateur se déplace de <distance> km
|
||||
Alors la file est <action>
|
||||
|
||||
Exemples:
|
||||
| distance | action |
|
||||
| 5 | conservée |
|
||||
| 9.9 | conservée |
|
||||
| 10.0 | conservée |
|
||||
| 10.1 | invalidée et recalculée |
|
||||
| 15 | invalidée et recalculée |
|
||||
| 50 | invalidée et recalculée |
|
||||
|
||||
Plan du Scénario: Conditions de recalcul selon temps écoulé
|
||||
Étant donné qu'une file a été calculée il y a <temps> minutes
|
||||
Quand je consulte la file
|
||||
Alors la file est <action>
|
||||
|
||||
Exemples:
|
||||
| temps | action |
|
||||
| 5 | conservée |
|
||||
| 9 | conservée |
|
||||
| 10 | invalidée et recalculée |
|
||||
| 15 | invalidée et recalculée |
|
||||
| 60 | invalidée et recalculée |
|
||||
Reference in New Issue
Block a user