POST /giraffe/users/signup à travers 8 fichiers
Bootcode IWA-S04 — Semaine 20, Jour 5
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
Le moment culminant
Tracer POST /giraffe/users/signup à travers 8 fichiers
Fichier par fichier
main.ts → setup.ts → UserController → SignUpCommand → AppKernel → SignUp → User → PostgresUserRepository
Chaque couche a un rôle unique
routing → validation → orchestration → logique métier → persistance
Les événements déclenchés
UserSignedUp → handlers asynchrones
Synthèse
Tout ce qu'ils ont appris en 5 mois tient dans ce flux
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. »
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
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.
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.
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;
}
}
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.
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
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.
Routing (main.ts, setup.ts)
Recevoir la requête, identifier le controller
Validation (SignUpCommand, class-validator)
Vérifier que les données sont valides avant toute logique
Orchestration (UserController, SignUp)
Coordonner les étapes, aucune logique métier
Logique métier (User agrégat)
Invariants, règles, événements de domaine
Persistance (PostgresUserRepository)
Stocker en base via TypeORM
❌ 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.
✅ 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 ! 🎉