Fournit une interface pour creer des familles d'objets lies sans specifier leurs classes concretes. C'est une factory de factories.
Un catalogue IKEA par style : tu choisis 'scandinave' et tu obtiens table, chaise et lampe assorties.
class DarkThemeFactory {
createButton() { return new DarkButton(); }
createInput() { return new DarkInput(); }
}
// Usage: const ui = getFactory('dark');Cas d'usage : Creer des composants UI coherents par theme ou des connecteurs DB par provider.
Convertit l'interface d'une classe en une autre interface attendue par le client. Permet a des classes incompatibles de collaborer.
Un adaptateur de prise electrique : ta prise francaise fonctionne dans une prise anglaise grace a l'adaptateur.
class StripeAdapter {
constructor(stripe) { this.stripe = stripe; }
pay(amount) {
return this.stripe.charges.create({ amount });
}
}Cas d'usage : Integrer une librairie tierce dont l'API ne correspond pas a votre interface interne.
Groupe d'entites et de value objects traite comme une unite de coherence transactionnelle. Toutes les modifications passent par la racine d'agregat.
Un bon de commande avec ses lignes : tu ne modifies jamais une ligne directement, tu passes toujours par la commande.
class Order {
#items = [];
addItem(product, qty) {
this.#items.push(new OrderLine(product, qty));
this.recalculateTotal();
}
}Cas d'usage : Garantir les invariants metier dans un perimetre transactionnel defini.
Entite principale d'un agregat qui sert de point d'entree unique. Les objets externes ne peuvent referencer que la racine, jamais les entites internes.
Le chef d'equipe : toute communication avec l'equipe passe par lui pour garantir la coherence.
// Order est la racine, OrderLine est interne
const order = await orderRepo.findById(id);
order.addItem(product, qty); // Pas: orderLine.setQty()
await orderRepo.save(order);Cas d'usage : Proteger les invariants metier en forçant toutes les operations via un point d'entree unique.
Proxy helper deploye a cote d'un service pour gerer les connexions sortantes (retry, circuit breaker, monitoring). Variante du Sidecar.
Un ambassadeur diplomatique : il represente ton pays et gere les protocoles complexes a ta place.
// Le sidecar ambassador gere le retry et le circuit breaking
// L'app appelle simplement localhost:9000
const res = await fetch('http://localhost:9000/external-api');Cas d'usage : Simplifier les connexions sortantes d'un service vers des APIs externes.
Couche de traduction entre votre domaine et un systeme externe ou legacy. Empeche les concepts etrangers de polluer votre modele.
Un traducteur-interprete entre deux pays : il adapte non seulement la langue mais aussi les conventions culturelles.
class LegacyUserAdapter {
toModernUser(legacyData) {
return new User({
id: legacyData.USR_ID,
name: `${legacyData.FNAME} ${legacyData.LNAME}`,
});
}
}Cas d'usage : Integrer un systeme legacy sans contaminer votre domaine avec ses conventions.
Point d'entree unique pour toutes les requetes client. Gere routage, authentification, rate limiting, aggregation et transformation.
La reception d'un hotel : tous les clients passent par elle, et elle dirige vers le bon service.
// Kong / AWS API Gateway config
routes:
- path: /users
service: user-service
plugins: [rate-limit, jwt-auth]
- path: /orders
service: order-serviceCas d'usage : Entree unifiee pour les microservices avec concerns transversaux centralises.
Offset : skip/limit simple mais instable sur les donnees qui changent. Cursor : basee sur un marqueur stable, performante a grande echelle.
Offset : 'page 5 d'un livre' (instable si on ajoute des pages). Cursor : 'lire a partir du marque-page' (toujours fiable).
// Offset: GET /users?page=3&limit=20
// Cursor: GET /users?after=eyJpZCI6MTAwfQ&limit=20
const cursor = Buffer.from(JSON.stringify({ id: lastId })).toString('base64');Cas d'usage : Offset pour les petits datasets avec UI de pages. Cursor pour les feeds infinis et les APIs a fort volume.
Limite le nombre de requetes qu'un client peut faire dans un intervalle de temps. Protege contre les abus, le DDoS et la surcharge.
Le nombre max de retraits au distributeur par jour : protege la banque et ton compte.
// Express rate limiter
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 min
max: 100, // 100 requetes par fenetre
});
app.use('/api/', limiter);Cas d'usage : APIs publiques, protection contre les abus, equite d'acces entre clients.
Strategies pour faire evoluer une API sans casser les clients existants : URL path (/v1/), header (Accept), query param. Chaque approche a ses tradeoffs.
Les editions d'un manuel scolaire : la v1 reste disponible pendant que la v2 est utilisee par les nouveaux etudiants.
// URL versioning (le plus courant)
app.use('/api/v1/users', usersV1Router);
app.use('/api/v2/users', usersV2Router);
// Header versioning
// Accept: application/vnd.api+json;version=2Cas d'usage : APIs publiques avec des clients que vous ne controlez pas et qui ne migrent pas en meme temps.
Orchestre les use cases de l'application : coordonne le domaine, les repositories et les services externes. Ne contient pas de logique metier.
Un chef de projet : il coordonne les equipes mais ne code pas lui-meme.
class PlaceOrderUseCase {
async execute(dto) {
const order = Order.create(dto);
await this.repo.save(order);
await this.eventBus.publish(order.events);
}
}Cas d'usage : Orchestrer un use case complet : validation, persistance, publication d'events.
Un backend dedie par type de client (web, mobile, IoT) qui adapte et agrege les donnees des services backend pour les besoins specifiques de chaque frontend.
Un assistant personnel par langue : chacun traduit et resume l'information selon les besoins de son interlocuteur.
// Mobile BFF - donnees legeres
app.get('/api/mobile/feed', async (req, res) => {
const data = await feedService.get();
res.json(data.map(toMobileFeedItem));
});Cas d'usage : Quand le web a besoin de donnees riches et le mobile de donnees legeres.
Frontiere explicite dans laquelle un modele de domaine particulier s'applique. Chaque contexte a son propre langage et ses propres regles.
Les departements d'une entreprise : 'client' signifie prospect en commercial et compte en comptabilite.
// Context 'Sales': Customer has cart, wishlist
// Context 'Billing': Customer has invoices, payments
// Meme mot, modeles differents, bases separeesCas d'usage : Decomposer un systeme complexe en sous-domaines autonomes avec des frontieres claires.
Decouple une abstraction de son implementation pour qu'elles puissent varier independamment. Evite l'explosion combinatoire de sous-classes.
Une telecommande universelle : la telecommande (abstraction) fonctionne avec n'importe quelle TV (implementation).
class Notification {
constructor(sender) { this.sender = sender; }
send(msg) { this.sender.deliver(msg); }
}
// new Notification(new SmsSender()).send('Hi');Cas d'usage : Quand deux dimensions de variation (ex: forme + couleur) generent trop de sous-classes.
Separe la construction d'un objet complexe de sa representation. Permet de construire etape par etape avec une API fluide.
Commander un burger personnalise : pain, puis steak, puis sauce, puis fromage — chaque etape est optionnelle.
const query = new QueryBuilder()
.select('name', 'email')
.from('users')
.where('active = true')
.limit(10)
.build();Cas d'usage : Construction de requetes SQL, objets de configuration ou DTOs avec beaucoup de parametres optionnels.
Passe une requete le long d'une chaine de handlers. Chaque handler decide de traiter la requete ou de la passer au suivant.
Le service client : operateur, superviseur, manager — ta demande remonte jusqu'a quelqu'un qui peut la traiter.
class Handler {
setNext(h) { this.next = h; return h; }
handle(req) {
if (this.canHandle(req)) return this.process(req);
return this.next?.handle(req);
}
}Cas d'usage : Pipeline de validation, middleware Express, gestion d'evenements DOM (bubbling).
Detecte les defaillances et empeche l'application de tenter des operations vouees a l'echec. Trois etats : ferme, ouvert, semi-ouvert.
Un disjoncteur electrique : il coupe le courant quand il detecte une surcharge pour proteger l'installation.
class CircuitBreaker {
#failures = 0;
async call(fn) {
if (this.#failures > 5) throw new Error('Circuit open');
try { return await fn(); }
catch(e) { this.#failures++; throw e; }
}
}Cas d'usage : Appels a des services externes instables pour eviter les cascades de pannes.
Architecture en couches concentriques ou les dependances pointent vers l'interieur. Le domaine est au centre, independant des frameworks et de l'infrastructure.
Un oignon : chaque couche protege le coeur (domaine) des details exterieurs (UI, DB, frameworks).
// Domain (centre) - aucune dependance
class User { validate() { ... } }
// Use Case
class CreateUser { execute(dto) { ... } }
// Infrastructure (exterieur)
class PostgresUserRepo { save(user) { ... } }Cas d'usage : Applications complexes avec logique metier riche qui doit survivre aux changements de frameworks.
Encapsule une requete comme un objet, permettant de parametrer, mettre en file d'attente, journaliser et annuler des operations.
Un bon de commande au restaurant : il capture l'intention, peut etre mis en attente ou annule.
class CreateUserCmd {
constructor(data) { this.data = data; }
execute() { return db.users.insert(this.data); }
undo() { return db.users.delete(this.data.id); }
}Cas d'usage : Systeme undo/redo, file de taches asynchrones, CQRS command side.
Compose des objets en structures arborescentes pour representer des hierarchies partie-tout. Les clients traitent uniformement objets simples et composes.
Un dossier de fichiers : un dossier peut contenir des fichiers ou d'autres dossiers, et on peut tous les supprimer de la meme facon.
class Folder {
children = [];
add(child) { this.children.push(child); }
getSize() {
return this.children.reduce((s, c) => s + c.getSize(), 0);
}
}Cas d'usage : Menus imbriques, arbres DOM, systemes de fichiers, organigrammes.
Favoriser la composition d'objets plutot que l'heritage de classes pour reutiliser du comportement. Plus flexible et moins fragile.
Les LEGO : tu assembles des pieces pour creer des formes variees, au lieu de mouler une piece monolithique par forme.
// Heritage fragile:
class FlyingSwimmingDuck extends FlyingDuck { swim() {} }
// Composition:
class Duck {
constructor(private fly: Flyable, private swim: Swimmable) {}
}Cas d'usage : Combiner des comportements sans les limites de l'heritage simple ou le diamond problem.
Mecanisme HTTP ou le client et le serveur se mettent d'accord sur le format de la reponse via les headers Accept et Content-Type.
Commander dans un restaurant multilangue : tu demandes le menu en français, on te le donne en français.
// Client
Accept: application/json
// Serveur
app.get('/users', (req, res) => {
if (req.accepts('json')) res.json(users);
else if (req.accepts('xml')) res.send(toXml(users));
else res.status(406).end();
});Cas d'usage : APIs qui doivent supporter plusieurs formats (JSON, XML, CSV) pour differents clients.
Cartographie des relations entre Bounded Contexts : shared kernel, customer-supplier, conformist, ACL, partnership. Definit les contrats d'integration.
La carte diplomatique mondiale : elle montre quels pays sont allies, en conflit, ou independants.
// Context Map (documentation)
// Sales <-> Billing : Customer-Supplier
// Sales -> Shipping : Conformist
// Sales <-> Legacy CRM : Anti-Corruption LayerCas d'usage : Visualiser et planifier les integrations entre sous-domaines d'un systeme complexe.
Privilegier des conventions sensees par defaut plutot que de forcer une configuration explicite. Le developpeur ne configure que ce qui devie de la convention.
Une reunion hebdo toujours lundi 9h : pas besoin de reinviter chaque semaine, seules les exceptions sont signalees.
// Convention NestJS: UserController -> /users
@Controller('users') // convention: nom du controleur = route
class UserController {
@Get() findAll() { ... } // GET /users
}Cas d'usage : Frameworks comme Rails, NestJS, Spring Boot : reduire le boilerplate de configuration.
Mecanisme de securite HTTP qui permet a un serveur d'indiquer quelles origines sont autorisees a acceder a ses ressources. Gere via des headers specifiques.
Le badge d'acces a un immeuble : seuls les employes autorises (origines) peuvent entrer dans certains etages (endpoints).
app.use(cors({
origin: ['https://myapp.com'],
methods: ['GET', 'POST'],
credentials: true,
maxAge: 86400 // preflight cache 24h
}));Cas d'usage : Frontend sur un domaine different du backend — quasi-obligatoire en SPA moderne.
Command Query Responsibility Segregation : separe les modeles de lecture (Query) et d'ecriture (Command). Permet d'optimiser chaque cote independamment.
Un guichet de banque : un guichet pour deposer (ecriture), un autre pour consulter le solde (lecture).
// Command side
class CreateOrderCmd { execute(data) { db.write(data); } }
// Query side (modele optimise lecture)
class OrderQuery { getList() { return readDb.orders.find(); } }Cas d'usage : Applications avec des ratios lecture/ecriture tres differents ou des modeles de lecture complexes.
Une methode est soit une commande (modifie l'etat, ne retourne rien) soit une query (retourne des donnees, ne modifie rien). Jamais les deux.
Au guichet : soit tu deposes un cheque (commande), soit tu consultes ton solde (query). Pas les deux en meme temps.
class Stack {
push(item) { this.items.push(item); } // Command: void
peek() { return this.items.at(-1); } // Query: value
// pop() viole CQS: modifie ET retourne
}Cas d'usage : Base de CQRS, rend le code previsible et plus facile a raisonner.
Utilitaire qui batch et cache les requetes de donnees pour resoudre le probleme N+1 dans GraphQL. Regroupe les appels DB d'un meme tick en une seule requete.
Un serveur qui attend que toute la table ait commande avant d'aller en cuisine, au lieu de faire un aller-retour par personne.
const userLoader = new DataLoader(async (ids) => {
const users = await db.users.findByIds(ids);
return ids.map(id => users.find(u => u.id === id));
});
// userLoader.load(1); userLoader.load(2); -> 1 queryCas d'usage : Resolvers GraphQL qui chargent des entites liees pour eviter N+1 requetes.
Ajoute dynamiquement des responsabilites a un objet sans modifier sa classe. Offre une alternative flexible a l'heritage.
Ajouter des garnitures sur une pizza : chaque garniture enveloppe la precedente et ajoute son propre gout.
function withLogging(fn) {
return (...args) => {
console.log('Call:', fn.name);
return fn(...args);
};
}
const save = withLogging(saveUser);Cas d'usage : Ajouter logging, cache, validation ou authentification de facon transparente.
Les modules de haut niveau ne doivent pas dependre des modules de bas niveau. Les deux doivent dependre d'abstractions. Les abstractions ne dependent pas des details.
Une prise electrique : l'appareil et le reseau dependent du standard de la prise, pas l'un de l'autre.
// Haut niveau depend d'une abstraction
interface Logger { log(msg: string): void; }
class UserService {
constructor(private logger: Logger) {}
}
// Injecte ConsoleLogger ou FileLoggerCas d'usage : Rendre le code testable en injectant des mocks et decoupler les couches.
Evenement significatif qui s'est produit dans le domaine metier. Nomme au passe (OrderPlaced, PaymentReceived). Declenche des reactions dans d'autres contextes.
L'annonce au micro dans un magasin : 'Nouveau produit disponible en rayon 3' — tous les interesses reagissent.
class OrderPlaced extends DomainEvent {
constructor(public orderId: string, public total: number) {
super('OrderPlaced');
}
}
order.addEvent(new OrderPlaced(order.id, order.total));Cas d'usage : Decoupler les side effects (emails, analytics) de la logique metier principale.
Service stateless qui encapsule une logique metier qui ne rentre naturellement dans aucune entite ou value object. Opere sur plusieurs agregats.
Un notaire : il ne possede rien mais orchestre une transaction entre acheteur et vendeur.
class TransferService {
transfer(from: Account, to: Account, amount: Money) {
from.debit(amount);
to.credit(amount);
}
}Cas d'usage : Operations metier impliquant plusieurs agregats qui ne peuvent pas etre attribuees a un seul.
Chaque piece de connaissance doit avoir une representation unique et non ambigue dans le systeme. Evite la duplication de logique.
Une source unique de verite : un seul calendrier familial, pas un par personne qui divergent.
// Mauvais: validation email dupliquee partout
// Bon:
const isValidEmail = (e) => /^[^@]+@[^@]+$/.test(e);
// Reutilise dans form, API, importCas d'usage : Centraliser les regles metier, validations et transformations pour eviter les incoherences.
Objet du domaine defini par son identite unique plutot que par ses attributs. Deux entites avec les memes attributs mais des IDs differents sont distinctes.
Deux jumeaux identiques : meme apparence, mais ce sont deux personnes distinctes avec des identites differentes.
class User {
constructor(public readonly id: string, public name: string) {}
equals(other: User) { return this.id === other.id; }
}Cas d'usage : Modeliser des concepts metier qui ont un cycle de vie et une identite persistante.
Mecanisme de cache HTTP : le serveur renvoie un ETag (hash du contenu), le client le renvoie dans If-None-Match. Si inchange, 304 Not Modified.
Un numero de version sur un document : si tu as deja la derniere version, pas besoin de retelecharger.
// Reponse serveur
res.setHeader('ETag', '"abc123"');
// Requete client suivante
// If-None-Match: "abc123"
// Serveur: 304 Not Modified (pas de body)Cas d'usage : Reduire la bande passante et la charge serveur pour les ressources rarement modifiees.
Canal central de communication par evenements au sein d'une application. Les composants publient et s'abonnent sans se connaitre mutuellement.
Le tableau d'affichage d'une entreprise : n'importe qui peut y poster une annonce, n'importe qui peut la lire.
class EventBus {
#handlers = {};
on(event, fn) { (this.#handlers[event] ??= []).push(fn); }
emit(event, data) { this.#handlers[event]?.forEach(fn => fn(data)); }
}Cas d'usage : Communication entre modules d'un monolithe modulaire ou composants UI decouples.
Stocke l'etat comme une sequence immuable d'evenements plutot que comme l'etat courant. L'etat se reconstruit en rejouant les evenements.
Un releve bancaire : tu ne stockes pas le solde, mais l'historique de toutes les transactions.
const events = [
{ type: 'AccountOpened', balance: 0 },
{ type: 'MoneyDeposited', amount: 100 },
{ type: 'MoneyWithdrawn', amount: 30 },
];
const balance = events.reduce(applyEvent, 0); // 70Cas d'usage : Audit trail complet, systemes financiers, undo/redo, debug temporel.
Architecture ou les composants communiquent par production et consommation d'evenements asynchrones. Decouplage temporel et spatial.
Un systeme de sonnettes dans un restaurant : le cuisinier sonne quand le plat est pret, le serveur reagit.
// Producer
await broker.publish('order.created', order);
// Consumer
broker.subscribe('order.created', async (order) => {
await inventory.reserve(order.items);
});Cas d'usage : Systemes reactifs, microservices decouples, traitement temps reel de flux de donnees.
Fournit une interface simplifiee a un sous-systeme complexe. Cache la complexite derriere une API unifiee et facile a utiliser.
Le bouton 'demarrer' d'une voiture : un geste simple qui declenche demarreur, injection, allumage.
class OrderFacade {
async placeOrder(cart) {
await this.payment.charge(cart.total);
await this.inventory.reserve(cart.items);
await this.shipping.schedule(cart.address);
}
}Cas d'usage : Simplifier l'utilisation d'un sous-systeme avec plusieurs classes interdependantes.
Definit une interface pour creer un objet, mais laisse les sous-classes decider quelle classe instancier. Permet de deleguer la logique de creation.
Un restaurant avec une carte : tu choisis 'pizza' et la cuisine decide quelle recette exacte preparer.
class NotifFactory {
create(type) {
if (type === 'email') return new EmailNotif();
if (type === 'sms') return new SmsNotif();
throw new Error('Unknown type');
}
}Cas d'usage : Quand la logique de creation varie selon le contexte et qu'on veut eviter les if/else dans le code client.
Detecter et signaler les erreurs le plus tot possible plutot que de les propager silencieusement. Reduit le temps de debug et les effets de bord.
Le voyant moteur : mieux vaut s'arreter au premier signal que rouler jusqu'a la panne totale.
function createUser(data) {
if (!data.email) throw new Error('Email required');
if (!isValid(data.email)) throw new Error('Invalid email');
// ... logique seulement si tout est valide
}Cas d'usage : Validation d'entrees, assertions en debut de fonction, guard clauses.
Partage efficacement des objets a grain fin pour economiser la memoire. Separe l'etat intrinseque (partage) de l'etat extrinseque (contextuel).
Les caracteres d'imprimerie : chaque lettre 'A' utilise le meme bloc metallique, seule la position sur la page change.
const icons = new Map();
function getIcon(name) {
if (!icons.has(name)) icons.set(name, loadIcon(name));
return icons.get(name);
}Cas d'usage : Rendu de milliers d'elements similaires (arbres dans un jeu, caracteres dans un editeur).
Langage de requete pour APIs ou le client specifie exactement les donnees souhaitees. Un seul endpoint, schema type, pas d'over/under-fetching.
Commander a la carte au restaurant : tu choisis exactement chaque plat et garniture, rien de plus.
query {
user(id: 42) {
name
email
orders(last: 5) {
total
status
}
}
}Cas d'usage : Frontends avec des besoins de donnees varies (mobile vs web), dashboards complexes.
Framework RPC haute performance de Google utilisant Protocol Buffers pour la serialisation et HTTP/2 pour le transport. Supporte le streaming bidirectionnel.
Un talkie-walkie numerique : communication directe, rapide, avec un protocole strict que les deux cotes comprennent.
// user.proto
service UserService {
rpc GetUser (UserRequest) returns (UserResponse);
rpc ListUsers (Empty) returns (stream UserResponse);
}
message UserRequest { int32 id = 1; }Cas d'usage : Communication inter-microservices performante, streaming temps reel, APIs internes.
Hypermedia As The Engine Of Application State. Niveau 3 de Richardson : les reponses contiennent des liens vers les actions possibles, rendant l'API auto-decouvrable.
Un site web : chaque page contient des liens vers les pages suivantes — tu n'as pas besoin de deviner les URLs.
{
"id": 42, "name": "Alice",
"_links": {
"self": { "href": "/users/42" },
"orders": { "href": "/users/42/orders" },
"delete": { "href": "/users/42", "method": "DELETE" }
}
}Cas d'usage : APIs publiques auto-documentees ou le client n'a pas besoin de hardcoder les URLs.
Architecture Ports & Adapters ou le domaine expose des ports (interfaces) et les adaptateurs connectent le monde exterieur. Isole le coeur metier.
Une prise universelle : le port est le standard, l'adaptateur change selon le pays (DB, API, UI).
// Port (interface)
interface UserPort { findById(id): User; }
// Adapter
class MongoUserAdapter implements UserPort {
findById(id) { return mongo.find({ _id: id }); }
}Cas d'usage : Remplacer facilement une base de donnees ou un service externe sans toucher au domaine.
Don't call us, we'll call you. Les composants de haut niveau appellent ceux de bas niveau via des callbacks ou des hooks, pas l'inverse.
Un casting Hollywood : l'acteur laisse ses coordonnees, c'est le studio qui rappelle — pas l'inverse.
// Framework appelle ton code (IoC)
app.get('/users', (req, res) => {
res.json(users); // Tu ne geres pas le serveur HTTP
});
// Express t'appelle quand la route matcheCas d'usage : Fondement de l'Inversion of Control dans les frameworks et le pattern Template Method.
Une operation idempotente produit le meme resultat qu'elle soit executee une ou plusieurs fois. Essentiel pour les retries et la fiabilite des APIs.
Appuyer sur le bouton d'un ascenseur deja allume : le resultat est le meme, qu'on appuie 1 ou 10 fois.
// Idempotency key pour eviter les doublons
app.post('/payments', async (req, res) => {
const existing = await db.findByIdempotencyKey(req.headers['idempotency-key']);
if (existing) return res.json(existing);
const payment = await processPayment(req.body);
res.json(payment);
});Cas d'usage : Paiements, creation de ressources, toute operation qui peut etre retentee par le client.
Un client ne doit pas etre force de dependre d'interfaces qu'il n'utilise pas. Preferer plusieurs interfaces specifiques a une seule interface generale.
Une telecommande par appareil plutot qu'une telecommande universelle avec 200 boutons dont tu n'en utilises que 5.
// Mauvais: interface Animal { fly(); swim(); run(); }
// Bon:
interface Flyable { fly(): void; }
interface Swimmable { swim(): void; }
class Duck implements Flyable, Swimmable { }Cas d'usage : Eviter les implementations vides ou les throws 'not supported' dans les classes.
Definit une representation grammaticale pour un langage et un interpreteur pour evaluer ses expressions. Utilisee pour les DSL.
Un traducteur humain : il comprend la grammaire de la langue source et produit l'equivalent dans la langue cible.
class NumberExpr {
constructor(val) { this.val = val; }
interpret() { return this.val; }
}
class AddExpr {
constructor(l, r) { this.l = l; this.r = r; }
interpret() { return this.l.interpret() + this.r.interpret(); }
}Cas d'usage : Moteurs de regles metier, parseurs d'expressions mathematiques, langages de requetes.
Fournit un moyen de parcourir sequentiellement les elements d'une collection sans exposer sa structure interne.
Une playlist musicale : tu appuies sur 'suivant' sans savoir si c'est un tableau, un arbre ou une API.
class Range {
constructor(start, end) { this.s = start; this.e = end; }
*[Symbol.iterator]() {
for (let i = this.s; i <= this.e; i++) yield i;
}
}Cas d'usage : Parcourir des structures de donnees complexes (arbres, graphes) avec une interface uniforme.
La simplicite doit etre un objectif cle du design. La complexite inutile est le pire ennemi de la maintenabilite.
Un interrupteur on/off vs un panneau de controle de centrale nucleaire — choisis la simplicite adaptee.
// Over-engineered:
class UserValidatorStrategyFactory { ... }
// KISS:
function validateUser(u) {
return u.name?.length > 0 && isValidEmail(u.email);
}Cas d'usage : Choisir la solution la plus simple qui resout le probleme actuel.
Un objet ne devrait parler qu'a ses amis proches, pas aux amis de ses amis. Evite les chaines d'appels (train wreck) qui creent du couplage profond.
Au restaurant, tu parles au serveur, pas directement au cuisinier ni au fournisseur du cuisinier.
// Mauvais (train wreck):
user.getAddress().getCity().getZipCode()
// Bon:
user.getZipCode() // delegation interneCas d'usage : Reduire le couplage entre objets et rendre le code plus resilient aux changements.
Vues precalculees et stockees physiquement, optimisees pour la lecture. Mises a jour a partir des evenements ou des changements de donnees sources.
Un tableau de bord affiche en permanence : les chiffres sont precalcules, pas recalcules a chaque regard.
// PostgreSQL
CREATE MATERIALIZED VIEW order_stats AS
SELECT user_id, COUNT(*), SUM(total)
FROM orders GROUP BY user_id;
-- REFRESH MATERIALIZED VIEW order_stats;Cas d'usage : Dashboards, rapports, requetes complexes cote lecture en CQRS.
Definit un objet qui encapsule les interactions entre un ensemble d'objets. Reduit le couplage en empechant les objets de se referer directement.
La tour de controle d'un aeroport : les avions ne se parlent pas entre eux, ils passent tous par la tour.
class ChatRoom {
send(msg, from, to) {
to.receive(msg, from.name);
}
}
// Les users ne se connaissent pas directementCas d'usage : Coordonner des composants UI interdependants, chat rooms, systemes de workflow.
Capture et externalise l'etat interne d'un objet sans violer l'encapsulation, pour pouvoir le restaurer plus tard.
La sauvegarde dans un jeu video : tu captures l'etat complet pour pouvoir y revenir si tu meurs.
class Editor {
save() { return { content: this.content }; }
restore(memento) { this.content = memento.content; }
}
const history = [editor.save()];Cas d'usage : Undo/redo dans un editeur, snapshots d'etat, point de restauration transactionnel.
Style architectural ou l'application est decomposee en services independants, deployables separement, communiquant via API ou messages.
Une equipe de specialistes independants : chacun fait son metier, ils communiquent par telephone.
// Service autonome
const app = express();
app.get('/users/:id', userController.findById);
app.listen(3001);
// Deploye independamment, sa propre DBCas d'usage : Grandes equipes, scaling independant, polyglottisme technologique.
Fonction intermediaire inseree dans un pipeline de traitement. Chaque middleware peut modifier la requete/reponse ou court-circuiter la chaine.
Les controles de securite a l'aeroport : chaque etape verifie quelque chose avant de te laisser passer.
const auth = (req, res, next) => {
if (!req.headers.token) return res.status(401).end();
req.user = verify(req.headers.token);
next();
};Cas d'usage : Authentification, logging, compression, CORS dans Express/Koa/NestJS.
Monolithe organise en modules bien decouples avec des frontieres claires. Combine la simplicite du monolithe et la modularite des microservices.
Un immeuble d'appartements : un seul batiment, mais chaque appartement est independant avec ses propres murs.
// Modules avec interfaces publiques
import { UserModule } from './modules/user';
import { OrderModule } from './modules/order';
// Communication via interfaces, pas d'acces DB croisesCas d'usage : Equipe moyenne qui veut de la modularite sans la complexite operationnelle des microservices.
Application deployee comme une seule unite ou tous les composants partagent le meme processus et la meme base de donnees. Simple mais limitant a l'echelle.
Un couteau suisse : tout est integre dans un seul outil, pratique au debut mais difficile a reparer piece par piece.
// Tout dans un seul projet
app.use('/users', userRoutes);
app.use('/orders', orderRoutes);
app.use('/payments', paymentRoutes);
// Un seul deploy, une seule DBCas d'usage : MVP, startups early-stage, equipes reduites ou le monolithe est le choix rationnel.
Remplace les verifications null par un objet neutre qui implemente l'interface attendue avec un comportement par defaut (no-op).
Un chauffeur fantome dans un taxi autonome : le siege conducteur est occupe par quelque chose qui ne fait rien mais le systeme fonctionne.
class NullLogger {
log() {} // no-op
error() {} // no-op
}
// Au lieu de: if (logger) logger.log(...)Cas d'usage : Eviter les verifications null repetitives pour des dependances optionnelles.
Maintient un ensemble d'objets reinitialises prets a l'emploi pour eviter le cout de creation/destruction repetee. Gere le cycle de vie acquire/release.
Les caddies au supermarche : tu en prends un disponible, tu l'utilises, puis tu le rends.
class Pool {
#available = [];
acquire() { return this.#available.pop() ?? create(); }
release(obj) { obj.reset(); this.#available.push(obj); }
}Cas d'usage : Pool de connexions DB, threads, ou objets graphiques dans un moteur de jeu.
Definit une dependance un-a-plusieurs : quand un objet change d'etat, tous ses dependants sont notifies automatiquement.
S'abonner a une chaine YouTube : tu es notifie a chaque nouvelle video sans verifier manuellement.
class EventEmitter {
#subs = new Map();
on(event, fn) { this.#subs.set(event, [...(this.#subs.get(event) ?? []), fn]); }
emit(event, data) { this.#subs.get(event)?.forEach(fn => fn(data)); }
}Cas d'usage : Systemes de notifications, reactive UI (React state), EventEmitter Node.js.
Variante de Clean Architecture avec des couches concentriques : Domain Model, Domain Services, Application Services, Infrastructure. Dependances vers le centre.
Les couches d'un oignon : chaque couche ne connait que celle juste en dessous, le coeur est pur.
// Couches de l'interieur vers l'exterieur
// 1. Domain: Entity, ValueObject
// 2. Domain Services: business rules
// 3. Application: use cases, orchestration
// 4. Infrastructure: DB, HTTP, messagingCas d'usage : Applications enterprise avec logique metier complexe et multiple integrations.
Ecrit les evenements dans une table outbox dans la meme transaction que les donnees metier. Un processus separe lit et publie ces evenements.
La boite 'courrier depart' au bureau : tu deposes ta lettre, et le facteur passe la relever plus tard.
await db.transaction(async (tx) => {
await tx.insert('orders', order);
await tx.insert('outbox', {
event: 'OrderCreated', payload: order
});
});Cas d'usage : Garantir la coherence entre l'etat local et les evenements publies en microservices.
Cree de nouveaux objets en clonant une instance existante plutot qu'en la construisant from scratch. Utile quand la creation est couteuse.
Photocopier un document modele puis modifier juste le nom et la date sur chaque copie.
const baseConfig = { theme: 'dark', lang: 'fr' };
const userConfig = { ...baseConfig, lang: 'en' };
// Ou: structuredClone(baseConfig);Cas d'usage : Cloner des objets de configuration ou des entites de jeu avec des etats precalcules.
Fournit un substitut ou un intermediaire controlant l'acces a un objet. Peut ajouter lazy loading, cache, logging ou controle d'acces.
Un agent immobilier : il represente le proprietaire et filtre les visiteurs avant de leur donner acces.
const handler = {
get(target, prop) {
console.log(`Access: ${prop}`);
return target[prop];
}
};
const proxy = new Proxy(obj, handler);Cas d'usage : Lazy loading d'images, cache transparent, protection d'acces, logging d'appels API.
Modele de messagerie ou les emetteurs (publishers) envoient des messages a des canaux sans connaitre les recepteurs (subscribers). Decouplage total.
Un journal : l'editeur publie sans savoir qui lit, les abonnes recoivent sans connaitre l'editeur personnellement.
// Redis Pub/Sub
await redis.publish('orders', JSON.stringify(order));
// Subscriber
redis.subscribe('orders', (msg) => {
processOrder(JSON.parse(msg));
});Cas d'usage : Communication inter-services dans les microservices, notifications temps reel, event streaming.
Abstrait l'acces aux donnees en encapsulant la logique de persistance derriere une interface de collection. Le domaine ignore la source de donnees.
Une bibliothecaire : tu demandes un livre par titre, elle sait ou le trouver sans que tu connaisses le systeme de classement.
class UserRepo {
async findById(id) { return db.users.findOne({ id }); }
async save(user) { return db.users.upsert(user); }
async findByEmail(e) { return db.users.findOne({ email: e }); }
}Cas d'usage : Decoupler le domaine metier du framework ORM ou de la base de donnees.
Style architectural pour APIs web base sur les ressources, les verbes HTTP et l'hypermedia. Le modele de Richardson definit 4 niveaux de maturite (0 a 3).
Un plan de ville : les rues sont les URLs, les panneaux sont les methodes HTTP, les liens sont les directions vers d'autres lieux.
// Niveau 2: resources + verbes HTTP
GET /api/users // Liste
GET /api/users/42 // Detail
POST /api/users // Creation
PUT /api/users/42 // Mise a jour
DELETE /api/users/42 // SuppressionCas d'usage : APIs web standard, communication inter-services, APIs publiques.
Retente automatiquement une operation echouee avec un delai croissant (backoff exponentiel). Gere les erreurs transitoires.
Rappeler quelqu'un qui ne repond pas : tu attends 1 min, puis 5 min, puis 15 min avant de reessayer.
async function retry(fn, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try { return await fn(); }
catch(e) { await sleep(2 ** i * 1000); }
}
throw new Error('Max retries reached');
}Cas d'usage : Appels reseau, envoi d'emails, operations cloud avec erreurs transitoires.
Gere les transactions distribuees via une sequence d'etapes locales avec des actions compensatoires en cas d'echec. Alternative a 2PC.
Organiser un voyage : reserver vol, hotel, voiture. Si l'hotel est indisponible, tu annules le vol deja reserve.
const saga = [
{ exec: bookFlight, comp: cancelFlight },
{ exec: bookHotel, comp: cancelHotel },
];
// Execute chaque etape; en cas d'echec, compense en ordre inverseCas d'usage : Transactions multi-services dans les microservices (commande, paiement, livraison).
La structure des dossiers doit crier le domaine metier, pas le framework. On devrait deviner le metier en regardant l'arborescence.
Un plan de maison : on voit 'cuisine', 'chambre', 'salon' — pas 'mur', 'tuyau', 'cable electrique'.
// Mauvais: controllers/, services/, repositories/
// Bon:
src/
ordering/ // Feature metier
inventory/ // Feature metier
shipping/ // Feature metierCas d'usage : Rendre le code navigable par concept metier plutot que par pattern technique.
Decoupe un programme en sections distinctes, chacune traitant une preoccupation specifique. Minimise le chevauchement de responsabilites.
Les rayons d'un supermarche : fruits, viande, boissons — chaque rayon a sa specialite.
// Separe: route, validation, business, persistence
router.post('/users', validate(schema), async (req, res) => {
const user = await userService.create(req.body);
res.json(user);
});Cas d'usage : Fondement de toute architecture en couches : UI, logique, donnees.
Modele ou le cloud gere l'infrastructure. Le code s'execute en fonctions ephemeres declenchees par des evenements, facturees a l'execution.
Un taxi : tu paies la course, pas la voiture. Pas de parking, pas d'entretien.
// AWS Lambda
export const handler = async (event) => {
const body = JSON.parse(event.body);
const result = await processOrder(body);
return { statusCode: 200, body: JSON.stringify(result) };
};Cas d'usage : Workloads imprevisibles, APIs legeres, traitement d'evenements, prototypage rapide.
Registre central qui fournit des services a la demande. Considere comme un anti-pattern car il cache les dependances au lieu de les rendre explicites.
Les Pages Jaunes : tu cherches un plombier sans savoir lequel tu auras. Le probleme : tu ne vois pas les dependances.
class ServiceLocator {
static #services = new Map();
static register(name, svc) { this.#services.set(name, svc); }
static get(name) { return this.#services.get(name); }
}Cas d'usage : Legacy code ou l'injection de dependances n'est pas disponible.
Deploie des fonctionnalites annexes dans un processus ou conteneur separe, attache au service principal. Decouple les concerns transversaux.
Le side-car d'une moto : un passager (le proxy) voyage a cote sans modifier la moto elle-meme.
# docker-compose.yml
services:
app:
image: my-app
envoy-sidecar:
image: envoyproxy/envoy
network_mode: 'service:app'Cas d'usage : Service mesh (Istio/Envoy), logging, monitoring, mTLS sans modifier le code applicatif.
Pattern qui garantit qu'une classe n'a qu'une seule instance et fournit un point d'acces global. Souvent considere comme un anti-pattern en contexte d'injection de dependances.
Le president d'un pays : il n'y en a qu'un seul a la fois, et tout le monde sait comment le contacter.
class DB {
static #instance;
static getInstance() {
if (!DB.#instance) DB.#instance = new DB();
return DB.#instance;
}
}Cas d'usage : Connexion base de donnees ou logger quand on ne dispose pas de conteneur DI.
Architecture ou les fonctionnalites sont exposees comme des services reutilisables communiquant via des protocoles standardises. Predecesseur des microservices.
Les services publics d'une ville : poste, mairie, hopital — chacun rend un service specifique accessible a tous.
// Service SOAP classique (legacy)
<wsdl:service name="UserService">
<wsdl:port binding="tns:UserBinding">
<soap:address location="http://api/users"/>
</wsdl:port>
</wsdl:service>Cas d'usage : Entreprises avec systemes heterogenes necessitant interoperabilite via ESB.
Cinq principes de conception OO : Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion. Fondamentaux du code maintenable.
Les cinq regles d'or de la construction : chacune empeche un type de defaut structurel.
// S: une classe, une raison de changer
// O: ouvert a l'extension, ferme a la modification
// L: les sous-types substituent le type parent
// I: interfaces specifiques > interface generale
// D: dependre des abstractions, pas des concretionsCas d'usage : Guide de design pour ecrire du code flexible, testable et maintenable.
Encapsule une regle metier dans un objet reutilisable et composable. Les specifications peuvent etre combinees avec AND, OR, NOT.
Les filtres d'une recherche immobiliere : 3 chambres ET jardin ET moins de 300k — chaque critere est un filtre composable.
class ActiveUserSpec {
isSatisfiedBy(user) { return user.active && !user.banned; }
}
class AndSpec {
constructor(a, b) { this.a = a; this.b = b; }
isSatisfiedBy(x) { return this.a.isSatisfiedBy(x) && this.b.isSatisfiedBy(x); }
}Cas d'usage : Regles metier complexes reutilisees dans les requetes, validations et autorisations.
Permet a un objet de modifier son comportement quand son etat interne change. L'objet semble changer de classe.
Un distributeur de boissons : son comportement change selon qu'il attend une piece, a recu le paiement ou est en rupture.
class Order {
setState(state) { this.state = state; }
next() { this.state.next(this); }
}
class PendingState {
next(order) { order.setState(new PaidState()); }
}Cas d'usage : Machine a etats pour workflows (commande, paiement), connexions reseau, UI.
Migre progressivement un systeme legacy en remplacant ses fonctionnalites piece par piece derriere une facade, jusqu'a ce que l'ancien systeme soit elimine.
Un figuier etrangleur qui pousse autour d'un arbre : le nouveau systeme grandit autour de l'ancien jusqu'a le remplacer.
app.use('/api/users', (req, res) => {
if (featureFlag('new-users-service'))
return proxy(req, newService);
return proxy(req, legacyService);
});Cas d'usage : Migration d'un monolithe vers des microservices sans big-bang risque.
Definit une famille d'algorithmes interchangeables encapsules separement. Le client choisit l'algorithme a utiliser au runtime.
Choisir son moyen de transport : velo, bus ou voiture — la destination est la meme, la strategie change.
const strategies = {
credit: (amount) => chargeCard(amount),
paypal: (amount) => paypalCheckout(amount),
};
const pay = strategies[method];Cas d'usage : Algorithmes de tri, strategies de paiement, politiques de retry configurables.
Dis a un objet ce qu'il doit faire au lieu de lui demander son etat puis decider a sa place. Favorise l'encapsulation du comportement.
Dire au taxi 'emmene-moi a l'aeroport' au lieu de lui demander la carte et conduire toi-meme.
// Ask (mauvais):
if (account.getBalance() >= amount) account.setBalance(...);
// Tell (bon):
account.withdraw(amount); // la logique est interneCas d'usage : Eliminer les Anemic Domain Models en mettant la logique dans les objets du domaine.
Definit le squelette d'un algorithme dans une methode, en deleguant certaines etapes aux sous-classes. Structure sans rigidite.
Une recette de cuisine : les etapes sont fixes (preparer, cuire, servir) mais chaque chef personnalise les details.
class Report {
generate() {
this.fetchData();
this.format();
this.export();
}
// sous-classes implementent fetchData, format, export
}Cas d'usage : Pipelines de traitement, generation de rapports, tests avec setup/teardown.
Langage commun partage entre developpeurs et experts metier dans un Bounded Context. Le code utilise les memes termes que le metier.
Parler la meme langue dans une equipe internationale : pas de traduction, pas de malentendu.
// Mauvais: class DataProcessor { handleItem() {} }
// Bon: class InvoiceGenerator { issueInvoice() {} }
// Les termes metier sont dans le codeCas d'usage : Reduire les malentendus entre devs et metier, rendre le code auto-documentant.
Maintient une liste d'objets modifies pendant une transaction et coordonne l'ecriture des changements en une seule operation atomique.
Un panier d'achat : tu ajoutes/supprimes des articles, et tout est valide en une seule fois au passage en caisse.
class UnitOfWork {
#changes = [];
track(entity) { this.#changes.push(entity); }
async commit() {
await db.transaction(tx => this.#changes.forEach(e => tx.save(e)));
}
}Cas d'usage : Sauvegarder plusieurs entites modifiees en une transaction atomique (ORM comme EF Core).
Objet immutable defini par ses attributs, sans identite propre. Deux value objects avec les memes attributs sont egaux. Pas de setter.
Un billet de 20 euros : tu ne te soucies pas de QUEL billet c'est, seule la valeur compte.
class Money {
constructor(readonly amount: number, readonly currency: string) {}
add(other: Money) {
if (this.currency !== other.currency) throw Error('Mismatch');
return new Money(this.amount + other.amount, this.currency);
}
}Cas d'usage : Modeliser des concepts sans identite : Money, Email, Address, DateRange.
Organise le code par feature (tranche verticale) plutot que par couche technique. Chaque slice contient tout : handler, logique, persistance.
Couper un gateau verticalement : chaque part contient toutes les couches (genoise, creme, glaçage).
// features/create-user/
// handler.ts - HTTP handler
// command.ts - business logic
// repository.ts - data access
// test.ts - testsCas d'usage : Equipes feature qui veulent minimiser les conflits de merge et le couplage inter-features.
Separe un algorithme de la structure d'objets sur laquelle il opere. Permet d'ajouter de nouvelles operations sans modifier les classes existantes.
Un inspecteur qui visite differents types de batiments : meme demarche, mais le rapport differe selon le batiment.
class TaxVisitor {
visitBook(b) { return b.price * 0.05; }
visitFood(f) { return f.price * 0.10; }
}
// item.accept(visitor)Cas d'usage : Calculs sur un AST (compilateur), serialisation polymorphe, rapports multi-formats.
Ne pas implementer une fonctionnalite tant qu'elle n'est pas reellement necessaire. Les besoins futurs imagines se materialisent rarement tels quels.
Ne pas construire un garage pour 4 voitures quand tu n'en as qu'une — et tu demenageras peut-etre.
// YAGNI violé:
class User {
// 'au cas ou on aurait besoin de multi-tenant'
tenantId?: string;
// 'au cas ou on supporterait l'i18n'
locale?: string;
}Cas d'usage : Eviter le code mort et la complexite prematuree en se concentrant sur le besoin actuel.
Autres stacks