Aller au contenu principal

Clean Architecture

MyTelevision API v2 suit les principes de Clean Architecture (Robert C. Martin) pour garantir une separation claire des responsabilites, une testabilite maximale et une independance vis-a-vis des frameworks et outils externes.


Regle de dependance

La regle fondamentale : les dependances pointent toujours vers l'interieur. Les couches externes dependent des couches internes, jamais l'inverse.

La couche Domain est au centre et ne depend de rien. La couche Infrastructure implemente les contrats definis dans le Domain.


Structure des repertoires

src/
├── presentation/ # Couche Presentation
│ ├── controllers/
│ │ ├── api/v2/ # API publique (/api/v2/*)
│ │ │ ├── auth/
│ │ │ ├── movies/
│ │ │ ├── series/
│ │ │ ├── live-tv/
│ │ │ ├── radio/
│ │ │ ├── podcasts/
│ │ │ ├── replays/
│ │ │ ├── news/
│ │ │ ├── live-events/
│ │ │ ├── favorites/
│ │ │ ├── reactions/
│ │ │ ├── views/
│ │ │ ├── profiles/
│ │ │ ├── sessions/
│ │ │ ├── devices/
│ │ │ ├── streaming/
│ │ │ └── ...
│ │ └── admin/ # API admin (/api/v2/admin/*)
│ └── modules/ # 72 modules NestJS

├── application/ # Couche Application
│ ├── dtos/
│ │ ├── movies/
│ │ ├── series/
│ │ ├── auth/
│ │ └── ...
│ └── services/
│ ├── movies/
│ ├── series/
│ ├── auth/
│ ├── streaming/
│ ├── tmdb/
│ └── ...

├── domain/ # Couche Domain
│ ├── entities/
│ ├── interfaces/
│ └── value-objects/

├── infrastructure/ # Couche Infrastructure
│ ├── cache/ # Service Redis
│ ├── config/ # Modules de configuration
│ │ ├── app/
│ │ ├── database/
│ │ ├── redis/
│ │ ├── jwt/
│ │ ├── storage/
│ │ ├── tmdb/
│ │ ├── firebase/
│ │ ├── streaming/
│ │ └── i18n/
│ ├── database/ # Prisma ORM
│ │ └── prisma/
│ ├── guards/ # Guards de securite
│ ├── i18n/ # Internationalisation
│ ├── metrics/ # Metriques Prometheus
│ └── storage/ # Stockage Cloudflare R2

└── shared/ # Constantes, types, utilitaires

Couche Presentation

La couche la plus externe, responsable de l'interaction avec le monde exterieur via HTTP.

Responsabilites

  • Routing HTTP : Definition des 587 routes REST
  • Validation des entrees : Via class-validator et Validation Pipes NestJS
  • Transformation des reponses : Serialisation, localisation (i18n)
  • Documentation Swagger : Decorateurs OpenAPI automatiques
  • Securite HTTP : Chaine de Guards d'authentification et d'autorisation

Composants

ComposantRoleExemples
ControllersPoint d'entree HTTP, routingMoviesController, AuthController
GuardsAuthentification, RBAC, Tenant, KidsJwtAuthGuard, RolesGuard, TenantGuard
InterceptorsTransformation requete/reponseI18nInterceptor, LoggingInterceptor
PipesValidation et transformationValidationPipe, ParseUUIDPipe
ModulesOrganisation NestJS72 modules au total

Exemple : Controller

@Controller('api/v2/movies')
@ApiTags('Movies')
export class MoviesController {
constructor(private readonly moviesService: MoviesService) {}

@Get(':id')
@ApiOperation({ summary: 'Get movie by ID' })
@ApiParam({ name: 'id', type: 'string', format: 'uuid' })
@ApiQuery({ name: 'lang', required: false, enum: ['fr_FR', 'en_US'] })
async findById(
@Param('id', ParseUUIDPipe) id: string,
@Locale() locale: SupportedLocale,
): Promise<MovieResponseDto> {
const result = await this.moviesService.findById(id);
return deepLocalizeObject(result, locale);
}
}

Chaine de Guards

L'ordre d'execution des Guards est crucial pour la securite :

  1. RateLimitGuard : Verifie les quotas par profil (Redis)
  2. JwtAuthGuard : Valide le token JWT d'acces
  3. TenantGuard : Resout le tenant depuis header/subdomain/domain
  4. RolesGuard : Verifie les permissions RBAC (USER, ADMIN, SUPER_ADMIN)
  5. ProfileRestrictionGuard : Applique les restrictions parentales (Kids)

Couche Application

Contient la logique metier et orchestre les operations. C'est le coeur fonctionnel de l'application.

Responsabilites

  • Orchestration des operations : Coordonne les appels entre services
  • Validation metier : Regles business au-dela de la validation de format
  • Transformation des donnees : Conversion entre entites et DTOs

Composants

ComposantRoleExemples
ServicesLogique metier, orchestrationMoviesService, AuthService, StreamingService
DTOsObjets de transfert de donneesCreateMovieDto, MovieResponseDto
ValidatorsRegles de validation customDecorateurs class-validator personnalises

Exemple : Service

@Injectable()
export class MoviesService {
constructor(
private readonly prisma: PrismaService,
private readonly redisService: RedisService,
) {}

async findById(id: string): Promise<Movie | null> {
// 1. Verifier le cache Redis
const cached = await this.redisService.get(`movie:${id}`);
if (cached) return JSON.parse(cached);

// 2. Requete base de donnees via Prisma
const movie = await this.prisma.movie.findUnique({
where: { id },
include: { genres: true, countries: true },
});

// 3. Mise en cache (TTL: 300s)
if (movie) {
await this.redisService.set(`movie:${id}`, JSON.stringify(movie), 300);
}

return movie;
}
}

