Builders Academy
Clean Architecture — Découplage et Testabilité
2 of 2

Chapitre 1 · Leçon 2

Express + Prisma

Les adapter sans coupler le domaine aux frameworks

Section 1

Repository Pattern avec Prisma

Step 1

Implémenter le Repository avec Prisma

L'implémentation Prisma du repository vit dans la couche infrastructure. Le domaine ne voit que l'interface — on peut remplacer Prisma par MongoDB sans changer un seul Use Case.

PrismaUserRepository.ts

typescript
1// infrastructure/repositories/PrismaUserRepository.ts
2import type { PrismaClient } from "@prisma/client";
3import type { IUserRepository } from "@/domain/repositories/IUserRepository";
4import type { User } from "@/domain/entities/User";
5
6export class PrismaUserRepository implements IUserRepository {
7  constructor(private readonly prisma: PrismaClient) {}
8
9  async findByEmail(email: string): Promise<User | null> {
10    const record = await this.prisma.user.findUnique({
11      where: { email },
12    });
13    if (!record) return null;
14    return this.toDomain(record);
15  }
16
17  async save(user: User): Promise<User> {
18    const record = await this.prisma.user.create({
19      data: {
20        id: user.id,
21        firstName: user.firstName,
22        lastName: user.lastName,
23        email: user.email,
24        password: user.password,
25        createdAt: user.createdAt,
26      },
27    });
28    return this.toDomain(record);
29  }
30
31  // Mapper Prisma model → Domain entity
32  private toDomain(record: PrismaUser): User {
33    return User.reconstitute({
34      id: record.id,
35      firstName: record.firstName,
36      lastName: record.lastName,
37      email: record.email,
38      password: record.password,
39      createdAt: record.createdAt,
40    });
41  }
42}

Step 2

Controller Express — adapter HTTP au Use Case

Le controller traduit la requête HTTP en appel de Use Case, et la réponse du Use Case en réponse HTTP. Il ne contient aucune logique métier.

PrismaUserRepository.ts

typescript
1// infrastructure/repositories/PrismaUserRepository.ts
2import type { PrismaClient } from "@prisma/client";
3import type { IUserRepository } from "@/domain/repositories/IUserRepository";
4import type { User } from "@/domain/entities/User";
5
6export class PrismaUserRepository implements IUserRepository {
7  constructor(private readonly prisma: PrismaClient) {}
8
9  async findByEmail(email: string): Promise<User | null> {
10    const record = await this.prisma.user.findUnique({
11      where: { email },
12    });
13    if (!record) return null;
14    return this.toDomain(record);
15  }
16
17  async save(user: User): Promise<User> {
18    const record = await this.prisma.user.create({
19      data: {
20        id: user.id,
21        firstName: user.firstName,
22        lastName: user.lastName,
23        email: user.email,
24        password: user.password,
25        createdAt: user.createdAt,
26      },
27    });
28    return this.toDomain(record);
29  }
30
31  // Mapper Prisma model → Domain entity
32  private toDomain(record: PrismaUser): User {
33    return User.reconstitute({
34      id: record.id,
35      firstName: record.firstName,
36      lastName: record.lastName,
37      email: record.email,
38      password: record.password,
39      createdAt: record.createdAt,
40    });
41  }
42}

Topics and skills covered

  • Clean Architecture
  • Express
  • Prisma
  • Repository Pattern