# 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

- [Hexagonal Architecture - Alistair Cockburn](https://alistair.cockburn.us/hexagonal-architecture/)
- [docs/architecture.md](./architecture.md)
- [CLAUDE.md](../CLAUDE.md)

---

## 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

```typescript
// 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

```typescript
// 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

```typescript
// 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**:
```bash
# 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

- [docs/backend/cleanup-report.md](./backend/cleanup-report.md)
- [Hexagonal Architecture Principles](https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/)

---

## 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**:
```typescript
// ❌ 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`:

```json
{
  "compilerOptions": {
    "strict": true
  }
}
```

Ou activer les flags individuellement:
```json
{
  "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**

```bash
# 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
```typescript
// 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
```typescript
// AVANT
const name = user.organization.name;

// APRÈS
const name = user?.organization?.name;
```

**Pattern 3**: Nullish coalescing
```typescript
// AVANT
const limit = params.limit || 20;

// APRÈS
const limit = params.limit ?? 20; // Gère correctement 0
```

**Phase 3: Validation**

```bash
# 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

- [TypeScript Strict Mode Guide](https://www.typescriptlang.org/tsconfig#strict)
- [docs/frontend/cleanup-report.md](./frontend/cleanup-report.md)

---

## 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é)
```typescript
const { data } = useQuery({
  queryKey: ['dashboard', 'kpis'],
  queryFn: () => getDashboardKpis(),
});
```

**Pattern 2**: Custom hook avec fetch direct (❌ Problématique)
```typescript
const response = await fetch(`/api/v1/bookings/search`, {
  headers: { Authorization: `Bearer ${localStorage.getItem('accessToken')}` },
});
```

**Pattern 3**: API client direct dans composant (⚠️ Acceptable)
```typescript
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):
```typescript
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):
```typescript
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**:

```typescript
// 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**:
```bash
# 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

- [React Query Best Practices](https://tkdodo.eu/blog/practical-react-query)
- [docs/frontend/cleanup-report.md](./frontend/cleanup-report.md)

---

## ADR-005: Migration pagination client-side vers serveur

**Date**: 2025-12-22
**Status**: 🟡 Proposée

### Contexte

**Problème actuel**:
```typescript
// 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**:
```typescript
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**:
```typescript
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**:
```typescript
// 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**:
```bash
# 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

- [React Query Pagination Guide](https://tanstack.com/query/latest/docs/react/guides/paginated-queries)
- [docs/frontend/cleanup-report.md](./frontend/cleanup-report.md)

---

## Template pour nouvelles décisions

```markdown
## 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.
