xpeditis2.0/docs/decisions.md
David c19af3b119
Some checks failed
CI/CD Pipeline / Backend - Build, Test & Push (push) Failing after 58s
CI/CD Pipeline / Frontend - Build, Test & Push (push) Failing after 5m55s
CI/CD Pipeline / Integration Tests (push) Has been skipped
CI/CD Pipeline / Deployment Summary (push) Has been skipped
CI/CD Pipeline / Deploy to Portainer (push) Has been skipped
CI/CD Pipeline / Discord Notification (Success) (push) Has been skipped
CI/CD Pipeline / Discord Notification (Failure) (push) Has been skipped
docs: reorganiser completement la documentation dans docs/
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>
2025-12-22 15:45:51 +01:00

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:

  1. Domain: Logique métier pure (zéro dépendance externe)
  2. Application: Orchestration et points d'entrée (controllers, DTOs)
  3. 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.ts importe @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:

  1. Retirer @Injectable(), @Inject() de BookingService
  2. Créer exception domaine RateQuoteNotFoundException
  3. Adapter l'injection dans bookings.module.ts
  4. 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/* dans domain/
  • 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.json a "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:

  1. Token management centralisé (dans apiClient)
  2. Error handling uniforme
  3. Cache management automatique
  4. Retry logic configurée
  5. Type safety maximale
  6. 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

  1. src/hooks/useBookings.ts (supprimer fetch direct)
  2. src/hooks/useCsvRateSearch.ts (vérifier pattern)
  3. src/hooks/useNotifications.ts (vérifier pattern)
  4. 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 useQuery ou useMutation
  • 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:

  1. Requêtes paginées (20 items par page)
  2. Filtres appliqués côté serveur
  3. Cache React Query pour navigation rapide
  4. 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)

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

  1. app/dashboard/bookings/page.tsx (refactoring pagination)
  2. lib/api/csv-bookings.ts (vérifier support filtres serveur)
  3. 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.