Architecture en couches

Donner des noms à ce que vous faites déjà

Bootcode IWA-S04 — Semaine 16, Jour 1

Objectifs de la leçon

1. Identifier les 3 couches

Dans un code existant

2. Classer chaque fichier

Dans la bonne couche

3. Comprendre la règle de dépendance

Le domaine ne dépend de RIEN

4. Documenter l'architecture

De son propre projet

Plan du cours

1

Aucun nouveau concept

On donne des NOMS à ce que vous faites déjà

2

La correspondance

routes/ = Controller, services/ = Use Case, storage/ = Repository

3

Les 3 couches

API, Domain, Infrastructure

4

Le flux de données

Controller → Use Case → Repository → Database

5

La règle de dépendance

Le domaine ne dépend de RIEN

6

Correspondance Bootcode

core/ = domain, adapters/ = infra

Module 1

Rassurez-vous !

Vous connaissez déjà tout — on donne juste des noms

Cette semaine n'introduit RIEN de nouveau

On formalise l'existant

Vous faites de l'architecture en couches depuis des semaines sans le savoir

📁

routes/

= Controller

⚙️

services/

= Use Case

💾

storage/

= Repository

💡 L'objectif du jour

Reconnaître vos propres fichiers et leur donner leur nom officiel

Module 2

Les 3 couches

API · Domain · Infrastructure

Les 3 couches en détail

🌐 API — Controllers

Reçoit la requête HTTP, valide l'entrée, renvoie la réponse

Connaît Express, mais ne contient AUCUNE logique métier

🧠 Domain — Use Cases

La logique métier pure, sans aucune dépendance externe

Ne connaît ni Express, ni TypeORM, ni la base de données

💾 Infrastructure — Repositories

Implémente la persistance : PostgreSQL, TypeORM, mappers

S'adapte aux besoins définis par le domaine

🌐 La couche API

Le point d'entrée — ce que le client voit

// api/controllers/articleController.ts

import { Router } from "express";

import { CreateArticle } from "../../domain/usecases/CreateArticle";

export class ArticleController {

constructor(private createArticle: CreateArticle) {}

create = async (req, res) => {

const result = await this.createArticle.execute(req.body);

res.status(201).json(result);

};

}

💡 Le controller ne fait QUE : valider → appeler le use case → renvoyer

Aucune logique métier ici ! Pas de calculs, pas de règles

🧠 La couche Domain

Le cœur — la logique métier pure

// domain/usecases/CreateArticle.ts

import { ArticleRepository } from "../repositories/ArticleRepository";

export class CreateArticle {

constructor(private repo: ArticleRepository) {}

async execute(input: { title: string; content: string }) {

if (!input.title) throw new Error("Titre requis");

const article = { id: crypto.randomUUID(), ...input };

await this.repo.save(article);

return article;

}

}

💡 Aucun import d'Express ou TypeORM ici !

Le domaine ne connaît que ses propres interfaces et modèles

💾 La couche Infrastructure

Les détails techniques — la base de données

// infra/repositories/PostgresArticleRepository.ts

import { DataSource } from "typeorm";

import { ArticleRepository } from "../../domain/repositories/ArticleRepository";

import { ArticleEntity } from "../entities/ArticleEntity";

export class PostgresArticleRepository implements ArticleRepository {

constructor(private db: DataSource) {}

async save(article: Article): Promise<void> {

await this.db.getRepository(ArticleEntity).save(article);

}

}

💡 L'infra implémente l'interface définie par le domaine

C'est l'infra qui s'adapte au domaine, pas l'inverse

Module 3

Le flux de données

Comment une requête traverse les couches

Le flux d'une requête

1️⃣

Requête HTTP arrive

POST /articles avec { title, content }

2️⃣

Controller reçoit

Valide l'entrée, appelle le use case

3️⃣

Use Case exécute la logique

Règles métier, appelle le repository

4️⃣

Repository persiste

Sauvegarde en base de données

Le flux en code TypeScript

// 1️⃣ Controller — api/controllers/articleController.ts

async create(req, res) {

const result = await this.createArticle.execute(req.body);

res.json(result);

}

