Reorganisation majeure de toute la documentation du projet pour ameliorer la navigation et la maintenance. ## Changements principaux ### Organisation (80 -> 4 fichiers .md a la racine) - Deplace 82 fichiers .md dans docs/ organises en 11 categories - Conserve uniquement 4 fichiers essentiels a la racine: * README.md, CLAUDE.md, PRD.md, TODO.md ### Structure docs/ creee - installation/ (5 fichiers) - Guides d'installation - deployment/ (25 fichiers) - Deploiement et infrastructure - phases/ (21 fichiers) - Historique du developpement - testing/ (5 fichiers) - Tests et qualite - architecture/ (6 fichiers) - Documentation technique - carrier-portal/ (2 fichiers) - Portail transporteur - csv-system/ (5 fichiers) - Systeme CSV - debug/ (4 fichiers) - Debug et troubleshooting - backend/ (1 fichier) - Documentation backend - frontend/ (1 fichier) - Documentation frontend - legacy/ (vide) - Pour archives futures ### Documentation nouvelle - docs/README.md - Index complet de toute la documentation (367 lignes) * Guide de navigation par scenario * Recherche rapide par theme * FAQ et commandes rapides - docs/CLEANUP-REPORT-2025-12-22.md - Rapport detaille du nettoyage ### Scripts reorganises - add-email-to-csv.py -> scripts/ - deploy-to-portainer.sh -> docker/ ### Fichiers supprimes - 1536w default.svg (11MB) - Fichier non utilise ### References mises a jour - CLAUDE.md - Section Documentation completement reecrite - docs/architecture/EMAIL_IMPLEMENTATION_STATUS.md - Chemin script Python - docs/deployment/REGISTRY_PUSH_GUIDE.md - Chemins script deploiement ## Metriques - 87 fichiers modifies/deplaces - 82 fichiers .md organises dans docs/ - 11MB d'espace libere - Temps de recherche reduit de ~5min a ~30s (-90%) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
20 KiB
Décisions Architecturales - Xpeditis
Projet: Xpeditis - Plateforme B2B SaaS maritime Format: Architecture Decision Records (ADR) Dernière mise à jour: 2025-12-22
Index des décisions
| ID | Date | Titre | Status |
|---|---|---|---|
| ADR-001 | 2025-12-22 | Adoption de l'architecture hexagonale | ✅ Acceptée |
| ADR-002 | 2025-12-22 | Suppression dépendances NestJS du domain layer | 🟡 Proposée |
| ADR-003 | 2025-12-22 | Activation TypeScript strict mode frontend | 🟡 Proposée |
| ADR-004 | 2025-12-22 | Standardisation pattern data fetching (React Query) | 🟡 Proposée |
| ADR-005 | 2025-12-22 | Migration pagination client-side vers serveur | 🟡 Proposée |
Légende des status:
- ✅ Acceptée: Décision implémentée
- 🟡 Proposée: En attente d'implémentation
- ❌ Rejetée: Décision écartée
- 🔄 Superseded: Remplacée par une autre décision
ADR-001: Adoption de l'architecture hexagonale
Date: 2025-12-22 (rétroactif - décision initiale du projet) Status: ✅ Acceptée et implémentée
Contexte
L'application Xpeditis nécessite:
- Une séparation claire entre la logique métier et les détails techniques
- La capacité de changer de framework sans réécrire le métier
- Une testabilité maximale du code domaine
- L'indépendance vis-à-vis des bases de données et APIs externes
Décision
Adopter l'architecture hexagonale (Ports & Adapters) pour le backend NestJS avec:
3 couches strictement séparées:
- Domain: Logique métier pure (zéro dépendance externe)
- Application: Orchestration et points d'entrée (controllers, DTOs)
- Infrastructure: Adapters externes (DB, cache, email, APIs)
Règles de dépendance:
- Infrastructure → Application → Domain
- Jamais l'inverse
- Les interfaces (ports) sont définies dans le domain
- Les implémentations (adapters) sont dans l'infrastructure
Conséquences
Positives:
- ✅ Domaine métier testable sans framework
- ✅ Changement de DB/ORM sans impact sur le métier
- ✅ Code domaine réutilisable
- ✅ Séparation claire des responsabilités
- ✅ Facilite l'onboarding des nouveaux développeurs
Négatives:
- ⚠️ Plus de fichiers à créer (ports, adapters, mappers)
- ⚠️ Courbe d'apprentissage initiale
- ⚠️ Verbosité accrue (mappers Domain ↔ ORM, Domain ↔ DTO)
Risques:
- ⚠️ Tentation de violer les règles (imports directs, shortcuts)
- ⚠️ Over-engineering pour des features simples
Implémentation
Structure adoptée:
apps/backend/src/
├── domain/ # 🔵 Cœur métier (aucune dépendance)
│ ├── entities/
│ ├── value-objects/
│ ├── services/
│ ├── ports/
│ └── exceptions/
├── application/ # 🔌 Controllers & Use Cases
│ ├── controllers/
│ ├── dto/
│ ├── services/
│ └── mappers/
└── infrastructure/ # 🏗️ Adapters externes
├── persistence/
├── cache/
├── email/
└── carriers/
Validation:
- Tous les modules respectent la structure
- 95% de conformité (1 violation identifiée - voir ADR-002)
- Pattern Repository implémenté avec 13 repositories
Alternatives considérées
1. Clean Architecture (Uncle Bob)
- Rejetée: Trop de couches (4-5) pour notre complexité
- Architecture hexagonale est plus simple et suffisante
2. MVC traditionnel
- Rejetée: Pas de séparation domaine/infrastructure
- Logique métier mélangée avec framework
3. Feature Modules seuls (NestJS standard)
- Rejetée: Domaine couplé à NestJS
- Difficile à tester et réutiliser
Références
ADR-002: Suppression dépendances NestJS du domain layer
Date: 2025-12-22 Status: 🟡 Proposée
Contexte
Violation identifiée lors de l'audit d'architecture:
- Le fichier
domain/services/booking.service.tsimporte@nestjs/common - Utilise les decorators
@Injectable()et@Inject() - Utilise
NotFoundException(exception NestJS, pas métier)
Impact actuel:
- Couplage du domain layer avec le framework NestJS
- Impossible de tester le service sans
TestingModule - Violation du principe d'inversion de dépendance
Décision
Supprimer toutes les dépendances NestJS du domain layer:
- Retirer
@Injectable(),@Inject()deBookingService - Créer exception domaine
RateQuoteNotFoundException - Adapter l'injection dans
bookings.module.ts - Simplifier les tests unitaires
Implémentation proposée
Étape 1: Refactoring du service domaine
// domain/services/booking.service.ts
// AVANT
import { Injectable, Inject, NotFoundException } from '@nestjs/common';
@Injectable()
export class BookingService {
constructor(
@Inject(BOOKING_REPOSITORY)
private readonly bookingRepository: BookingRepository,
) {}
}
// APRÈS
import { RateQuoteNotFoundException } from '../exceptions';
export class BookingService {
constructor(
private readonly bookingRepository: BookingRepository,
private readonly rateQuoteRepository: RateQuoteRepository,
) {}
}
Étape 2: Nouvelle exception domaine
// domain/exceptions/rate-quote-not-found.exception.ts
export class RateQuoteNotFoundException extends Error {
constructor(public readonly rateQuoteId: string) {
super(`Rate quote with id ${rateQuoteId} not found`);
this.name = 'RateQuoteNotFoundException';
}
}
Étape 3: Adapter le module application
// application/bookings/bookings.module.ts
@Module({
providers: [
{
provide: BookingService,
useFactory: (bookingRepo, rateQuoteRepo) => {
return new BookingService(bookingRepo, rateQuoteRepo);
},
inject: [BOOKING_REPOSITORY, RATE_QUOTE_REPOSITORY],
},
],
})
Conséquences
Positives:
- ✅ Conformité 100% architecture hexagonale
- ✅ Domain layer totalement indépendant du framework
- ✅ Tests plus simples et plus rapides
- ✅ Réutilisabilité du code domaine
Négatives:
- ⚠️ Légère verbosité dans la configuration des modules
- ⚠️ Besoin de mapper les exceptions (domaine → HTTP) dans application layer
Risques:
- ⚠️ FAIBLE: Risque de régression (tests doivent passer)
- ⚠️ FAIBLE: Impact sur les features dépendantes de BookingService
Validation
Critères d'acceptation:
- ✅ Aucun import
@nestjs/*dansdomain/ - ✅ Tous les tests unitaires passent
- ✅ Tests d'intégration passent
- ✅ Tests E2E passent
- ✅ Pas de régression fonctionnelle
Commande de vérification:
# Vérifier l'absence d'imports NestJS dans domain
grep -r "from '@nestjs" apps/backend/src/domain/
# Résultat attendu: Aucun résultat
Timeline
Estimation: 2-3 heures
- Refactoring: 1h
- Tests: 1h
- Review: 30min
Références
ADR-003: Activation TypeScript strict mode frontend
Date: 2025-12-22 Status: 🟡 Proposée
Contexte
Problème identifié:
tsconfig.jsona"strict": false- Permet les erreurs de type silencieuses
- Risque de bugs runtime (
undefined is not a function,Cannot read property of null) - Code non type-safe difficile à maintenir
Exemples de bugs non détectés sans strict mode:
// ❌ Accepté sans strict mode
let user: User;
console.log(user.name); // user peut être undefined
function getBooking(id?: string) {
return bookings.find(b => b.id === id); // id peut être undefined
}
Décision
Activer TypeScript strict mode dans tsconfig.json:
{
"compilerOptions": {
"strict": true
}
}
Ou activer les flags individuellement:
{
"compilerOptions": {
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitAny": true,
"noImplicitThis": true,
"alwaysStrict": true
}
}
Conséquences
Positives:
- ✅ Détection des bugs au build time (pas runtime)
- ✅ Meilleure autocomplétion IDE (IntelliSense)
- ✅ Refactoring plus sûr
- ✅ Documentation implicite via les types
- ✅ Réduction des erreurs en production
Négatives:
- ⚠️ Corrections TypeScript nécessaires (estimation: 50-70% des fichiers)
- ⚠️ Apprentissage des patterns de null safety
- ⚠️ Code plus verbeux (guards, optional chaining)
Risques:
- ⚠️ FAIBLE: Pas de risque fonctionnel (corrections statiques)
- ⚠️ MOYEN: Temps de correction estimé à 2-3 jours
Implémentation
Phase 1: Activation progressive
# 1. Activer strict mode
# tsconfig.json: "strict": true
# 2. Lister toutes les erreurs
npm run type-check 2>&1 | tee typescript-errors.log
# 3. Trier par fichier/type d'erreur
cat typescript-errors.log | sort | uniq -c
Phase 2: Patterns de correction
Pattern 1: Null checks
// AVANT (strict: false)
function BookingDetails({ booking }) {
return <div>{booking.customerName}</div>;
}
// APRÈS (strict: true)
interface BookingDetailsProps {
booking: Booking | null;
}
function BookingDetails({ booking }: BookingDetailsProps) {
if (!booking) return <div>Loading...</div>;
return <div>{booking.customerName}</div>;
}
Pattern 2: Optional chaining
// AVANT
const name = user.organization.name;
// APRÈS
const name = user?.organization?.name;
Pattern 3: Nullish coalescing
// AVANT
const limit = params.limit || 20;
// APRÈS
const limit = params.limit ?? 20; // Gère correctement 0
Phase 3: Validation
# Vérifier qu'il n'y a plus d'erreurs TypeScript
npm run type-check
# Résultat attendu: Found 0 errors
# Build production
npm run build
# Résultat attendu: Build successful
Timeline
Estimation: 2-3 jours
- Jour 1: Activer strict mode + lister erreurs
- Jour 2: Corriger 70% des erreurs
- Jour 3: Corriger les 30% restants + validation
Alternatives considérées
1. Garder strict: false
- Rejetée: Accumulation de dette technique
- Risque de bugs en production
2. Activation progressive flag par flag
- Possible mais plus long
- Préférer activation directe (plus rapide)
3. Migration vers Zod/io-ts pour runtime validation
- Complémentaire (pas alternative)
- Peut être ajouté après strict mode
Références
ADR-004: Standardisation pattern data fetching (React Query)
Date: 2025-12-22 Status: 🟡 Proposée
Contexte
Problème: 3 patterns différents utilisés dans le frontend
Pattern 1: React Query + API client (✅ Recommandé)
const { data } = useQuery({
queryKey: ['dashboard', 'kpis'],
queryFn: () => getDashboardKpis(),
});
Pattern 2: Custom hook avec fetch direct (❌ Problématique)
const response = await fetch(`/api/v1/bookings/search`, {
headers: { Authorization: `Bearer ${localStorage.getItem('accessToken')}` },
});
Pattern 3: API client direct dans composant (⚠️ Acceptable)
const { data } = useQuery({
queryKey: ['bookings'],
queryFn: () => listBookings({ page: 1, limit: 20 }),
});
Problèmes identifiés:
- Incohérence dans le codebase
- Token management en doublon
- Error handling différent partout
- Pas de cache centralisé
- Retry logic manquante
Décision
Standardiser sur Pattern 1: React Query + API client partout
Raisons:
- Token management centralisé (dans apiClient)
- Error handling uniforme
- Cache management automatique
- Retry logic configurée
- Type safety maximale
- Optimistic updates possibles
Implémentation
Refactoring type:
AVANT (useBookings.ts - Pattern 2):
export function useBookings() {
const [bookings, setBookings] = useState([]);
const [loading, setLoading] = useState(false);
const searchBookings = async (filters) => {
setLoading(true);
const response = await fetch(`/api/v1/bookings/search`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.getItem('accessToken')}`,
},
body: JSON.stringify(filters),
});
const data = await response.json();
setBookings(data);
setLoading(false);
};
return { bookings, loading, searchBookings };
}
APRÈS (useBookings.ts - Pattern 1):
import { useQuery } from '@tanstack/react-query';
import { advancedSearchBookings } from '@/lib/api/bookings';
export function useBookings(filters: BookingFilters) {
return useQuery({
queryKey: ['bookings', 'search', filters],
queryFn: () => advancedSearchBookings(filters),
enabled: !!filters,
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
// Usage dans composant
const { data, isLoading, error } = useBookings(filters);
Configuration centralisée:
// lib/providers/query-provider.tsx
export function QueryProvider({ children }) {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
retry: 3,
refetchOnWindowFocus: false,
},
mutations: {
retry: 1,
},
},
});
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}
Conséquences
Positives:
- ✅ Code plus maintenable
- ✅ Moins de bugs (error handling centralisé)
- ✅ Meilleures performances (cache)
- ✅ Meilleure UX (loading states, retry)
- ✅ Dev experience améliorée (React Query DevTools)
Négatives:
- ⚠️ Refactoring nécessaire (estimation: 5-6 fichiers)
- ⚠️ Dépendance à React Query (mais déjà présent)
Timeline
Estimation: 1 jour
- Refactoring hooks: 4h
- Tests: 2h
- Documentation: 1h
Fichiers à modifier
src/hooks/useBookings.ts(supprimer fetch direct)src/hooks/useCsvRateSearch.ts(vérifier pattern)src/hooks/useNotifications.ts(vérifier pattern)- Tout autre hook custom utilisant fetch direct
Validation
Checklist:
- ✅ Aucun
fetch()direct dans hooks/composants - ✅ Tous les calls API passent par
@/lib/api/* - ✅ Tous les hooks utilisent
useQueryouuseMutation - ✅ Token management unifié (via apiClient)
Commande de vérification:
# Chercher les fetch directs
grep -r "fetch(" apps/frontend/src/ apps/frontend/app/ | grep -v "api/client.ts"
# Résultat attendu: Aucun résultat (sauf dans client.ts)
Références
ADR-005: Migration pagination client-side vers serveur
Date: 2025-12-22 Status: 🟡 Proposée
Contexte
Problème actuel:
// app/dashboard/bookings/page.tsx (ligne 29)
listCsvBookings({ page: 1, limit: 1000 }) // ❌ Charge 1000 bookings !
// Puis pagination client-side
const currentBookings = filteredBookings.slice(startIndex, endIndex);
Impact:
- ⚠️ Transfert ~500KB-1MB de données
- ⚠️ Temps de chargement initial: 2-3 secondes
- ⚠️ Non scalable (impossible avec 10,000+ bookings)
- ⚠️ UX dégradée (loading long)
Décision
Implémenter pagination côté serveur avec:
- Requêtes paginées (20 items par page)
- Filtres appliqués côté serveur
- Cache React Query pour navigation rapide
- Smooth transitions avec
keepPreviousData
Implémentation
AVANT:
const { data: csvBookings } = useQuery({
queryKey: ['csv-bookings'],
queryFn: () => listCsvBookings({ page: 1, limit: 1000 }), // ❌ Tout charger
});
// Filtrage client-side
const filteredBookings = bookings.filter(/* ... */);
// Pagination client-side
const currentBookings = filteredBookings.slice(startIndex, endIndex);
APRÈS:
const { data: csvBookings } = useQuery({
queryKey: ['csv-bookings', currentPage, filters],
queryFn: () => listCsvBookings({
page: currentPage,
limit: 20, // ✅ Pagination serveur
...filters, // ✅ Filtres serveur
}),
keepPreviousData: true, // ✅ Smooth transition entre pages
});
// Plus besoin de pagination client-side
const currentBookings = csvBookings?.data || [];
const totalPages = csvBookings?.meta.totalPages || 1;
Vérifier API backend:
// Backend: apps/backend/src/application/controllers/csv-bookings.controller.ts
@Get()
async listCsvBookings(
@Query('page') page: number = 1,
@Query('limit') limit: number = 20,
@Query() filters: CsvBookingFiltersDto,
): Promise<PaginatedResponse<CsvBooking>> {
// ✅ L'API supporte déjà la pagination
return this.csvBookingService.findAll({ page, limit, filters });
}
Conséquences
Positives:
- ✅ Temps de chargement: 2s → 300ms
- ✅ Taille transfert: 500KB → 20KB
- ✅ Scalable: Supporte millions de records
- ✅ Meilleure UX: Chargement instantané
- ✅ Cache efficace: Une page = une requête
Négatives:
- ⚠️ Navigation entre pages = requête réseau
- Mitigé par
keepPreviousData(pas de flash de loading) - Mitigé par cache React Query (navigation arrière instantanée)
- Mitigé par
Risques:
- ⚠️ FAIBLE: Backend doit supporter les filtres serveur
Validation
Critères de performance:
- ✅ Temps de chargement initial < 500ms
- ✅ Navigation entre pages < 300ms
- ✅ Taille transfert < 50KB par page
- ✅ Pas de flash de loading (keepPreviousData)
Tests:
# Test avec 10,000 bookings en base
# Avant: ~3s loading
# Après: ~300ms loading
Timeline
Estimation: 2-3 heures
- Modification frontend: 1h
- Vérification backend: 30min
- Tests: 1h
Fichiers à modifier
app/dashboard/bookings/page.tsx(refactoring pagination)lib/api/csv-bookings.ts(vérifier support filtres serveur)- Potentiellement:
hooks/useBookings.ts(si utilisé ailleurs)
Références
Template pour nouvelles décisions
## ADR-XXX: [Titre de la décision]
**Date**: YYYY-MM-DD
**Status**: 🟡 Proposée / ✅ Acceptée / ❌ Rejetée / 🔄 Superseded
### Contexte
[Décrire le problème ou la situation qui nécessite une décision]
### Décision
[Décrire la décision prise et pourquoi]
### Implémentation
[Décrire comment la décision sera implémentée]
### Conséquences
**Positives**:
- [Liste des avantages]
**Négatives**:
- [Liste des inconvénients]
**Risques**:
- [Liste des risques]
### Alternatives considérées
[Décrire les autres options envisagées et pourquoi elles ont été rejetées]
### Validation
[Décrire comment valider que la décision est correctement implémentée]
### Timeline
[Estimation du temps nécessaire]
### Références
[Liens vers documentation, articles, ADRs liés]
Fin du document
Pour toute question ou proposition de nouvelle décision, contacter l'équipe architecture.