Exemple : DTO avec validation

export class CreateMovieDto {
@ApiProperty({ description: 'Titre du film (francais)' })
@IsString()
@MinLength(1)
title: string;

@ApiPropertyOptional({ description: 'Titre du film (anglais)' })
@IsString()
@IsOptional()
titleEn?: string;

@ApiPropertyOptional({ description: 'Synopsis' })
@IsString()
@IsOptional()
overview?: string;

@ApiPropertyOptional({ description: 'Statut du contenu' })
@IsEnum(ContentStatus)
@IsOptional()
status?: ContentStatus;
}

Couche Domain

Le coeur du systeme. Contient les entites metier et les contrats d'interface. Cette couche n'a aucune dependance vers les couches externes.

Responsabilites

  • Definition des entites : Modeles metier avec leurs regles
  • Regles metier invariantes : Contraintes business toujours valides
  • Contrats d'interface : Interfaces que l'infrastructure implemente

Composants

ComposantRoleExemples
EntitiesModeles metier avec comportementMovie, Account, Profile
InterfacesContrats pour les repositories/servicesIMovieRepository, ICacheService
Value ObjectsObjets immuables sans identiteEmailAddress, Money, DateRange

Exemple : Interface (Contract)

export interface IMovieRepository {
findById(id: string): Promise<Movie | null>;
findAll(filters: MovieFilters): Promise<PaginatedResult<Movie>>;
create(data: CreateMovieData): Promise<Movie>;
update(id: string, data: UpdateMovieData): Promise<Movie>;
delete(id: string): Promise<void>;
}

Couche Infrastructure

Implemente les details techniques. C'est la couche qui connecte le systeme au monde reel.

Responsabilites

  • Acces base de donnees : Prisma ORM avec 73 modeles
  • Cache distribue : Redis pour sessions, cache et rate limiting
  • Stockage fichiers : Cloudflare R2 (compatible S3)
  • APIs externes : TMDb, Firebase, Payment providers
  • Metriques : Exposition Prometheus
  • Internationalisation : Service i18n bilingue (fr_FR / en_US)

Composants

ComposantTechnologieRole
PrismaServicePrisma 6.19ORM et acces PostgreSQL (73 modeles)
RedisServiceioredisCache, sessions, rate limiting
StorageServiceAWS SDK (R2)Upload/download medias
TMDbServiceREST APIAuto-fill metadonnees films/series
FirebaseServiceAdmin SDK 13.xVerification tokens sociaux
MetricsServiceprom-clientMetriques applicatives Prometheus
I18nServiceCustomTraductions et localisation

Exemple : Guard d'authentification

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
return super.canActivate(context);
}
}

Exemple : Interceptor i18n

@Injectable()
export class I18nInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
const locale = this.detectLocale(context);
return next
.handle()
.pipe(map((data) => this.localizeResponse(data, locale)));
}
}

Path Aliases

Le projet utilise des alias TypeScript pour simplifier les imports entre couches :

// tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["src/*"],
"@domain/*": ["src/domain/*"],
"@application/*": ["src/application/*"],
"@infrastructure/*": ["src/infrastructure/*"],
"@presentation/*": ["src/presentation/*"],
"@shared/*": ["src/shared/*"]
}
}
}

Utilisation :

import { MoviesService } from '@application/services/movies';
import { PrismaService } from '@infrastructure/database/prisma';
import { JwtAuthGuard } from '@infrastructure/guards';
import { Locale, SupportedLocale } from '@infrastructure/i18n';

Patterns implementes

Repository Pattern (via Prisma)

Prisma agit comme couche d'abstraction de la base de donnees. Les services interagissent avec Prisma, jamais directement avec SQL.

@Injectable()
export class MoviesService {
constructor(private readonly prisma: PrismaService) {}

async findOne(id: string): Promise<Movie | null> {
return this.prisma.movie.findUnique({
where: { id },
include: { genres: true },
});
}
}

DTO Pattern

Les DTOs assurent la validation des entrees (class-validator) et la documentation des API (@nestjs/swagger). Chaque endpoint utilise des DTOs distincts pour la creation, la mise a jour et la reponse.

Guard Pattern

Les Guards NestJS implementent la chaine de securite de maniere declarative :

@Controller('admin/movies')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('ADMIN', 'SUPER_ADMIN')
export class AdminMoviesController {
// Tous les endpoints sont proteges par JWT + Role ADMIN
}

Interceptor Pattern

Les Interceptors transforment les requetes/reponses de maniere transversale, sans modifier la logique metier :

  • I18nInterceptor : Detection automatique de la locale, localisation des reponses
  • LoggingInterceptor : Logging structure des requetes/reponses

Module Pattern (NestJS)

Chaque fonctionnalite suit le pattern module NestJS standardise :

1. DTO definitions       -> application/dtos/{feature}/
2. Service -> application/services/{feature}/
3. Controller -> presentation/controllers/{feature}/
4. Presentation Module -> presentation/modules/{feature}/

Flux de requete complet


Regles de developpement

  1. Respecter la regle de dependance : Jamais de reference directe d'une couche interne vers une couche externe
  2. Un module = un dossier dans chaque couche (DTO, Service, Controller, Module)
  3. Toujours utiliser les alias @application/, @infrastructure/, etc.
  4. Les Services ne connaissent pas Express/HTTP : Pas de Request, Response dans les services
  5. Les DTOs ont des decorateurs : @ApiProperty, @IsString, @IsOptional, etc.
  6. Les Guards sont declaratifs : @UseGuards() au niveau du controller ou de la methode

References