11 KiB
ADR-004 : Architecture Microservices Hybride
Statut : ✅ Accepté
Date : Octobre 2025
Décideurs : Équipe Architecture
Tags : architecture, microservices, patterns
Contexte
Le système BricoLoc peut être architecturé de plusieurs façons :
- Architecture monolithique (comme le legacy)
- Architecture en couches (layered)
- Architecture microservices
- Architecture hexagonale
L'objectif est de démontrer la maîtrise de l'architecture logicielle en appliquant des patterns modernes.
Options considérées
- Monolithe modulaire (architecture en couches uniquement)
- Microservices purs (services déployés séparément)
- Microservices hybride (logiquement séparés, physiquement colocalisés)
- Architecture hexagonale (ports & adapters)
Décision
✅ Nous adoptons une architecture Microservices Hybride avec Clean Architecture interne
Hybride signifie :
- Logiquement : 6 microservices distincts avec responsabilités claires
- Physiquement : Colocalisés dans le même déploiement Next.js (simplicité)
- Évolutif : Possibilité de séparer les déploiements si besoin
Justification
Pourquoi Microservices ?
1. Décomposition par Domaine Métier 📦
Chaque microservice correspond à un bounded context :
| Microservice | Responsabilité | Bounded Context |
|---|---|---|
| Auth Service | Authentification, autorisation | Identity & Access Management |
| Catalogue Service | Gestion outils, catégories, recherche | Product Catalog |
| Reservation Service | Réservations, planning | Booking Management |
| Inventory Service | Stocks, entrepôts, disponibilité | Inventory Management |
| Payment Service | Paiements, transactions | Payment Processing |
| Notification Service | Emails, notifications | Communication |
Avantage : Séparation claire des responsabilités (Single Responsibility Principle)
2. Autonomie 🚀
Chaque microservice :
- A sa propre logique métier (domaine)
- Gère ses propres données (database per service - logique)
- Peut être développé indépendamment
- Peut évoluer sans impacter les autres
Exemple : Changement de la logique de pricing → uniquement Reservation Service touché
3. Scalabilité Ciblée 📈
Possibilité de scaler uniquement les services sous charge :
- Inventory Service (beaucoup d'accès en temps réel) → scale horizontal
- Les autres restent à 1 instance
Comparaison Legacy : Monolithe = scale tout ou rien (coûteux)
4. Isolation des Pannes 🛡️
Si un service tombe en panne, les autres continuent :
- Payment Service down → Catalogue et Recherche fonctionnent toujours
- Graceful degradation possible
Comparaison Legacy : Monolithe = single point of failure
5. Évolutivité Technologique 🔧
Chaque microservice pourrait utiliser une techno différente :
- Catalogue Service → Node.js (si besoin d'ElasticSearch)
- Payment Service → Python (si ML pour fraude detection)
- Etc.
Note : Dans notre cas, tout est Next.js API Routes (simplicité), mais l'architecture permet l'évolution.
6. Démonstration Architecturale 🏛️
C'est un projet d'architecture logicielle :
- Microservices = pattern architectural majeur
- Démontre la maîtrise de la décomposition
- Impressionnant pour un portfolio
Pourquoi "Hybride" ? (Pas microservices purs)
Microservices Purs (Rejeté)
Avantages :
- Déploiement indépendant par service
- Technologies hétérogènes possibles
- Scaling précis
Inconvénients :
- ❌ Complexité opérationnelle (6 déploiements séparés)
- ❌ Coûts (6 instances minimum)
- ❌ Latence réseau (appels HTTP inter-services)
- ❌ Debugging complexe (logs distribués)
- ❌ Overkill pour projet académique
Microservices Hybride (Choisi)
Principe :
- Services logiquement séparés (code organisé en microservices)
- Physiquement colocalisés (même déploiement Next.js)
- Communication intra-process (pas HTTP externe)
Avantages :
- ✅ Simplicité opérationnelle (1 seul déploiement)
- ✅ Performance (pas de latence réseau)
- ✅ Coûts réduits (1 seule instance)
- ✅ Debugging facile (logs centralisés)
- ✅ Démontre l'architecture sans overhead
Possibilité d'évolution :
- Si besoin de scale → extraire un service en déploiement séparé
- Architecture prête pour ça (faible couplage)
Clean Architecture Interne
Chaque microservice implémente Clean Architecture :
microservice/
├── domain/ # Couche Domaine
│ ├── entities/ # Entités métier
│ ├── value-objects/ # Value Objects
│ ├── repositories/ # Interfaces repositories
│ └── rules/ # Règles métier
│
├── application/ # Couche Application
│ ├── use-cases/ # Cas d'usage
│ ├── services/ # Services applicatifs
│ └── dtos/ # Data Transfer Objects
│
└── infrastructure/ # Couche Infrastructure
├── repositories/ # Implémentations (Supabase)
├── external-apis/ # APIs externes (Stripe)
└── events/ # Event Bus
Bénéfices :
- ✅ Testabilité maximale
- ✅ Indépendance des frameworks
- ✅ Maintenabilité
- ✅ Respect de SOLID
Architecture Proposée
Vue d'Ensemble
┌───────────────────────────────────────────────────┐
│ Client (Browser) - Next.js SSR │
└─────────────────────┬─────────────────────────────┘
│
┌─────────────────────▼─────────────────────────────┐
│ API Gateway / BFF (Next.js API) │
│ Orchestration & Aggregation │
└─┬──────┬───────┬───────┬───────┬────────┬────────┘
│ │ │ │ │ │
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌─────┐┌─────┐┌──────┐┌──────┐┌──────┐┌──────┐
│Auth ││Cata-││Reser-││Inven-││Pay- ││Noti- │
│Svc ││logue││vation││tory ││ment ││fica- │
│ ││Svc ││Svc ││Svc ││Svc ││tion │
│ ││ ││ ││ ││ ││Svc │
└──┬──┘└──┬──┘└───┬──┘└───┬──┘└───┬──┘└───┬──┘
│ │ │ │ │ │
│ │ │ │ │ │
└──────┴───────┴───────┴───────┴────────┘
│
┌──────▼──────┐
│ Event Bus │ (Supabase Realtime)
└──────┬──────┘
│
┌──────▼──────┐
│ Supabase │ (PostgreSQL + Auth + Storage)
└─────────────┘
Communication
Synchrone (REST-like)
// API Gateway appelle directement le service (même process)
const availability = await inventoryService.checkAvailability({
outilId,
entrepotId,
dateDebut,
dateFin
})
Asynchrone (Events)
// Service émet un event via Event Bus (Supabase Realtime)
await eventBus.publish('reservation.confirmed', {
reservationId,
outilId,
entrepotId
})
// Autre service écoute l'event
eventBus.subscribe('reservation.confirmed', async (data) => {
await inventoryService.reserveStock(data)
})
Conséquences
✅ Avantages
- Architecture claire : Décomposition fonctionnelle évidente
- Maintenabilité : Chaque service est focalisé et petit
- Testabilité : Tests unitaires isolés par service
- Évolutivité : Possibilité d'extraire un service si besoin
- Démonstration : Montre la maîtrise de patterns avancés
- Simplicité opérationnelle : 1 seul déploiement
- Performance : Pas de latence réseau
⚠️ Complexités
- Structure du code : Plus de dossiers et fichiers
- Communication : Gérer Event Bus pour async
- Transactions distribuées : Saga pattern si nécessaire
- Tests d'intégration : Plus complexes
🛠️ Mitigations
- Structure : Documentation claire (README par service)
- Communication : Event Bus simple (Supabase Realtime)
- Transactions : Éviter si possible, ou Saga simplifié
- Tests : Focus sur tests unitaires par service
Alternatives Rejetées
❌ Monolithe Modulaire (Couches uniquement)
Raisons du rejet :
- Moins impressionnant architecturalement
- Pas de décomposition fonctionnelle
- Moins adapté pour démonstration d'architecture
- Scaling "tout ou rien"
❌ Microservices Purs (Déploiements séparés)
Raisons du rejet :
- Trop complexe pour projet académique
- Coûts hébergement (6 instances)
- Overhead opérationnel
- Debugging distribué difficile
❌ Architecture Hexagonale Seule
Raisons du rejet :
- Pattern complémentaire (on l'utilise en interne)
- Moins "visible" que microservices
- Moins adapté pour comparaison avec legacy
Migration vers Microservices Purs (Futur)
L'architecture permet d'extraire un service facilement :
Étape 1 : Extraire Inventory Service (le plus sollicité)
Avant : Next.js (tout colocalisé)
Après : Next.js + Inventory Service (Node.js séparé)
Changement minimal :
- Remplacer appel direct par appel HTTP
- Déployer Inventory Service séparément
- Event Bus déjà en place (pas de changement)
Couplage faible ✅ → Migration facile
Références
Validations
- Bounded contexts identifiés
- 6 microservices définis
- Communication sync/async définie
- Clean Architecture par service validée
- Event Bus (Supabase Realtime) testé
Prochaine révision : Si complexité trop élevée lors du développement