Mis à jour ces 90 derniers jours 7 nouvelles · 31 révisées
Fournit une interface pour créer des familles d'objets lies sans spécifier leurs classes concrètes. 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 : Créer des composants UI cohérents 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 électrique : ta prise française fonctionne dans une prise anglaise grâce a l'adaptateur.
class StripeAdapter {
constructor(stripe) { this.stripe = stripe; }
pay(amount) {
return this.stripe.charges.create({ amount });
}
}Cas d'usage : Intégrer une librairie tierce dont l'API ne correspond pas a votre interface interne.
Document court, versionné avec le code, qui consigne une décision d'architecture importante : contexte, décision, conséquences, statut (proposé, accepté, déprécié, remplacé). Popularisé par Michael Nygard dans son billet « Documenting Architecture Décisions » de 2011, le format a évolué vers des modèles comme MADR. Une ADR n'est jamais modifiée après acceptation : on en écrit une nouvelle qui la remplace.
Le journal de bord d'un capitaine : on ne réécrit pas les pages d'hier, on ajoute celle d'aujourd'hui en expliquant pourquoi on change de cap.
# docs/adr/0007-remplacer-ingress-nginx-par-gateway-api.md
# 7. Remplacer Ingress NGINX par la Gateway API
Date : 2026-03-02
Statut : Accepté (remplace ADR-0003)
## Contexte
Ingress NGINX est retiré par le projet Kubernetes en mars 2026 :
plus de correctifs de sécurité.
## Décision
Migrer vers Envoy Gateway + HTTPRoute via ingress2gateway.
## Conséquences
+ routage canary natif, rôles séparés plateforme/équipes
- formation, réécriture des annotations spécifiquesCas d'usage : Toute décision structurante (base de données, découpage en services, framework, hébergement) qu'un nouvel arrivant devra comprendre dans deux ans.
Groupe d'entités et de value objects traite comme une unité de cohérence transactionnelle. Toutes les modifications passent par la racine d'agrégat.
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 métier dans un périmètre transactionnel défini.
Entité principale d'un agrégat qui sert de point d'entrée unique. Les objets externes ne peuvent référencer que la racine, jamais les entités internes.
Le chef d'équipe : toute communication avec l'équipe passe par lui pour garantir la cohérence.
// 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 : Protéger les invariants métier en forçant toutes les opérations via un point d'entrée unique.
Proxy helper déployé a cote d'un service pour gérer les connexions sortantes (retry, circuit breaker, monitoring). Variante du Sidecar.
Un ambassadeur diplomatique : il représente ton pays et gère 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 système externe ou legacy. Empêche les concepts étrangers de polluer votre modèle.
Un traducteur-interprété 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 : Intégrer un système legacy sans contaminer votre domaine avec ses conventions.
Point d'entrée unique pour toutes les requêtes client. Gère routage, authentification, rate limiting, aggregation et transformation.
La réception d'un hôtel : 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 : Entrée unifiée pour les microservices avec concerns transversaux centralises.
Offset : skip/limit simple mais instable sur les données qui changent. Cursor : basée sur un marqueur stable, performante a grande échelle.
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 requêtes qu'un client peut faire dans un intervalle de temps. Protège contre les abus, le DDoS et la surcharge.
Le nombre max de retraits au distributeur par jour : protège 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, équité d'accès entre clients.
Stratégies pour faire évoluer une API sans casser les clients existants : URL path (/v1/), header (Accept), query param. Chaque approche a ses tradeoffs.
Les éditions d'un manuel scolaire : la v1 reste disponible pendant que la v2 est utilisée par les nouveaux étudiants.
// 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 contrôlez pas et qui ne migrent pas en même temps.
Orchestre les use cases de l'application : coordonne le domaine, les repositories et les services externes. Ne contient pas de logique métier.
Un chef de projet : il coordonne les équipes mais ne code pas lui-même.
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.
Mécanisme par lequel un consommateur lent signale au producteur de ralentir, pour éviter que les données s'accumulent en mémoire jusqu'au plantage. Dans les streams Node.js, write() renvoie false quand le tampon interne dépasse highWaterMark et l'événement drain indique quand reprendre ; pipeline() gère cela automatiquement. Le même principe existe en Kafka (consommation par lot), en RxJS et dans les files de messages (limites de préfetch).
Un serveur qui apporte les plats au rythme où le convive les mange, au lieu d'empiler quinze assiettes sur la table jusqu'à ce qu'elle cède.
import { pipeline } from 'node:stream/promises';
import { createReadStream, createWriteStream } from 'node:fs';
import { createGzip } from 'node:zlib';
// pipeline() propage la backpressure et les erreurs :
// si le disque écrit lentement, la lecture ralentit d'elle-même.
await pipeline(
createReadStream('export-10go.csv'),
createGzip(),
createWriteStream('export-10go.csv.gz'),
);
// À la main, ce serait :
// if (!dest.write(chunk)) await once(dest, 'drain');Cas d'usage : Export de fichiers volumineux, relais de flux HTTP, ingestion de messages : partout où un producteur rapide alimente un consommateur plus lent.
Un backend dédié par type de client (web, mobile, IoT) qui adapte et agrège les données des services backend pour les besoins spécifiques de chaque frontend.
Un assistant personnel par langue : chacun traduit et résume 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 données riches et le mobile de données légères.
Frontière explicite dans laquelle un modèle de domaine particulier s'applique. Chaque contexte a son propre langage et ses propres règles.
Les départements d'une entreprise : 'client' signifie prospect en commercial et compte en comptabilité.
// Context 'Sales': Customer has cart, wishlist
// Context 'Billing': Customer has invoices, payments
// Meme mot, modeles differents, bases separeesCas d'usage : Décomposer un système complexe en sous-domaines autonomes avec des frontières claires.
Découplé une abstraction de son implémentation pour qu'elles puissent varier indépendamment. Évite l'explosion combinatoire de sous-classes.
Une télécommande universelle : la télécommande (abstraction) fonctionne avec n'importe quelle TV (implémentation).
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) génèrent trop de sous-classes.
Sépare la construction d'un objet complexe de sa représentation. Permet de construire étape par étape avec une API fluide.
Commander un burger personnalise : pain, puis steak, puis sauce, puis fromage — chaque étape est optionnelle.
const query = new QueryBuilder()
.select('name', 'email')
.from('users')
.where('active = true')
.limit(10)
.build();Cas d'usage : Construction de requêtes SQL, objets de configuration ou DTOs avec beaucoup de paramètrès optionnels.
Incident où une clé très demandée expire et où des centaines de requêtes simultanées ratent le cache en même temps et recalculent toutes la même valeur, écrasant la base. Parades : verrou ou « single flight » (une seule recalcule, les autres attendent), servir la valeur périmée pendant le recalcul (stale-while-revalidate), et l'expiration anticipée probabiliste XFetch (article VLDB 2015) où chaque lecteur a une probabilité croissante de rafraîchir avant l'échéance, ce qui lisse les recalculs.
La sonnerie de fin de cours : si toute l'école se rue sur la même cantine à la même seconde, la file explose ; faire sortir les classes avec quelques minutes d'écart règle le problème.
// XFetch : rafraîchir « un peu avant » avec une probabilité croissante
const BETA = 1.0;
async function getWithXFetch(key, ttlSec, compute) {
const hit = await cache.get(key); // { value, expiry, delta }
const now = Date.now() / 1000;
const mustRefresh =
!hit || now - hit.delta * BETA * Math.log(Math.random()) >= hit.expiry;
if (!mustRefresh) return hit.value;
const t0 = Date.now() / 1000;
const value = await compute(); // recalcul coûteux
const delta = Date.now() / 1000 - t0; // durée du recalcul
await cache.set(key, { value, delta, expiry: now + ttlSec });
return value;
}Cas d'usage : Page d'accueil, top produits, configuration globale : toute clé chaude dont le recalcul est coûteux et l'expiration synchronisée.
Découpage d'un système en cellules indépendantes et identiques, chacune servant un sous-ensemble de clients avec sa propre pile complète (calcul, base, files). Un routeur de cellules, le plus mince possible, dirige chaque requête vers la bonne cellule ; un plan de contrôle provisionne les cellules. Une panne, un bogue ou un déploiement raté ne touche qu'une fraction des clients. AWS documente le pattern dans son guide Well-Architected « Reducing the Scope of Impact with Cell-Based Architecture ».
Les compartiments étanches d'un navire : une voie d'eau inonde un compartiment, pas la coque entière.
// Routeur de cellules : couche mince, déterministe, sans logique métier
const CELLS = ['cell-a', 'cell-b', 'cell-c'];
function cellFor(tenantId: string): string {
// hachage stable → un tenant vit toujours dans la même cellule
const h = [...tenantId].reduce((a, c) => (a * 31 + c.charCodeAt(0)) >>> 0, 7);
return CELLS[h % CELLS.length];
}
app.use((req, res, next) => {
const cell = cellFor(req.headers['x-tenant-id'] as string);
proxy.web(req, res, { target: `https://${cell}.internal` });
});
// Déployer la version N+1 sur cell-a d'abord = canary par cellule.Cas d'usage : Plateforme multi-tenant à forte exigence de disponibilité, où l'on veut borner le « rayon d'explosion » d'un incident ou d'un déploiement.
Passe une requête le long d'une chaîne de handlers. Chaque handler décidé de traiter la requête ou de la passer au suivant.
Le service client : opérateur, 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).
Détecte les défaillances et empêche l'application de tenter des opérations vouées a l'échec. Trois états : ferme, ouvert, semi-ouvert.
Un disjoncteur électrique : il coupe le courant quand il détecte une surcharge pour protéger 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 éviter les cascades de pannes.
Architecture en couches concentriques ou les dépendances pointent vers l'intérieur. Le domaine est au centre, indépendant des frameworks et de l'infrastructure.
Un oignon : chaque couche protège le coeur (domaine) des détails extérieurs (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 métier riche qui doit survivre aux changements de frameworks.
Encapsulé une requête comme un objet, permettant de paramétrer, mettre en file d'attente, journaliser et annuler des opérations.
Un bon de commande au restaurant : il capture l'intention, peut être 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 : Système undo/redo, file de taches asynchrones, CQRS command side.
Compose des objets en structures arborescentes pour représenter des hiérarchies partie-tout. Les clients traitent uniformément 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 même façon.
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, systèmes de fichiers, organigrammes.
Favoriser la composition d'objets plutôt que l'héritage de classes pour réutiliser du comportement. Plus flexible et moins fragile.
Les LEGO : tu assembles des pièces pour créer des formes variées, au lieu de mouler une pièce 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'héritage simple ou le diamond problem.
Mécanisme HTTP ou le client et le serveur se mettent d'accord sur le format de la réponse 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 différents clients.
Cartographie des relations entre Bounded Contexts : shared kernel, customer-supplier, conformist, ACL, partnership. Définit les contrats d'intégration.
La carte diplomatique mondiale : elle montre quels pays sont allies, en conflit, ou indépendants.
// Context Map (documentation)
// Sales <-> Billing : Customer-Supplier
// Sales -> Shipping : Conformist
// Sales <-> Legacy CRM : Anti-Corruption LayerCas d'usage : Visualiser et planifier les intégrations entre sous-domaines d'un système complexe.
Privilégier des conventions sensées par défaut plutôt que de forcer une configuration explicite. Le développeur ne configure que ce qui dévie de la convention.
Une réunion hebdo toujours lundi 9h : pas besoin de réinviter chaque semaine, seules les exceptions sont signalées.
// 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 : réduire le boilerplate de configuration.
Mécanisme de sécurité HTTP qui permet a un serveur d'indiquer quelles origines sont autorisées a accéder a ses ressources. Gère via des headers spécifiques.
Le badge d'accès a un immeuble : seuls les employés autorises (origines) peuvent entrer dans certains étages (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 différent du backend — quasi-obligatoire en SPA moderne.
Command Query Responsibility Ségrégation : sépare les modèles de lecture (Query) et d'écriture (Command). Permet d'optimiser chaque cote indépendamment.
Un guichet de banque : un guichet pour déposer (écriture), 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/écriture très différents ou des modèles de lecture complexes.
Une méthode est soit une commande (modifie l'état, ne retourne rien) soit une query (retourne des données, ne modifie rien). Jamais les deux.
Au guichet : soit tu deposes un chèque (commande), soit tu consultes ton solde (query). Pas les deux en même 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 prévisible et plus facile a raisonner.
Utilitaire qui batch et cache les requêtes de données pour résoudre le problème N+1 dans GraphQL. Regroupe les appels DB d'un même tick en une seule requête.
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 entités liées pour éviter N+1 requêtes.
Ajoute dynamiquement des responsabilités a un objet sans modifier sa classe. Offre une alternative flexible a l'héritage.
Ajouter des garnitures sur une pizza : chaque garniture enveloppe la précédenté et ajoute son propre goût.
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 façon transparente.
Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d'abstractions. Les abstractions ne dépendent pas des détails.
Une prise électrique : l'appareil et le réseau dépendent 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 découpler les couches.
Evenement significatif qui s'est produit dans le domaine métier. Nomme au passe (OrderPlaced, PaymentReceived). Déclenché des réactions dans d'autres contextes.
L'annonce au micro dans un magasin : 'Nouveau produit disponible en rayon 3' — tous les intéressés réagissent.
class OrderPlaced extends DomainEvent {
constructor(public orderId: string, public total: number) {
super('OrderPlaced');
}
}
order.addEvent(new OrderPlaced(order.id, order.total));Cas d'usage : Découpler les side effects (emails, analytics) de la logique métier principale.
Service stateless qui encapsulé une logique métier qui ne rentre naturellement dans aucune entité ou value object. Opéré sur plusieurs agrégats.
Un notaire : il ne possède 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 : Opérations métier impliquant plusieurs agrégats qui ne peuvent pas être attribuées a un seul.
Chaque pièce de connaissance doit avoir une représentation unique et non ambiguë dans le système. Évite la duplication de logique.
Une source unique de vérité : 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 règles métier, validations et transformations pour éviter les incohérences.
Objet du domaine défini par son identité unique plutôt que par ses attributs. Deux entités avec les mêmes attributs mais des IDs différents sont distinctes.
Deux jumeaux identiques : même apparence, mais ce sont deux personnes distinctes avec des identités différentes.
class User {
constructor(public readonly id: string, public name: string) {}
equals(other: User) { return this.id === other.id; }
}Cas d'usage : Modéliser des concepts métier qui ont un cycle de vie et une identité persistante.
Mécanisme de cache HTTP : le serveur renvoie un ETag (hash du contenu), le client le renvoie dans If-None-Match. Si inchangé, 304 Not Modified.
Un numéro de version sur un document : si tu as déjà la dernière 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 : Réduire la bande passante et la charge serveur pour les ressources rarement modifiées.
Canal central de communication par evenements au sein d'une application. Les composants publient et s'abonnent sans se connaître 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 découplés.
Stocke l'état comme une séquence immuable d'evenements plutôt que comme l'état courant. L'état se reconstruit en rejouant les evenements.
Un relève 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, systèmes financiers, undo/redo, debug temporel.
Architecture ou les composants communiquent par production et consommation d'evenements asynchrones. Decouplage temporel et spatial.
Un système de sonnettes dans un restaurant : le cuisinier sonne quand le plat est prêt, le serveur réagit.
// Producer
await broker.publish('order.created', order);
// Consumer
broker.subscribe('order.created', async (order) => {
await inventory.reserve(order.items);
});Cas d'usage : Systèmes réactifs, microservices découplés, traitement temps réel de flux de données.
Fournit une interface simplifiée a un sous-système complexe. Cache la complexité derrière une API unifiée et facile a utiliser.
Le bouton 'démarrer' d'une voiture : un geste simple qui déclenche démarreur, 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-système avec plusieurs classes interdépendantes.
Définit une interface pour créer un objet, mais laisse les sous-classes décider quelle classe instancier. Permet de déléguer la logique de création.
Un restaurant avec une carte : tu choisis 'pizza' et la cuisine décidé quelle recette exacte préparer.
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 création varie selon le contexte et qu'on veut éviter les if/else dans le code client.
Détecter et signaler les erreurs le plus tôt possible plutôt que de les propager silencieusement. Réduit le temps de debug et les effets de bord.
Le voyant moteur : mieux vaut s'arrêter 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'entrées, assertions en début de fonction, guard clauses.
Partage efficacement des objets a grain fin pour économiser la mémoire. Sépare l'état intrinsèque (partage) de l'état extrinsèque (contextuel).
Les caractères d'imprimerie : chaque lettre 'A' utilise le même bloc métallique, 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'éléments similaires (arbres dans un jeu, caractères dans un éditeur).
Langage de requête pour APIs ou le client spécifié exactement les données souhaitées. 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 données varies (mobile vs web), dashboards complexes.
Framework RPC haute performance de Google utilisant Protocol Buffers pour la sérialisation et HTTP/2 pour le transport. Supporte le streaming bidirectionnel.
Un talkie-walkie numérique : 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 réel, APIs internes.
Hypermedia As The Engine Of Application State. Niveau 3 de Richardson : les réponses 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-documentées 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 extérieur. Isole le coeur métier.
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 données 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 coordonnées, 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 opération idempotente produit le même résultat qu'elle soit exécutée une ou plusieurs fois. Essentiel pour les retries et la fiabilité des APIs.
Appuyer sur le bouton d'un ascenseur déjà allume : le résultat est le même, 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, création de ressources, toute opération qui peut être retentee par le client.
Un client ne doit pas être force de dépendre d'interfaces qu'il n'utilise pas. Préférer plusieurs interfaces spécifiques a une seule interface générâle.
Une télécommande par appareil plutôt qu'une télécommande 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 : Éviter les implémentations vides ou les throws 'not supported' dans les classes.
Définit une représentation grammaticale pour un langage et un interpréteur pour évaluer ses expressions. Utilisée pour les DSL.
Un traducteur humain : il comprend la grammaire de la langue source et produit l'équivalent 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 règles métier, parseurs d'expressions mathématiques, langages de requêtes.
Fournit un moyen de parcourir sequentiellement les éléments 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 données complexes (arbres, graphes) avec une interface uniforme.
La simplicité doit être un objectif clé du design. La complexité inutile est le pire ennemi de la maintenabilite.
Un interrupteur on/off vs un panneau de contrôle de centrale nucléaire — choisis la simplicité adaptée.
// 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 résout le problème actuel.
Un objet ne devrait parler qu'a ses amis proches, pas aux amis de ses amis. Évite les chaînés d'appels (train wreck) qui créent 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 : Réduire le couplage entre objets et rendre le code plus résilient aux changements.
Approche où l'application stocke et modifie les données d'abord sur l'appareil de l'utilisateur, puis synchronise quand le réseau le permet : zéro attente, fonctionnement hors ligne, collaboration en temps réel, données qui survivent au service. Le terme vient de l'essai d'Ink & Switch de 2019. Les CRDT (Conflict-free Replicated Data Types) rendent la fusion automatique et sans conflit : Yjs domine l'édition de texte collaborative (ProseMirror, TipTap, CodeMirror), Automerge les documents structurés.
Chacun écrit sur son carnet dans le train sans réseau ; à l'arrivée, les carnets se fusionnent tout seuls, sans que personne perde une ligne.
import * as Y from 'yjs';
// Deux répliques déconnectées modifient le même texte
const a = new Y.Doc(), b = new Y.Doc();
a.getText('note').insert(0, 'Bonjour');
b.getText('note').insert(0, 'Salut ');
// Échange des mises à jour (peu importe l'ordre, ni le nombre de fois)
Y.applyUpdate(b, Y.encodeStateAsUpdate(a));
Y.applyUpdate(a, Y.encodeStateAsUpdate(b));
console.log(a.getText('note').toString() === b.getText('note').toString());
// true : convergence garantie sans serveur arbitreCas d'usage : Éditeurs collaboratifs, applications de prise de notes ou de terrain avec réseau intermittent, outils où la latence du serveur est inacceptable.
Vues precalculees et stockées physiquement, optimisées pour la lecture. Mises a jour a partir des evenements ou des changements de données 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, requêtes complexes cote lecture en CQRS.
Définit un objet qui encapsulé les interactions entre un ensemble d'objets. Réduit le couplage en empêchant les objets de se référer directement.
La tour de contrôle d'un aéroport : 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 interdépendants, chat rooms, systèmes de workflow.
Capture et externalise l'état interne d'un objet sans violer l'encapsulation, pour pouvoir le restaurer plus tard.
La sauvegarde dans un jeu vidéo : tu captures l'état 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 éditeur, snapshots d'état, point de restauration transactionnel.
Style architectural ou l'application est décomposée en services indépendants, deployables séparément, communiquant via API ou messages.
Une équipe de spécialistes indépendants : chacun fait son métier, ils communiquent par téléphone.
// Service autonome
const app = express();
app.get('/users/:id', userController.findById);
app.listen(3001);
// Deploye independamment, sa propre DBCas d'usage : Grandes équipes, scaling indépendant, polyglottisme technologique.
Fonction intermédiaire insérée dans un pipeline de traitement. Chaque middleware peut modifier la requête/réponse ou court-circuiter la chaîne.
Les contrôles de sécurité a l'aéroport : chaque étape vérifié 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.
Méthode de Simon Brown pour représenter une architecture logicielle à quatre niveaux de zoom : Contexte (le système et ses utilisateurs/systèmes voisins), Conteneurs (applications, API, bases déployables), Composants (modules à l'intérieur d'un conteneur) et Code (classes, si nécessaire). Chaque niveau s'adresse à un public différent et reste lisible seul ; l'outil Structurizr permet de décrire le modèle en texte versionné.
Google Maps pour un logiciel : la vue pays, puis la ville, puis le quartier, puis la rue ; on choisit le zoom selon l'interlocuteur.
# workspace.dsl (Structurizr DSL) – modèle versionné avec le code
workspace {
model {
client = person "Client"
boutique = softwareSystem "Boutique en ligne" {
web = container "Front Next.js" "React"
api = container "API commandes" "NestJS"
db = container "Base" "PostgreSQL"
}
client -> web "Commande via"
web -> api "Appelle" "HTTPS/JSON"
api -> db "Lit/écrit"
}
views { systemContext boutique { include * } container boutique { include * } }
}Cas d'usage : Expliquer l'architecture à un nouvel arrivant, à un client ou en entretien sans noyer le contexte sous les détails d'implémentation.
Monolithe organise en modules bien découplés avec des frontières claires. Combine la simplicité du monolithe et la modularité des microservices.
Un immeuble d'appartements : un seul bâtiment, mais chaque appartement est indépendant 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 : Équipe moyenne qui veut de la modularité sans la complexité opérationnelle des microservices.
Application déployée comme une seule unité ou tous les composants partagent le même processus et la même base de données. Simple mais limitant a l'échelle.
Un couteau suisse : tout est intégré dans un seul outil, pratique au début mais difficile a réparer pièce par pièce.
// 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, équipes réduites ou le monolithe est le choix rationnel.
Remplace les vérifications null par un objet neutre qui implémente l'interface attendue avec un comportement par défaut (no-op).
Un chauffeur fantôme dans un taxi autonome : le siège conducteur est occupe par quelque chose qui ne fait rien mais le système fonctionne.
class NullLogger {
log() {} // no-op
error() {} // no-op
}
// Au lieu de: if (logger) logger.log(...)Cas d'usage : Éviter les vérifications null répétitives pour des dépendances optionnelles.
Maintient un ensemble d'objets réinitialisés prêts a l'emploi pour éviter le coût de création/destruction répétée. Gère le cycle de vie acquire/release.
Les caddies au supermarché : 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.
Définit une dépendance un-a-plusieurs : quand un objet change d'état, tous ses dépendants sont notifies automatiquement.
S'abonner a une chaîne YouTube : tu es notifie a chaque nouvelle vidéo sans vérifier 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 : Systèmes de notifications, réactivé UI (React state), EventEmitter Node.js.
Variante de Clean Architecture avec des couches concentriques : Domain Model, Domain Services, Application Services, Infrastructure. Dépendances vers le centre.
Les couches d'un oignon : chaque couche ne connaît 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 métier complexe et multiple intégrations.
Écrit les evenements dans une table outbox dans la même transaction que les données métier. Un processus sépare lit et publie ces evenements.
La boite 'courrier départ' 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 cohérence entre l'état local et les evenements publies en microservices.
Créé de nouveaux objets en clonant une instance existante plutôt qu'en la construisant from scratch. Utile quand la création est coûteuse.
Photocopier un document modèle 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 entités de jeu avec des états precalcules.
Fournit un substitut ou un intermédiaire contrôlant l'accès a un objet. Peut ajouter lazy loading, cache, logging ou contrôle d'accès.
Un agent immobilier : il représente le propriétaire et filtre les visiteurs avant de leur donner accès.
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'accès, logging d'appels API.
Modèle de messagerie ou les émetteurs (publishers) envoient des messages a des canaux sans connaître les récepteurs (subscribers). Decouplage total.
Un journal : l'éditeur publie sans savoir qui lit, les abonnes reçoivent sans connaître l'éditeur 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 réel, event streaming.
Abstrait l'accès aux données en encapsulant la logique de persistance derrière une interface de collection. Le domaine ignore la source de données.
Une bibliothécaire : tu demandes un livre par titre, elle sait ou le trouver sans que tu connaisses le système 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 : Découpler le domaine métier du framework ORM ou de la base de données.
Style architectural pour APIs web base sur les ressources, les verbes HTTP et l'hypermedia. Le modèle de Richardson définit 4 niveaux de maturité (0 a 3).
Un plan de ville : les rues sont les URLs, les panneaux sont les méthodes 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 opération échouée avec un délai croissant (backoff exponentiel). Gère les erreurs transitoires.
Rappeler quelqu'un qui ne répond pas : tu attends 1 min, puis 5 min, puis 15 min avant de réessayer.
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 réseau, envoi d'emails, opérations cloud avec erreurs transitoires.
Gère les transactions distribuées via une séquence d'étapes locales avec des actions compensatoires en cas d'échec. Alternative a 2PC.
Organiser un voyage : réserver vol, hôtel, voiture. Si l'hôtel est indisponible, tu annules le vol déjà réservé.
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 métier, pas le framework. On devrait deviner le métier en regardant l'arborescence.
Un plan de maison : on voit 'cuisine', 'chambre', 'salon' — pas 'mur', 'tuyau', 'câble électrique'.
// Mauvais: controllers/, services/, repositories/
// Bon:
src/
ordering/ // Feature metier
inventory/ // Feature metier
shipping/ // Feature metierCas d'usage : Rendre le code navigable par concept métier plutôt que par pattern technique.
Découpé un programme en sections distinctes, chacune traitant une préoccupation spécifique. Minimise le chevauchement de responsabilités.
Les rayons d'un supermarché : fruits, viande, boissons — chaque rayon a sa spécialité.
// 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, données.
Modèle ou le cloud gère l'infrastructure. Le code s'exécute en fonctions éphémères déclenchées par des evenements, facturées a l'exécution.
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 imprévisibles, APIs légères, traitement d'evenements, prototypage rapide.
Registre central qui fournit des services a la demande. Considère comme un anti-pattern car il cache les dépendances au lieu de les rendre explicites.
Les Pages Jaunes : tu cherches un plombier sans savoir lequel tu auras. Le problème : tu ne vois pas les dépendances.
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 dépendances n'est pas disponible.
Déploie des fonctionnalités annexes dans un processus ou conteneur sépare, attache au service principal. Découplé les concerns transversaux.
Le side-car d'une moto : un passager (le proxy) voyage a cote sans modifier la moto elle-même.
# 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'accès global. Souvent considère comme un anti-pattern en contexte d'injection de dépendances.
Le président 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 données ou logger quand on ne dispose pas de conteneur DI.
Architecture ou les fonctionnalités sont exposées comme des services réutilisables communiquant via des protocoles standardises. Prédécesseur des microservices.
Les services publics d'une ville : poste, mairie, hôpital — chacun rend un service spécifique 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 systèmes hétérogènés nécessitant interopérabilité via ESB.
Cinq principes de conception OO : Single Responsibility, Open/Closed, Liskov Substitution, Interface Ségrégation, Dependency Inversion. Fondamentaux du code maintenable.
Les cinq règles d'or de la construction : chacune empêche un type de défaut 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 écrire du code flexible, testable et maintenable.
Encapsulé une règle métier dans un objet réutilisable et composable. Les spécifications peuvent être combinées avec AND, OR, NOT.
Les filtres d'une recherche immobilière : 3 chambres ET jardin ET moins de 300k — chaque critère 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 : Règles métier complexes réutilisées dans les requêtes, validations et autorisations.
Permet a un objet de modifier son comportement quand son état interne change. L'objet semble changer de classe.
Un distributeur de boissons : son comportement change selon qu'il attend une pièce, a reçu 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 états pour workflows (commande, paiement), connexions réseau, UI.
Migre progressivement un système legacy en remplaçant ses fonctionnalités pièce par pièce derrière une façade, jusqu'a ce que l'ancien système soit éliminé.
Un figuier étrangleur qui pousse autour d'un arbre : le nouveau système 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.
Définit une famille d'algorithmes interchangeables encapsulés séparément. Le client choisit l'algorithme a utiliser au runtime.
Choisir son moyen de transport : vélo, bus ou voiture — la destination est la même, la stratégie change.
const strategies = {
credit: (amount) => chargeCard(amount),
paypal: (amount) => paypalCheckout(amount),
};
const pay = strategies[method];Cas d'usage : Algorithmes de tri, stratégies de paiement, politiques de retry configurables.
Dis a un objet ce qu'il doit faire au lieu de lui demander son état puis décider a sa place. Favorise l'encapsulation du comportement.
Dire au taxi 'emmené-moi a l'aéroport' au lieu de lui demander la carte et conduire toi-même.
// Ask (mauvais):
if (account.getBalance() >= amount) account.setBalance(...);
// Tell (bon):
account.withdraw(amount); // la logique est interneCas d'usage : Éliminer les Anemic Domain Models en mettant la logique dans les objets du domaine.
Définit le squelette d'un algorithme dans une méthode, en déléguant certaines étapes aux sous-classes. Structure sans rigidité.
Une recette de cuisine : les étapes sont fixes (préparer, cuire, servir) mais chaque chef personnalise les détails.
class Report {
generate() {
this.fetchData();
this.format();
this.export();
}
// sous-classes implementent fetchData, format, export
}Cas d'usage : Pipelines de traitement, génération de rapports, tests avec setup/teardown.
Langage commun partage entre développeurs et experts métier dans un Bounded Context. Le code utilise les mêmes termes que le métier.
Parler la même langue dans une équipe internationale : pas de traduction, pas de malentendu.
// Mauvais: class DataProcessor { handleItem() {} }
// Bon: class InvoiceGenerator { issueInvoice() {} }
// Les termes metier sont dans le codeCas d'usage : Réduire les malentendus entre devs et métier, rendre le code auto-documentant.
Maintient une liste d'objets modifies pendant une transaction et coordonne l'écriture des changements en une seule opération 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 entités modifiées en une transaction atomique (ORM comme EF Core).
Objet immutable défini par ses attributs, sans identité propre. Deux value objects avec les mêmes attributs sont égaux. 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 : Modéliser des concepts sans identité : Money, Émail, Address, DateRange.
Organise le code par feature (tranche verticale) plutôt que par couche technique. Chaque slice contient tout : handler, logique, persistance.
Couper un gâteau verticalement : chaque part contient toutes les couches (genoise, crème, glaçâge).
// features/create-user/
// handler.ts - HTTP handler
// command.ts - business logic
// repository.ts - data access
// test.ts - testsCas d'usage : Équipes feature qui veulent minimiser les conflits de merge et le couplage inter-features.
Sépare un algorithme de la structure d'objets sur laquelle il opère. Permet d'ajouter de nouvelles opérations sans modifier les classes existantes.
Un inspecteur qui visite différents types de bâtiments : même démarche, mais le rapport diffère selon le bâtiment.
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), sérialisation polymorphe, rapports multi-formats.
Trois transports pour le temps réel. WebSocket : bidirectionnel, sur TCP, universel, mais sans reconnexion native. SSE (Server-Sent Events) : unidirectionnel serveur→client sur HTTP simple, reconnexion automatique, idéal pour notifications et flux de LLM. WebTransport : construit sur HTTP/3 (QUIC), flux multiples sans blocage en tête de ligne, datagrammes non fiables pour le jeu ou la vidéo ; Baseline depuis Safari 26.4 en mars 2026 (Chrome 97+, Firefox 114+).
Le téléphone (WebSocket, on se parle tous les deux), la radio (SSE, on écoûte), et la fibre multi-canaux (WebTransport : plusieurs conversations et une vidéo sur la même ligne, sans que l'une bloque l'autre).
// SSE : le plus simple pour du serveur → client (ex. streaming d'un LLM)
const es = new EventSource('/api/stream');
es.onmessage = (e) => appendToken(JSON.parse(e.data)); // reconnexion auto
// WebSocket : bidirectionnel (chat, jeu, curseurs collaboratifs)
const ws = new WebSocket('wss://api.example.com/rt');
ws.onmessage = (e) => handle(JSON.parse(e.data));
ws.send(JSON.stringify({ type: 'join', room: 'r1' }));
// WebTransport : HTTP/3, flux multiples + datagrammes (Baseline 2026)
const wt = new WebTransport('https://api.example.com:4433/wt');
await wt.ready;
const writer = wt.datagrams.writable.getWriter();
writer.write(new TextEncoder().encode('pos:12,48')); // perte toléréeCas d'usage : Choisir : notifications ou tokens d'IA → SSE ; chat, jeu au tour par tour, édition collaborative → WebSocket ; jeu temps réel, vidéo, très nombreux flux → WebTransport.
Ne pas implémenter une fonctionnalité tant qu'elle n'est pas réellement nécessaire. Les besoins futurs imagines se matérialisent rarement tels quels.
Ne pas construire un garage pour 4 voitures quand tu n'en as qu'une — et tu déménageras peut-être.
// 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 : Éviter le code mort et la complexité prématurée en se concentrant sur le besoin actuel.