xpeditis2.0/docs/architecture/backend.md
2026-05-14 21:11:54 +02:00

181 lines
6.0 KiB
Markdown

# Architecture Backend — NestJS Hexagonale
---
## Structure des couches
```
apps/backend/src/
├── domain/ # Logique métier PURE — aucun import NestJS/TypeORM
│ ├── entities/ # 17 entités métier (private constructor + static create())
│ ├── value-objects/ # Objets valeur immuables (Email, Money, BookingNumber...)
│ ├── services/ # Services métier purs (csv-rate-price-calculator)
│ ├── exceptions/ # Exceptions domaine
│ └── ports/
│ ├── in/ # Interfaces use-case (execute())
│ └── out/ # Interfaces repository/SPI (token constants)
├── application/ # Couche API — dépend UNIQUEMENT du domain
│ ├── controllers/ # REST controllers (Swagger + class-validator)
│ ├── dto/ # Data Transfer Objects
│ ├── mappers/ # Domain ↔ DTO (méthodes statiques)
│ ├── guards/ # JwtAuthGuard, RolesGuard, ApiKeyOrJwtGuard
│ ├── decorators/ # @Public(), @Roles(), @CurrentUser()
│ ├── filters/ # DomainExceptionFilter
│ ├── interceptors/ # PerformanceMonitoringInterceptor
│ ├── gateways/ # WebSocket Socket.IO (notifications)
│ ├── services/ # Services applicatifs (audit, notification, export, webhooks)
│ └── [feature]/ # Modules par feature (auth, bookings, rates, etc.)
└── infrastructure/ # Adaptateurs externes — dépend UNIQUEMENT du domain
├── persistence/typeorm/
│ ├── entities/ # ORM entities (*.orm-entity.ts)
│ ├── repositories/# Implémentations TypeORM
│ ├── mappers/ # ORM ↔ Domain (static toOrm/toDomain/toDomainMany)
│ └── migrations/ # Migrations TypeORM
├── carriers/ # 5 connecteurs carriers + CSV loader
├── cache/ # Adaptateur Redis
├── email/ # MJML + Nodemailer
├── storage/ # S3/MinIO (+ csv-storage/)
├── stripe/ # Paiements et abonnements
├── external/ # Pappers (SIRET), ECU Worldwide
├── pdf/ # Génération PDF pdfkit
├── security/ # Utilitaires sécurité
└── monitoring/ # Sentry + Pino
```
---
## Entités domaine (17)
| Entité | Fichier |
|--------|---------|
| ApiKey | api-key.entity.ts |
| AuditLog | audit-log.entity.ts |
| BlogPost | blog-post.entity.ts |
| Booking | booking.entity.ts |
| Carrier | carrier.entity.ts |
| Container | container.entity.ts |
| CsvBooking | csv-booking.entity.ts |
| CsvRate | csv-rate.entity.ts |
| InvitationToken | invitation-token.entity.ts |
| License | license.entity.ts |
| Notification | notification.entity.ts |
| Organization | organization.entity.ts |
| Port | port.entity.ts |
| RateQuote | rate-quote.entity.ts |
| Subscription | subscription.entity.ts |
| User | user.entity.ts |
| Webhook | webhook.entity.ts |
---
## Pattern entité domaine
```typescript
// Private constructor + static create() factory
export class Booking {
private constructor(private readonly props: BookingProps) {}
static create(props: Omit<BookingProps, 'bookingNumber' | 'status'>): Booking {
return new Booking({
...props,
bookingNumber: BookingNumber.generate(),
status: BookingStatus.DRAFT,
});
}
// Les mutations retournent une nouvelle instance (immuabilité)
updateStatus(newStatus: BookingStatus): Booking {
return new Booking({ ...this.props, status: newStatus });
}
}
```
---
## Pattern repository
```typescript
// domain/ports/out/booking.repository.ts
export const BOOKING_REPOSITORY = 'BookingRepository';
export interface BookingRepository {
findById(id: string): Promise<Booking | null>;
save(booking: Booking): Promise<Booking>;
findByOrganization(orgId: string, filters: BookingFilters): Promise<Booking[]>;
}
// infrastructure/persistence/typeorm/repositories/typeorm-booking.repository.ts
@Injectable()
export class TypeOrmBookingRepository implements BookingRepository {
constructor(
@InjectRepository(BookingOrmEntity)
private readonly repo: Repository<BookingOrmEntity>,
) {}
// ...
}
```
---
## Conventions de nommage
| Couche | Suffix | Exemple |
|--------|--------|---------|
| Domain entity | .entity.ts | booking.entity.ts |
| Value object | .vo.ts | money.vo.ts |
| In port | .use-case.ts | create-booking.use-case.ts |
| Out port | .repository.ts | booking.repository.ts |
| ORM entity | .orm-entity.ts | booking.orm-entity.ts |
| ORM mapper | .mapper.ts | booking.mapper.ts |
| Controller | .controller.ts | bookings.controller.ts |
| DTO | .dto.ts | create-booking-request.dto.ts |
| App mapper | .mapper.ts | booking.mapper.ts |
---
## Règles critiques
- **Jamais** d'import NestJS/TypeORM dans `domain/`
- **Jamais** de type `any` dans le backend (strict mode)
- **Jamais** modifier une migration déjà appliquée
- **Toujours** valider les DTOs avec class-validator
- **Toujours** créer un mapper séparé pour ORM ↔ Domain
- `DATABASE_SYNC` est hard-codé à `false` — utiliser les migrations
---
## Modules NestJS (app.module.ts)
**Guards globaux** (toutes routes) :
- `JwtAuthGuard` — JWT obligatoire sauf routes `@Public()`
- `CustomThrottlerGuard` — rate limiting
**Feature modules** :
Auth · Rates · Ports · Bookings · CsvBookings · Organizations · Users · Dashboard · Audit · Notifications · Webhooks · GDPR · Admin · Subscriptions · ApiKeys · Blog · Logs
**Infrastructure modules** :
CacheModule · CarrierModule · SecurityModule · CsvRateModule · StripeModule · PdfModule · StorageModule · EmailModule
---
## Alias de chemins (tsconfig.json)
```json
{
"@domain/*": ["src/domain/*"],
"@application/*": ["src/application/*"],
"@infrastructure/*": ["src/infrastructure/*"]
}
```
---
## Configuration TypeScript
- `strict: true`
- `strictNullChecks: true`
- `strictPropertyInitialization: false` (exception pour ORM entities)
- `noImplicitAny: true`