Comment un gros projet se découpe en domaines indépendants
Bootcode IWA-S04 — Semaine 17, Jour 4
1. Naviguer dans un monorepo
Organisé en bounded contexts
2. Identifier les briques DDD
Agrégats, repositories, use cases
3. Comprendre les interactions
Entre contextes par événements
Le problème
50 agrégats dans un seul dossier = ingérable
Qu'est-ce qu'un Bounded Context ?
Un département indépendant de l'entreprise logicielle
Le monorepo Bootcode
IAC · Education · Notifications · Social · Giraffe
User ≠ Student
Deux classes différentes — et c'est voulu
Communication entre contextes
Par Domain Events, pas par imports directs
Module 1
Quand tout est dans un seul dossier, ça devient ingérable
Imaginez un projet qui grossit…
Au début, tout est simple. Puis les fonctionnalités s'empilent. Un seul dossier core/ contient TOUT.
// core/ — un seul dossier pour TOUT le domaine 😱
core/
User.ts
Student.ts
Course.ts
Enrollment.ts
Notification.ts
Post.ts
Comment.ts
Giraffe.ts
Invoice.ts
Payment.ts
… et 40 autres fichiers 😵
💡 Le problème
On ne sait plus qui dépend de qui. Changer un fichier peut casser n'importe quoi. Le cerveau humain ne peut plus suivre.
Module 2
Un département indépendant de l'entreprise logicielle
Dans une entreprise, chaque département a son propre vocabulaire et ses propres règles
🏥
Comptabilité
"Facture", "TVA", "Exercice fiscal"
📦
Logistique
"Colis", "Bon de livraison", "Tournée"
👥
RH
"Salarié", "Congés", "Bulletin de paie"
💡 L'analogie
Un Bounded Context = un département. Il a ses propres modèles, son vocabulaire, ses règles. Il ne mélange pas avec les autres.
L'Ubiquitous Language revisitée : le même mot peut avoir un sens différent selon le contexte
📋 Contexte "Education"
"User" = un étudiant qui suit des cours
Propriétés : enrollments, progress, certificates
🔐 Contexte "IAC"
"User" = un compte d'authentification
Propriétés : email, passwordHash, roles
💡 Le mot "User" n'est pas universel
Dans IAC, on parle de connexion. Dans Education, on parle d'apprentissage. Deux mondes, deux modèles.
Module 3
5 bounded contexts en pratique
🔐
IAC
Identity & Access Control
Auth, users, roles, sessions
🎓
Education
Cours & apprentissage
Courses, students, enrollments
🔔
Notifications
Emails & alertes
Templates, delivery, logs
💬
Social
Posts & communauté
Posts, comments, likes
🦒
Giraffe
L'outil pédagogique
Exercises, grading, feedback
💡 Chaque contexte = un département
Ils vivent dans le même monorepo mais sont indépendants. Chacun a son propre domaine métier.
Chaque contexte suit la MÊME structure — on s'y retrouve partout
🧠 core/
Le domaine pur
Agrégats, use cases, interfaces de repositories
🔌 adapters/
L'infrastructure
Implémentations repositories, API HTTP, DB
📡 messages/
Les événements
Domain Events pour communiquer entre contextes
💡 On retrouve ses marques partout
Que vous soyez dans IAC ou dans Giraffe, la structure est la même. Le cerveau n'a qu'un seul plan à mémoriser.
contexts/education/
core/
aggregates/
Student.ts
Course.ts
Enrollment.ts
usecases/
EnrollStudent.ts
CompleteCourse.ts
repositories/
StudentRepository.ts // interface
adapters/
api/
EnrollmentController.ts
repositories/
PostgresStudentRepository.ts
messages/
events/
StudentEnrolled.ts
CourseCompleted.ts
💡 Cette structure se répète pour chaque contexte
IAC, Notifications, Social, Giraffe : même squelette, contenus différents
Module 4
Deux classes différentes — et c'est voulu
Le même humain, deux modèles différents
Une personne qui s'inscrit à un cours est un "User" pour l'auth et un "Student" pour l'éducation. Deux classes, deux responsabilités.
🔐 IAC — User
Ce qui compte : l'identité
🎓 Education — Student
Ce qui compte : l'apprentissage
💡 C'est INTENTIONNEL
Chaque contexte ne garde que ce qui le concerne. IAC ignore les cours. Education ignore les mots de passe.
// contexts/iac/core/User.ts
export class User {
constructor(
private id: string,
private email: string,
private passwordHash: string,
private roles: string[]
) {}
canAccess(resource: string): boolean {
return this.roles.includes("admin");
}
}
// contexts/education/core/Student.ts
export class Student {
constructor(
private id: string,
private enrollments: Enrollment[],
private certificates: string[]
) {}
isEnrolledIn(courseId: string): boolean {
return this.enrollments
.some(e => e.courseId === courseId);
}
}
💡 Deux classes, zéro lien entre elles
User ne sait pas ce qu'est un cours. Student ne sait pas ce qu'est un mot de passe. Indépendance totale.
❌ Penser que c'est un bug
"Pourquoi on a deux classes User ? On devrait factoriser en une seule !"
→ On mélange l'auth et l'éducation → couplage → chaos
✅ Comprendre que c'est intentionnel
"Chaque contexte a son propre modèle. User (IAC) et Student (Education) servent des buts différents."
→ Indépendance → clarté → évolutivité
💡 La règle d'or
Ne JAMAIS partager un agrégat entre contextes. Chaque contexte a ses propres modèles, point.
Module 5
Par Domain Events, pas par imports directs
Pas d'import direct entre contextes
IAC ne peut pas importer Student depuis Education. À la place, il publie un événement et Education réagit.
IAC publie un événement
"UserRegistered" avec l'email et l'ID
Education écoute et réagit
Crée un Student avec cet ID, prêt à s'inscrire à des cours
Notifications écoute aussi
Envoie un email de bienvenue au nouvel utilisateur
💡 Le principe
L'événement est le SEUL pont entre contextes. Celui qui publie ne sait pas qui écoute. Couplage minimal.
// 1️⃣ IAC publie un événement
// contexts/iac/messages/events/UserRegistered.ts
export class UserRegistered {
constructor(public userId: string, public email: string) {}
}
// contexts/iac/core/usecases/RegisterUser.ts
async execute(input) {
const user = await this.repo.save(input);
await this.eventBus.publish(
new UserRegistered(user.id, user.email)
);
}
// 2️⃣ Education réagit à l'événement
// contexts/education/adapters/handlers/OnUserRegistered.ts
@EventHandler(UserRegistered)
async handle(event: UserRegistered) {
const student = new Student(event.userId, [], []);
await this.studentRepo.save(student);
}
💡 IAC ne sait pas qu'Education existe
Il publie juste "UserRegistered". Ceux qui veulent réagir s'abonnent. Couplage = zéro.
❌ Import direct
// iac/core/usecases/RegisterUser.ts
import { Student } from
"../../education/core/Student";
// IAC dépend d'Education 😱
const student = new Student(...);
Couplage fort : changer Education casse IAC
✅ Communication par événement
// iac/core/usecases/RegisterUser.ts
await this.eventBus.publish(
new UserRegistered(id, email)
);
// IAC ne connaît pas Education ✅
Couplage faible : chaque contexte évolue librement
❌ Penser que deux classes "User" sont un bug
✅ C'est intentionnel : chaque contexte a son propre modèle adapté à son métier
❌ Confondre bounded context et dossier technique
✅ Un bounded context = un domaine métier, pas un regroupement technique (ex: "utils/", "helpers/")
❌ Partager un agrégat entre contextes
✅ Chaque contexte a ses propres modèles. La communication se fait par événements, pas par imports
Ouvrez le monorepo Bootcode dans VS Code et suivez ces repères
1️⃣ Le dossier racine contexts/
C'est là que vivent les 5 bounded contexts : iac/, education/, notifications/, social/, giraffe/
2️⃣ Dans chaque contexte, commencez par core/
C'est le cœur métier : les agrégats, les use cases, les interfaces de repositories
3️⃣ Regardez messages/events/ pour comprendre les liens
Les événements révèlent comment les contextes se parlent sans se connaître
4️⃣ Utilisez la recherche VS Code (Ctrl+P)
Tapez "Student" — vous verrez qu'il n'existe que dans education/. Preuve d'indépendance.
🏢
Bounded Context = département
Domaine métier indépendant, pas dossier technique
🗣️
Vocabulaire propre
Le même mot = sens différent selon le contexte
📡
Communication par événements
Pas d'import direct entre contextes
🏗️
Structure uniforme
core/ + adapters/ + messages/ partout
Ouvrez le monorepo Bootcode et identifiez les 5 contextes
Demain : Atelier de modélisation — dessinez vos propres bounded contexts