Le flux complet

POST /giraffe/users/signup à travers 8 fichiers

Bootcode IWA-S04 — Semaine 20, Jour 5

Objectifs de la leçon

Le jour de culmination de la Session 1 — tout s'assemble.

1. Tracer une requête HTTP de bout en bout

Du client jusqu'à la base de données et retour

2. Comprendre le rôle unique de chaque couche

routing → validation → orchestration → logique métier → persistance

3. Voir les événements déclenchés en chemin

UserSignedUp → handlers asynchrones

4. Savoir quel fichier ouvrir pour modifier quel comportement

La carte mentale du codebase

Plan du cours

1

Le moment culminant

Tracer POST /giraffe/users/signup à travers 8 fichiers

2

Fichier par fichier

main.ts → setup.ts → UserController → SignUpCommand → AppKernel → SignUp → User → PostgresUserRepository

3

Chaque couche a un rôle unique

routing → validation → orchestration → logique métier → persistance

4

Les événements déclenchés

UserSignedUp → handlers asynchrones

5

Synthèse

Tout ce qu'ils ont appris en 5 mois tient dans ce flux

Le moment culminant

8 fichiers, 8 responsabilités — la Clean Architecture en action

« On va tracer POST /giraffe/users/signup de bout en bout. Tout ce que vous avez appris depuis la S8 est dans ce flux. »

La carte des 8 fichiers

L'ordre exact du parcours d'une requête.

1️⃣ main.ts

Express reçoit la requête HTTP

2️⃣ setup.ts

routing-controllers route vers UserController

3️⃣ UserController

@Post('/signup') → reçoit le body

4️⃣ SignUpCommand (DTO)

class-validator valide le body

5️⃣ AppKernel

Le container Inversify a câblé SignUp use case

6️⃣ SignUp (use case)

Orchestre : crée l'agrégat, persiste, lève l'événement

7️⃣ User (agrégat)

Logique métier : valide l'email, hash le password, raise(UserSignedUp)

8️⃣ PostgresUserRepository

Persiste l'agrégat en base via TypeORM

1️⃣ main.ts + 2️⃣ setup.ts

Express reçoit la requête, routing-controllers route vers le controller.

// main.ts — Express écoute

app.listen(port);

// setup.ts — routing-controllers a branché UserController

useExpressServer(app, {

controllers: [UserController, ...],

});

// POST /giraffe/users/signup arrive → UserController.signup()

💡 À ce stade : la requête est reçue, le bon controller est identifié. Aucune logique métier encore.

3️⃣ UserController + 4️⃣ SignUpCommand

Le controller reçoit le body, class-validator valide le DTO.

// SignUpCommand — le DTO validé

export class SignUpCommand {

@IsEmail() email: string;

@MinLength(8) password: string;

}

// UserController — orchestre

@Post('/signup')

async signup(@Body() cmd: SignUpCommand) {

return this.signUp.execute(cmd); // → use case

}

💡 Si la validation échoue → 400 automatique. Le use case n'est jamais appelé avec des données invalides.

5️⃣ AppKernel + 6️⃣ SignUp use case

Le container a câblé le use case. Le use case orchestre la logique métier.

// AppKernel a enregistré SignUp dans le container

container.bind(TYPES.SignUpUseCase).to(SignUp);

// SignUp — le use case orchestre

@injectable()

export class SignUp implements UseCase<SignUpCommand> {

constructor(

@inject(TYPES.UserRepository) private repo: UserRepository,

@inject(TYPES.Hasher) private hasher: Hasher,

) {}

async execute(cmd: SignUpCommand) {

const hashed = await this.hasher.hash(cmd.password);

const user = User.create(cmd.email, hashed); // → agrégat

await this.repo.save(user); // → persistance

return user;

}

}

7️⃣ User agrégat + 8️⃣ PostgresUserRepository

L'agrégat contient la logique métier et lève l'événement. Le repository persiste.

// User — l'agrégat (logique métier)

export class User extends AggregateRoot {

static create(email: string, hashedPassword: string) {

const user = new User(email, hashedPassword);

user.raise(new UserSignedUp(user.id)); // ← événement !

return user;

}

}

// PostgresUserRepository — persistance

export class PostgresUserRepository implements UserRepository {

async save(user: User) {

await this.ds.getRepository(UserEntity).save(user);

}

}

💡 L'agrégat ne sait pas qu'il est persisté en Postgres. Il ne connaît que ses invariants et ses événements.

UserSignedUp → handlers

L'événement voyage en arrière-plan

1️⃣ User.create() lève UserSignedUp

2️⃣ EventDispatcher persiste l'événement en queue Postgres

3️⃣ La requête HTTP répond 201 Created immédiatement

4️⃣ Le polling (3s) lit l'événement et le dispatch aux handlers

5️⃣ UserSignedUpHandler envoie l'email de bienvenue

Le flux aller-retour

Ne pas oublier la réponse — le flux est bidirectionnel.

ALLER :

main.ts → setup.ts → UserController → SignUpCommand → SignUp → User → PostgresUserRepository

RETOUR :

User (agrégat) → SignUp (use case) → UserController → 201 Created JSON

ARRIÈRE-PLAN (asynchrone) :

UserSignedUp → queue Postgres → polling → UserSignedUpHandler → email

⚠️ Piège : sauter le retour. Le client reçoit une réponse — c'est important de la montrer.

Chaque couche a un rôle unique

1️⃣

Routing (main.ts, setup.ts)

Recevoir la requête, identifier le controller

2️⃣

Validation (SignUpCommand, class-validator)

Vérifier que les données sont valides avant toute logique

3️⃣

Orchestration (UserController, SignUp)

Coordonner les étapes, aucune logique métier

4️⃣

Logique métier (User agrégat)

Invariants, règles, événements de domaine

5️⃣

Persistance (PostgresUserRepository)

Stocker en base via TypeORM

Pièges courants

❌ Aller trop vite

Prendre le temps de chaque fichier. Laisser les étudiants digérer.

⚠️ Sauter le middleware d'authentification

C'est une couche importante à comprendre — même si /signup n'est pas protégé.

❌ Ne pas montrer le retour (la réponse)

Le flux est aller-retour. Le client reçoit une réponse JSON.

À retenir !

✅ C'est le jour de culmination de la Session 1 — les étudiants voient TOUT s'assembler.

✅ 8 fichiers, 8 responsabilités différentes — c'est ça, la Clean Architecture en action.

✅ Chaque pattern appris depuis S8 (Repository, Use Case, Aggregate, DI, Events) est visible dans ce flux.

✅ Les étudiants doivent repartir en sachant exactement quel fichier ouvrir pour modifier quel comportement.

Fin de la Session 1 — bravo ! 🎉