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
| Composant | Role | Exemples |
|---|---|---|
| Controllers | Point d'entree HTTP, routing | MoviesController, AuthController |
| Guards | Authentification, RBAC, Tenant, Kids | JwtAuthGuard, RolesGuard, TenantGuard |
| Interceptors | Transformation requete/reponse | I18nInterceptor, LoggingInterceptor |
| Pipes | Validation et transformation | ValidationPipe, ParseUUIDPipe |
| Modules | Organisation NestJS | 72 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 :
- RateLimitGuard : Verifie les quotas par profil (Redis)
- JwtAuthGuard : Valide le token JWT d'acces
- TenantGuard : Resout le tenant depuis header/subdomain/domain
- RolesGuard : Verifie les permissions RBAC (USER, ADMIN, SUPER_ADMIN)
- 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
| Composant | Role | Exemples |
|---|---|---|
| Services | Logique metier, orchestration | MoviesService, AuthService, StreamingService |
| DTOs | Objets de transfert de donnees | CreateMovieDto, MovieResponseDto |
| Validators | Regles de validation custom | Decorateurs 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
| Composant | Role | Exemples |
|---|---|---|
| Entities | Modeles metier avec comportement | Movie, Account, Profile |
| Interfaces | Contrats pour les repositories/services | IMovieRepository, ICacheService |
| Value Objects | Objets immuables sans identite | EmailAddress, 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
| Composant | Technologie | Role |
|---|---|---|
| PrismaService | Prisma 6.19 | ORM et acces PostgreSQL (73 modeles) |
| RedisService | ioredis | Cache, sessions, rate limiting |
| StorageService | AWS SDK (R2) | Upload/download medias |
| TMDbService | REST API | Auto-fill metadonnees films/series |
| FirebaseService | Admin SDK 13.x | Verification tokens sociaux |
| MetricsService | prom-client | Metriques applicatives Prometheus |
| I18nService | Custom | Traductions 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
- Respecter la regle de dependance : Jamais de reference directe d'une couche interne vers une couche externe
- Un module = un dossier dans chaque couche (DTO, Service, Controller, Module)
- Toujours utiliser les alias
@application/,@infrastructure/, etc. - Les Services ne connaissent pas Express/HTTP : Pas de
Request,Responsedans les services - Les DTOs ont des decorateurs :
@ApiProperty,@IsString,@IsOptional, etc. - Les Guards sont declaratifs :
@UseGuards()au niveau du controller ou de la methode
References
- Vue d'ensemble -- Architecture globale avec diagrammes C4
- Schema Base de Donnees -- 73 modeles Prisma
- Diagrammes -- Tous les diagrammes Mermaid
- NestJS Documentation
- Clean Architecture (Uncle Bob)