// 2️⃣ Use Case — domain/usecases/CreateArticle.ts

async execute(input) {

const article = { id: crypto.randomUUID(), ...input };

await this.repo.save(article);

return article;

}

// 3️⃣ Repository — infra/repositories/PostgresArticleRepository.ts

async save(article) {

await this.db.save(article);

}

💡 Chaque couche ne connaît que la couche en dessous

Le controller ne sait pas comment le repo sauvegarde — il s'en fiche !

Module 4

La règle de dépendance

Le domaine ne dépend de RIEN

La règle d'or

Le domaine ne dépend de RIEN

Pas d'Express, pas de TypeORM, pas de PostgreSQL

✅ Le domaine DÉFINIT ses besoins

Il crée les interfaces que l'infra doit implémenter

❌ Le domaine ne S'ADAPTE pas

Il ne change pas pour plaire à TypeORM ou Express

💡 C'est le Dependency Inversion

L'infra dépend du domaine, jamais l'inverse

Direction des dépendances

🌐 API (Controller)

Dépend du Domain

🧠 Domain (Use Case)

Ne dépend de RIEN (que de lui-même)

↑ ← l'infra dépend de lui

💾 Infrastructure (Repository)

Dépend du Domain (implémente ses interfaces)

💡 Les flèches pointent VERS le domaine

Tout le monde dépend du domaine, le domaine ne dépend de personne

L'assemblage : main.ts

Le SEUL fichier qui connaît toutes les couches

// main.ts — le point d'assemblage

import { DataSource } from "typeorm";

import { ArticleController } from "./api/controllers/ArticleController";

import { CreateArticle } from "./domain/usecases/CreateArticle";

import { PostgresArticleRepository } from "./infra/repositories/PostgresArticleRepository";

// 1. Créer l'infrastructure

const db = new DataSource({ ... });

const repo = new PostgresArticleRepository(db);

// 2. Créer le domaine (avec ses dépendances)

const createArticle = new CreateArticle(repo);

// 3. Créer l'API

const controller = new ArticleController(createArticle);

💡 main.ts est le SEUL endroit qui connaît tout

C'est lui qui "branche" les implémentations aux interfaces

Module 5

Correspondance Bootcode

Vos fichiers = les couches

Votre codebase = l'architecture

Couche

Votre dossier

Bootcode monorepo

API

routes/

adapters/api/

Domain

core/

core/

Infrastructure

adapters/

adapters/repositories/

💡 Ouvrez votre projet maintenant

Identifiez vos fichiers et dites : "ça c'est un Controller, ça c'est un Use Case"

Pièges courants à éviter

❌ Penser que c'est une NOUVELLE architecture à apprendre

✅ Rassurez-vous : vous la faites déjà ! On donne juste des noms

❌ Confondre "domain" et "domaine métier"

✅ Le domain = la logique pure, sans aucune dépendance technique

❌ Mettre l'interface du repository dans infra/

✅ L'interface va dans domain/ — c'est le domaine qui définit ses besoins

Où ranger ce fichier ?

❌ L'erreur

domain/

usecases/

repositories/

PostgresArticleRepo.ts ❌

infra/

ArticleRepository.ts ❌

L'implémentation dans le domaine, l'interface dans l'infra

✅ La solution

domain/

usecases/

repositories/

ArticleRepository.ts ✅ (interface)

infra/

PostgresArticleRepo.ts ✅ (implémentation)

L'interface dans le domaine, l'implémentation dans l'infra

Points clés à retenir

📌 Rien de nouveau — on formalise l'existant

📌 Le domaine est au centre, indépendant

📌 L'interface du repository vit dans domain/

📌 main.ts est le seul fichier qui connaît tout

À retenir !

🏗️

3 couches

API · Domain · Infrastructure

🧠

Domain = indépendant

Pas d'Express, pas de TypeORM

🔌

main.ts = l'assemblage

Le seul fichier qui connecte tout

Vous le faites déjà !

On donne juste des noms formels

Questions ?

Ouvrez votre projet et identifiez vos 3 couches

Demain : Le pattern Repository en détail