30 KiB
🏛️ Architecture Logicielle - BricoLoc Evolution
Document de Référence Architecture
Version 1.0 - Octobre 2025
📋 Table des Matières
- Vue d'Ensemble
- Principes Architecturaux
- Patterns Utilisés
- Architecture Legacy
- Architecture Moderne
- Comparaison Architecturale
- Décisions Techniques
- Qualités Architecturales
🎯 Vue d'Ensemble
Contexte
BricoLoc est une plateforme de location d'outils en ligne. L'application actuelle (Legacy) souffre de plusieurs problèmes architecturaux accumulés depuis 2013 :
- ❌ Architecture monolithique avec couplage fort
- ❌ Logique métier éparpillée (frontend, backend, base de données)
- ❌ Maintenance difficile (régressions fréquentes)
- ❌ Performance dégradée (temps de réponse > 3s)
- ❌ Scalabilité limitée
Objectif
Démontrer une refonte architecturale complète en appliquant les principes et patterns modernes d'architecture logicielle.
Approche
Création de deux applications côte à côte :
- Legacy : Simulation du système actuel avec ses problèmes
- Moderne : Nouvelle architecture avec résolution des problèmes
🏛️ Principes Architecturaux
SOLID Principles
S - Single Responsibility Principle
Une classe/module ne doit avoir qu'une seule raison de changer.
Application :
- Séparation claire entre couches (UI, Business Logic, Data)
- Composants React mono-responsabilité
- Services dédiés par domaine métier
O - Open/Closed Principle
Ouvert à l'extension, fermé à la modification.
Application :
- Utilisation d'interfaces et abstractions
- Pattern Strategy pour comportements variables
- Plugins et extensions sans modifier le code existant
L - Liskov Substitution Principle
Les classes dérivées doivent être substituables à leurs classes de base.
Application :
- Interfaces respectées par toutes les implémentations
- Repository pattern avec différentes sources de données
I - Interface Segregation Principle
Les clients ne doivent pas dépendre d'interfaces qu'ils n'utilisent pas.
Application :
- Interfaces spécifiques par contexte
- Composants avec props minimales
D - Dependency Inversion Principle
Dépendre des abstractions, pas des implémentations.
Application :
- Injection de dépendances
- Abstractions (interfaces) entre couches
Clean Architecture
┌─────────────────────────────────────────────┐
│ UI Layer (React Components) │
│ - Pages, Components, Hooks │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ Application Layer (Use Cases) │
│ - Services, Business Logic │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ Domain Layer (Entities) │
│ - Models, Types, Business Rules │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ Infrastructure Layer (Data Sources) │
│ - Repositories, APIs, Database │
└─────────────────────────────────────────────┘
Règles :
- Les dépendances pointent vers l'intérieur
- Le domaine ne dépend de rien
- L'infrastructure dépend du domaine (inversion)
Separation of Concerns
Chaque partie du système a une responsabilité claire :
| Couche | Responsabilité | Exemples |
|---|---|---|
| UI | Présentation | Composants React, pages |
| Application | Orchestration | Services, use cases |
| Domain | Métier | Entities, business rules |
| Infrastructure | Technique | API calls, DB access |
🎨 Patterns Utilisés
1. Repository Pattern
Objectif : Abstraire l'accès aux données
// Interface (Domain Layer)
interface OutilRepository {
findAll(filters?: OutilFilters): Promise<Outil[]>
findById(id: string): Promise<Outil | null>
create(outil: CreateOutilDTO): Promise<Outil>
update(id: string, data: UpdateOutilDTO): Promise<Outil>
delete(id: string): Promise<void>
}
// Implémentation (Infrastructure Layer)
class SupabaseOutilRepository implements OutilRepository {
constructor(private supabase: SupabaseClient) {}
async findAll(filters?: OutilFilters): Promise<Outil[]> {
// Implémentation Supabase
}
// ... autres méthodes
}
Bénéfices :
- ✅ Changement de source de données sans impact
- ✅ Tests facilités (mock du repository)
- ✅ Logique data isolée
2. Service Layer Pattern
Objectif : Centraliser la logique métier
// Service Layer
class ReservationService {
constructor(
private reservationRepo: ReservationRepository,
private stockService: StockService,
private pricingService: PricingService
) {}
async createReservation(data: CreateReservationDTO): Promise<Reservation> {
// 1. Vérifier disponibilité
const isAvailable = await this.stockService.checkAvailability(
data.outilId,
data.entrepotId,
data.dateDebut,
data.dateFin
)
if (!isAvailable) {
throw new UnavailableError()
}
// 2. Calculer prix
const prix = await this.pricingService.calculate(data)
// 3. Créer réservation
const reservation = await this.reservationRepo.create({
...data,
prixTotal: prix
})
// 4. Mettre à jour stock
await this.stockService.reserve(data.outilId, data.entrepotId)
return reservation
}
}
Bénéfices :
- ✅ Logique métier centralisée et réutilisable
- ✅ Orchestration des opérations complexes
- ✅ Tests unitaires faciles
3. Dependency Injection
Objectif : Découpler les dépendances
// Container DI (simplifié)
class ServiceContainer {
private static instance: ServiceContainer
private services = new Map<string, any>()
register<T>(key: string, factory: () => T): void {
this.services.set(key, factory)
}
resolve<T>(key: string): T {
const factory = this.services.get(key)
return factory()
}
}
// Enregistrement
container.register('outilRepository', () =>
new SupabaseOutilRepository(supabaseClient)
)
container.register('outilService', () =>
new OutilService(
container.resolve('outilRepository')
)
)
// Utilisation
const outilService = container.resolve<OutilService>('outilService')
4. Factory Pattern
Objectif : Créer des objets complexes
class ReservationFactory {
static create(dto: CreateReservationDTO, user: User): Reservation {
return {
id: generateId(),
utilisateurId: user.id,
outilId: dto.outilId,
statut: 'en_attente_paiement',
createdAt: new Date(),
// ... autres champs calculés
}
}
static createFromLegacy(legacyData: any): Reservation {
// Adapter les anciennes données
}
}
5. Observer Pattern (Realtime)
Objectif : Réagir aux changements en temps réel
// Supabase Realtime = Observer Pattern
const stockChannel = supabase
.channel('stocks')
.on('postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'stocks'
},
(payload) => {
// Notifier les observers (composants React)
updateStockCache(payload.new)
}
)
.subscribe()
🕰️ Architecture Legacy
Vue d'Ensemble
graph TD
A[Client Browser] --> B[Apache HTTP Reverse Proxy]
B --> C[Tomcat - Spring Framework Frontend]
C --> D[WebLogic - Java EE Backend]
D --> E[Oracle Database]
D --> F[MySQL Cache DB]
C --> F
G[Client Lourd Stocks] --> H[WCF Service]
H --> E
I[SAP ERP] --> J[Batch CSV]
J --> E
Problèmes Architecturaux
1. Couplage Fort
- Frontend accède directement à la BDD (court-circuite backend)
- Logique métier dans les procédures stockées PL/SQL
- Dépendance directe aux technologies (Oracle, WebLogic)
2. Monolithe Complexe
- Tout dans une seule application
- Déploiement all-or-nothing
- Scaling vertical uniquement
3. Maintenance Difficile
- Code spaghetti accumulé sur 10+ ans
- Tables avec 150+ colonnes
- Documentation obsolète
- Pas de tests automatisés
4. Performance
- Synchronisation stocks quotidienne (décalage 24h)
- Pas de cache efficace
- Requêtes SQL non optimisées
⚡ Architecture Moderne
Style Architectural : Microservices Hybride + Clean Architecture
L'architecture moderne combine :
- Microservices pour la décomposition fonctionnelle et l'autonomie
- Clean Architecture (couches) à l'intérieur de chaque microservice
- API Gateway pour l'orchestration
- Event-Driven pour la communication asynchrone
Vue d'Ensemble - Architecture Microservices
graph TB
subgraph "Client Layer"
A[Web App - Next.js]
B[Mobile App - Future]
end
subgraph "API Gateway Layer"
C[API Gateway / BFF]
end
A --> C
B --> C
subgraph "Microservices Layer"
D[Auth Service<br/>Supabase Auth]
E[Catalogue Service<br/>Next.js API + Supabase]
F[Reservation Service<br/>Next.js API + Supabase]
G[Payment Service<br/>Next.js API + Stripe Mock]
H[Notification Service<br/>Next.js API + Resend]
I[Inventory Service<br/>Next.js API + Supabase Realtime]
end
C --> D
C --> E
C --> F
C --> G
C --> H
C --> I
subgraph "Data Layer"
J[(Supabase PostgreSQL<br/>+ Realtime)]
K[Cache - Redis/Supabase]
L[Storage - Supabase Storage]
end
E --> J
F --> J
I --> J
E --> K
F --> K
I --> L
subgraph "Event Bus"
M[Event Bus<br/>Supabase Realtime Channels]
end
F --> M
I --> M
M --> H
Décomposition en Microservices
🔐 Auth Service (Supabase Auth)
Responsabilité : Authentification et autorisation
- Inscription / Connexion
- Gestion des tokens JWT
- Gestion des rôles (RBAC)
- OAuth providers (Google, etc.)
Technologies : Supabase Auth (géré)
📚 Catalogue Service
Responsabilité : Gestion du catalogue d'outils
- CRUD outils
- Recherche et filtrage
- Catégories
- Comparateur de prix
API Endpoints :
GET /api/catalogue/outilsGET /api/catalogue/outils/:idGET /api/catalogue/categoriesPOST /api/catalogue/search
Base de données : Tables outils, categories, outil_images, prix_comparatifs
Architecture interne (Clean Architecture) :
catalogue-service/
├── domain/
│ ├── entities/
│ │ └── outil.entity.ts
│ └── repositories/
│ └── outil.repository.interface.ts
│
├── application/
│ ├── use-cases/
│ │ ├── get-outils.use-case.ts
│ │ └── search-outils.use-case.ts
│ └── services/
│ └── catalogue.service.ts
│
└── infrastructure/
├── repositories/
│ └── supabase-outil.repository.ts
└── api/
└── routes.ts
📅 Reservation Service
Responsabilité : Gestion des réservations
- Création de réservations
- Vérification de disponibilité
- Calcul des prix
- Gestion du cycle de vie (en cours, terminée, annulée)
API Endpoints :
POST /api/reservationsGET /api/reservations/:idGET /api/reservations/user/:userIdPATCH /api/reservations/:id/cancelGET /api/reservations/check-availability
Base de données : Table reservations
Events émis :
reservation.createdreservation.confirmedreservation.cancelled
Architecture interne :
reservation-service/
├── domain/
│ ├── entities/
│ │ └── reservation.entity.ts
│ ├── value-objects/
│ │ ├── date-range.vo.ts
│ │ └── price.vo.ts
│ └── rules/
│ └── availability.rule.ts
│
├── application/
│ ├── use-cases/
│ │ ├── create-reservation.use-case.ts
│ │ └── check-availability.use-case.ts
│ └── services/
│ ├── reservation.service.ts
│ └── pricing.service.ts
│
└── infrastructure/
├── repositories/
│ └── supabase-reservation.repository.ts
└── events/
└── reservation.events.ts
📦 Inventory Service
Responsabilité : Gestion des stocks temps réel
- Stocks par entrepôt
- Disponibilité en temps réel
- Réservation temporaire de stock
- Synchronisation multi-entrepôts
API Endpoints :
GET /api/inventory/stocks/:outilIdGET /api/inventory/entrepots/:entrepotId/stocksPOST /api/inventory/reservePOST /api/inventory/release
Base de données : Table stocks, entrepots
Realtime : Supabase Realtime pour push des mises à jour
Events émis/écoutés :
- Écoute :
reservation.confirmed→ Décrémente stock - Écoute :
reservation.cancelled→ Libère stock - Émet :
stock.updated
💳 Payment Service
Responsabilité : Gestion des paiements
- Intégration Stripe (simulée)
- Création de payment intent
- Confirmation de paiement
- Remboursements
API Endpoints :
POST /api/payment/create-intentPOST /api/payment/confirmPOST /api/payment/refundGET /api/payment/:id/status
Base de données : Table transactions (logs)
Events émis :
payment.succeededpayment.failed
💬 Notification Service
Responsabilité : Notifications multi-canal
- Emails (Resend API)
- Notifications in-app
- SMS (futur)
API Endpoints :
POST /api/notifications/sendGET /api/notifications/user/:userId
Events écoutés :
reservation.created→ Email confirmationpayment.succeeded→ Email facturestock.low→ Email admin
Communication entre Microservices
1. Communication Synchrone (REST)
Pour les opérations nécessitant une réponse immédiate :
// Reservation Service appelle Inventory Service
const availability = await fetch('/api/inventory/check-availability', {
method: 'POST',
body: JSON.stringify({ outilId, entrepotId, dateDebut, dateFin })
})
2. Communication Asynchrone (Events)
Pour les opérations non-bloquantes :
// Reservation Service émet un event
await eventBus.publish('reservation.confirmed', {
reservationId,
outilId,
entrepotId,
dateDebut,
dateFin
})
// Inventory Service écoute l'event
eventBus.subscribe('reservation.confirmed', async (data) => {
await inventoryService.reserveStock(data.outilId, data.entrepotId)
})
Implémentation : Supabase Realtime Channels (Pub/Sub)
API Gateway / BFF (Backend For Frontend)
Le frontend Next.js agit comme BFF (Backend For Frontend) :
// app/api/reservations/route.ts (API Gateway pattern)
export async function POST(request: Request) {
const data = await request.json()
// 1. Vérifier auth
const user = await authService.getCurrentUser()
// 2. Vérifier disponibilité (appel Inventory Service)
const isAvailable = await inventoryService.checkAvailability(data)
if (!isAvailable) {
return NextResponse.json({ error: 'Unavailable' }, { status: 400 })
}
// 3. Créer réservation (appel Reservation Service)
const reservation = await reservationService.create({
...data,
utilisateurId: user.id
})
// 4. Créer payment intent (appel Payment Service)
const paymentIntent = await paymentService.createIntent({
amount: reservation.prixTotal,
reservationId: reservation.id
})
return NextResponse.json({ reservation, paymentIntent })
}
Avantages du BFF :
- ✅ Agrégation de plusieurs microservices
- ✅ Réduction du nombre d'appels frontend
- ✅ Logique d'orchestration centralisée
- ✅ Adaptation au besoin du client (web, mobile)
Architecture Hybride : Couches + Microservices
Chaque microservice implémente Clean Architecture en interne :
┌─────────────────────────────────────────────────────────┐
│ MICROSERVICE │
│ ┌─────────────────────────────────────────────────┐ │
│ │ API Layer (Controllers/Routes) │ │
│ └────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌────────────────▼────────────────────────────────┐ │
│ │ Application Layer (Use Cases, Services) │ │
│ └────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌────────────────▼────────────────────────────────┐ │
│ │ Domain Layer (Entities, Business Rules) │ │
│ └────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌────────────────▼────────────────────────────────┐ │
│ │ Infrastructure (Repositories, DB, Events) │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Pourquoi Hybride ?
- ✅ Microservices : Autonomie, scalabilité indépendante, équipes séparées
- ✅ Clean Architecture : Maintenabilité, testabilité, indépendance technologique
- ✅ Best of both worlds
Structure des Dossiers - Architecture Microservices
apps/modern-app/
├── src/
│ ├── app/ # Next.js App Router (UI + BFF)
│ │ ├── (auth)/
│ │ ├── (main)/
│ │ └── api/ # API Gateway / BFF
│ │ ├── catalogue/ # Catalogue Service API
│ │ ├── reservations/ # Reservation Service API
│ │ ├── inventory/ # Inventory Service API
│ │ ├── payment/ # Payment Service API
│ │ └── notifications/ # Notification Service API
│ │
│ ├── components/ # UI Components (Presentation)
│ │
│ ├── services/ # Microservices (Business Logic)
│ │ ├── catalogue/
│ │ │ ├── domain/
│ │ │ │ ├── entities/
│ │ │ │ └── repositories/
│ │ │ ├── application/
│ │ │ │ ├── use-cases/
│ │ │ │ └── services/
│ │ │ └── infrastructure/
│ │ │ ├── repositories/
│ │ │ └── supabase/
│ │ │
│ │ ├── reservation/
│ │ │ ├── domain/
│ │ │ ├── application/
│ │ │ └── infrastructure/
│ │ │
│ │ ├── inventory/
│ │ │ ├── domain/
│ │ │ ├── application/
│ │ │ └── infrastructure/
│ │ │
│ │ ├── payment/
│ │ │ ├── domain/
│ │ │ ├── application/
│ │ │ └── infrastructure/
│ │ │
│ │ └── notification/
│ │ ├── domain/
│ │ ├── application/
│ │ └── infrastructure/
│ │
│ ├── shared/ # Code partagé entre services
│ │ ├── domain/ # Shared entities, value objects
│ │ ├── infrastructure/ # Shared utilities
│ │ └── events/ # Event Bus
│ │
│ ├── hooks/ # Custom React Hooks
│ └── types/ # TypeScript Types
Remarque : Chaque microservice suit Clean Architecture en interne (domain → application → infrastructure)
Flux de Données - Architecture Microservices
sequenceDiagram
participant U as User (UI)
participant BFF as API Gateway (BFF)
participant RS as Reservation Service
participant IS as Inventory Service
participant PS as Payment Service
participant EB as Event Bus
participant NS as Notification Service
participant DB as Supabase DB
U->>BFF: POST /api/reservations
BFF->>IS: Check Availability
IS->>DB: Query stocks
DB-->>IS: Stock data
IS-->>BFF: Available
BFF->>RS: Create Reservation
RS->>DB: Insert reservation
DB-->>RS: Reservation created
RS->>EB: Publish "reservation.created"
RS-->>BFF: Reservation object
BFF->>PS: Create Payment Intent
PS-->>BFF: Payment Intent
BFF-->>U: {reservation, paymentIntent}
EB->>IS: Event "reservation.created"
IS->>DB: Reserve stock
EB->>NS: Event "reservation.created"
NS->>U: Send confirmation email
📊 Comparaison Architecturale
Styles Architecturaux
| Aspect | Legacy | Moderne |
|---|---|---|
| Style | Monolithe | Microservices Hybride |
| Frontend | MVC (Spring) | SPA + BFF (Next.js) |
| Backend | Java EE (monolithe) | Microservices (Next.js API) |
| Communication | SOAP | REST + Events |
| Déploiement | Tout-en-un | Indépendant par service |
| Scalabilité | Verticale | Horizontale par service |
| Architecture interne | ❌ Aucune | Clean Architecture |
Caractéristiques
| Aspect | Legacy | Moderne |
|---|---|---|
| Couplage | Fort (direct DB access) | Faible (via interfaces) |
| Cohésion | Faible | Forte (services focalisés) |
| Testabilité | Difficile | Facile (isolation) |
| Autonomie | ❌ Aucune | ✅ Par service |
| Résilience | ❌ Single point of failure | ✅ Fault isolation |
| Maintenance | Complexe (tout lié) | Simple (services isolés) |
Décomposition
| Critère | Legacy | Moderne |
|---|---|---|
| Granularité | 1 application | 6 microservices |
| Base de données | 1 monolithe Oracle | Database per service (logique) |
| Déploiement | 1 artefact | 6 services indépendants |
| Équipes | 1 équipe | Possibilité d'équipes par service |
| Technologies | Uniformes | Polyglotte (si besoin) |
Patterns
| Pattern | Legacy | Moderne |
|---|---|---|
| Microservices | ❌ Non | ✅ Oui |
| API Gateway / BFF | ❌ Non | ✅ Oui |
| Event-Driven | ❌ Non | ✅ Oui (Supabase Realtime) |
| Repository | ❌ Non | ✅ Oui |
| Service Layer | ⚠️ Partiel | ✅ Oui |
| Dependency Injection | ❌ Non | ✅ Oui |
| Clean Architecture | ❌ Non | ✅ Oui (par service) |
| SOLID | ❌ Non | ✅ Oui |
| CQRS | ❌ Non | ⚠️ Partiel (read/write séparés) |
Bénéfices de l'Architecture Microservices
✅ Avantages
- Autonomie : Chaque service peut être développé/déployé/scalé indépendamment
- Isolation des pannes : Un service en panne n'affecte pas les autres
- Scalabilité ciblée : Scale uniquement les services sous charge (ex: Inventory Service)
- Flexibilité technologique : Possibilité d'utiliser différentes techs par service
- Équipes indépendantes : Une équipe par service (futur)
- Déploiement continu : Déploiement d'un service sans impact sur les autres
⚠️ Complexités introduites
- Distributed system : Gestion de la cohérence, latence réseau
- Observabilité : Besoin de tracing distribué (logs, metrics)
- Transactions distribuées : Saga pattern (si nécessaire)
- Tests d'intégration : Plus complexes (inter-services)
Mitigations appliquées :
- ✅ Event-driven pour asynchrone (résilience)
- ✅ BFF pour agrégation (moins d'appels frontend)
- ✅ Supabase pour gestion centralisée de la data
- ✅ Next.js pour colocalisation (simplicité de déploiement)
Métriques
| Métrique | Legacy | Moderne | Impact |
|---|---|---|---|
| Complexité Cyclomatique | ~35 | ~8 | -77% |
| Couplage (afferent/efferent) | ~15 | ~3 | -80% |
| Lignes par fichier | ~500 | ~150 | -70% |
| Tests Coverage | 0% | 80%+ | +∞ |
🎯 Décisions Techniques
Toutes les décisions architecturales majeures sont documentées dans les ADR.
ADR Principaux
| # | Titre | Décision |
|---|---|---|
| 001 | Structure Mono-repo | pnpm workspaces |
| 002 | Framework Frontend | Next.js 14 |
| 003 | Backend & BDD | Supabase |
| 004 | Architecture en Couches | Clean Architecture |
| 005 | Patterns | Repository + Service Layer |
🏆 Qualités Architecturales
1. Maintenabilité
Legacy : ⭐ (1/5)
- Code spaghetti
- Pas de séparation des responsabilités
- Documentation obsolète
Moderne : ⭐⭐⭐⭐⭐ (5/5)
- Code modulaire et propre
- Séparation claire des couches
- Documentation à jour
2. Testabilité
Legacy : ⭐ (1/5)
- Couplage fort = tests difficiles
- Pas de tests existants
- Dépendances sur BDD réelle
Moderne : ⭐⭐⭐⭐⭐ (5/5)
- Interfaces = mocking facile
- Tests unitaires isolés
- Tests d'intégration avec Supabase local
3. Scalabilité
Legacy : ⭐⭐ (2/5)
- Scaling vertical uniquement
- Monolithe = réplication complète
- Coûts exponentiels
Moderne : ⭐⭐⭐⭐⭐ (5/5)
- Scaling horizontal (Vercel)
- Supabase géré et scalable
- Coûts linéaires
4. Performance
Legacy : ⭐⭐ (2/5)
- Temps de réponse lents
- Pas de cache efficace
- Synchronisation différée
Moderne : ⭐⭐⭐⭐⭐ (5/5)
- SSR/SSG optimisés
- Cache intelligent (React Query)
- Temps réel (Supabase)
5. Sécurité
Legacy : ⭐⭐ (2/5)
- Hash MD5 (faible)
- SQL injection possible
- Pas de RLS
Moderne : ⭐⭐⭐⭐⭐ (5/5)
- JWT sécurisés (Supabase)
- Parameterized queries
- Row Level Security (RLS)
📚 Références
Livres
- Clean Architecture - Robert C. Martin
- Design Patterns - Gang of Four
- Domain-Driven Design - Eric Evans
Articles & Ressources
🔄 Évolution
Ce document évolue avec le projet. Chaque décision architecturale majeure doit :
- Être discutée en équipe
- Faire l'objet d'un ADR
- Être intégrée dans ce document
- Être reflétée dans le code
Document maintenu par : Équipe Architecture
Dernière révision : Octobre 2025