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

6.0 KiB

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

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

// 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)

{
  "@domain/*": ["src/domain/*"],
  "@application/*": ["src/application/*"],
  "@infrastructure/*": ["src/infrastructure/*"]
}

Configuration TypeScript

  • strict: true
  • strictNullChecks: true
  • strictPropertyInitialization: false (exception pour ORM entities)
  • noImplicitAny: true