Implémentation fonctionnelle de Voyanta en architecture microservices, conforme au cahier des charges et au plan d'architecture "Phase 2" (User Service / Itinerary Service / Recommendation Service derrière un API Gateway, communication REST synchrone + événements asynchrones via RabbitMQ), entièrement dockerisée.
┌─────────────────┐
│ Frontend │ (React, servi par Nginx)
│ :5173 │
└────────┬─────────┘
│
┌────────▼─────────┐
│ API Gateway │ (Nginx) :8080
└───┬───────┬───┬───┘
┌──────────────┘ │ └──────────────┐
▼ ▼ ▼
┌────────────────┐ ┌───────────────────┐ ┌─────────────────────┐
│ User Service │ │ Itinerary Service │ │ Recommendation Svc │
│ :5001 │ │ :5002 │ │ :5003 │
│ - auth (JWT) │ │ - destinations │ │ - lit User Service │
│ - profils │ │ - distances/coûts │ │ et Itinerary Svc │
│ - favoris │ │ - itinéraires │ │ (REST synchrone) │
└────────┬─────────┘ └─────────┬──────────┘ │ - consomme les │
│ │ │ événements │
┌───────▼───────┐ ┌────────▼────────┐ │ RabbitMQ │
│ user-db │ │ itinerary-db │ │ (asynchrone) │
│ (PostgreSQL) │ │ (PostgreSQL/PostGIS) │ └──────────┬───────────┘
└────────────────┘ └────────┬────────┘ │
│ ┌─────────────────┘
▼ ▼
┌────────────┐ ┌───────┐
│ RabbitMQ │ │ Redis │
└────────────┘ └───────┘
Prérequis : Docker et Docker Compose.
cp .env.example .env # puis changez JWT_SECRET_KEY
docker compose up --buildUne fois les conteneurs démarrés (comptez 30 à 60 secondes le premier lancement, le temps que les bases de données soient prêtes et que les destinations de démonstration soient chargées) :
| Service | URL |
|---|---|
| Frontend | http://localhost:5173 |
| API Gateway | http://localhost:8080 |
| Console RabbitMQ | http://localhost:15672 (guest / guest) |
Testez rapidement que tout communique :
curl http://localhost:8080/api/v1/destinations | head -c 300
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"test@voyanta.cm","password":"motdepasse123","first_name":"Aline"}'Possède exclusivement les données utilisateurs (voyanta_users, base
PostgreSQL dédiée). Gère l'inscription, la connexion (JWT via Argon2),
le profil, les préférences de voyage et les favoris.
Endpoints principaux :
POST /api/v1/auth/register
POST /api/v1/auth/login
GET /api/v1/auth/me
PATCH /api/v1/users/me
GET /api/v1/users/me/favorites
POST /api/v1/users/me/favorites/<destination_id>
DELETE /api/v1/users/me/favorites/<destination_id>
GET /api/v1/users/<id>/public-profile # interne, appelé par le Recommendation Service
Possède les destinations, les tarifs de transport et les itinéraires
(base PostgreSQL/PostGIS dédiée voyanta_itineraries). Calcule
systématiquement distance et coût côté serveur — jamais confiés au
client (règle explicite du cahier des charges, section 14.4). Publie un
événement itinerary.created sur RabbitMQ à chaque création de voyage.
Endpoints principaux :
GET /api/v1/destinations
GET /api/v1/destinations/<id>
GET /api/v1/destinations/nearby
POST /api/v1/routes/calculate
POST /api/v1/routes/matrix
GET /api/v1/costs/rates
POST /api/v1/costs/calculate
GET /api/v1/itineraries
POST /api/v1/itineraries
PATCH /api/v1/itineraries/<id>
DELETE /api/v1/itineraries/<id>
Note de conception : le cahier des charges d'origine prévoyait un Destination Service séparé. Le plan d'architecture "Phase 2" fourni ensuite ne liste que trois services (User / Itinerary / Recommendation). Les destinations ont donc été rattachées à l'Itinerary Service, qui en est le principal consommateur — c'est aussi la première extraction suggérée si le besoin s'en fait sentir plus tard (voir section 8 de
GUIDE_COMPLET.mddu dossierfrontend/).
Ne possède aucune donnée propre : il lit le User Service et l'Itinerary Service.
- Synchrone : à chaque appel à
GET /api/v1/recommendations, il interroge en REST le profil public de l'utilisateur (favoris, catégories préférées) sur le User Service, et la liste des destinations sur l'Itinerary Service. - Asynchrone : un processus séparé (
consumer.py, lancé en parallèle du serveur HTTP — voir leDockerfile) écoute en continu la queue RabbitMQitinerary.createdet met à jour un compteur de tendances (catégories et régions les plus demandées) dans Redis, utilisé pour enrichir le score de recommandation de tous les utilisateurs, y compris ceux non connectés.
GET /api/v1/recommendations?limit=6
api-gateway/nginx.conf route chaque famille d'URL vers le bon service et
centralise CORS et les logs d'accès (section 4.3.1 du cahier des charges).
C'est le seul point d'entrée que le frontend appelle.
Le dossier frontend/ contient l'application React déjà livrée
précédemment, désormais branchée sur le vrai backend (plus de données
mockées) :
src/context/AuthContext.tsx— session utilisateur (JWT stocké enlocalStorage, à faire évoluer vers un cookie httpOnly en production).src/services/*.ts— un fichier par domaine (authService,destinationService,itineraryService,recommendationService), chacun n'appelant que les routes qui le concernent.- Nouvelles pages : Connexion, Inscription, Profil (préférences de
voyage), Favoris.
Mes voyagesliste désormais les vrais itinéraires enregistrés en base. - La page d'accueil affiche une section "Recommandé pour vous" quand l'utilisateur est connecté (sinon "Près de vous", basé sur les tendances globales) — alimentée par le Recommendation Service.
Les cartes de destination gardent un calcul d'aperçu côté client (distance à vol d'oiseau) pour éviter un appel réseau par carte affichée dans une liste ; la page de détail et la création d'itinéraire, elles, appellent toujours le backend pour le chiffre définitif.
Conformément à la feuille de route du cahier des charges (section 18.2),
n'ont pas été implémentés : avis/modération, espace partenaires, espace
administrateur, réservation, paiement Mobile Money, application mobile
native, mode hors connexion complet, distance routière réelle (OSRM
est documenté mais pas branché — le calcul actuel utilise une distance à
vol d'oiseau majorée de 18 %, isolée dans geo.py pour un remplacement
en un seul endroit). Le dossier frontend/GUIDE_COMPLET.md détaille
comment brancher chacun de ces éléments.
- Mots de passe hachés avec Argon2 (jamais en clair, jamais en MD5/SHA seul)
- JWT signé avec un secret partagé entre les trois services (
JWT_SECRET_KEY) - Le coût et la distance affichés sont toujours recalculés par l'Itinerary Service ; le frontend n'envoie jamais de montant à enregistrer tel quel
- CORS restreint par route dans l'API Gateway (
*en développement — à remplacer par le domaine réel avant mise en production) - Chaque service a sa propre base de données : aucune clé étrangère SQL ne traverse les services (référence logique par identifiant uniquement), conformément au principe d'isolation des microservices
À faire avant une mise en production réelle : passer JWT_SECRET_KEY à
une vraie valeur secrète (jamais celle du .env.example), restreindre
CORS, ajouter un rate-limiter sur /auth/login, servir le tout derrière
HTTPS.
# Voir les logs d'un service en particulier
docker compose logs -f itinerary-service
# Réinitialiser complètement les données (supprime les volumes des bases)
docker compose down -v
# Reconstruire un seul service après une modification
docker compose up --build recommendation-service
# Ouvrir un shell dans un conteneur pour déboguer
docker compose exec itinerary-service sh