[{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/acid/","section":"Tags","summary":"","title":"Acid"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/architecture/","section":"Tags","summary":"","title":"Architecture"},{"content":"Le problème #Une application qui expose sa logique métier uniquement via une API REST fonctionne très bien, jusqu\u0026rsquo;au jour où il faut aussi l\u0026rsquo;exposer via une commande CLI pour les traitements batch, ou via un worker qui consomme une file de messages. Si la logique métier est écrite à l\u0026rsquo;intérieur des contrôleurs HTTP, elle doit être dupliquée — ou pire, le worker se met à appeler l\u0026rsquo;API HTTP en interne, ce qui ajoute une latence et une fragilité inutiles.\nLe même problème se pose côté sortie : le métier appelle directement un client PostgreSQL, un SDK d\u0026rsquo;envoi d\u0026rsquo;e-mails, une bibliothèque de paiement. Tester ce métier isolément devient impossible sans mocker des dizaines de dépendances techniques, et changer de fournisseur de paiement oblige à modifier le cœur du système plutôt qu\u0026rsquo;un simple point de branchement.\nL\u0026rsquo;idée générale #L\u0026rsquo;architecture hexagonale, formalisée par Alistair Cockburn, place le métier au centre et matérialise chaque point de contact avec l\u0026rsquo;extérieur sous la forme d\u0026rsquo;un port : une interface définie par le métier lui-même, jamais par la technique.\nDeux familles de ports coexistent :\nPorts primaires (driving) : le point d\u0026rsquo;entrée du métier. Un adaptateur extérieur — contrôleur HTTP, commande CLI, listener de message — appelle le métier à travers ce port pour déclencher une action. Ports secondaires (driven) : le point de sortie du métier. Le métier appelle ce port pour parler au monde extérieur — persister une donnée, envoyer un e-mail, appeler une API tierce — sans jamais connaître l\u0026rsquo;implémentation réelle derrière. Chaque port est ensuite branché à un ou plusieurs adaptateurs, qui traduisent entre le langage du métier et celui d\u0026rsquo;une technologie précise. Un adaptateur REST et un adaptateur CLI peuvent tous deux utiliser le même port primaire ; un adaptateur PostgreSQL et un adaptateur en mémoire (pour les tests) peuvent tous deux implémenter le même port secondaire.\nC\u0026rsquo;est très exactement la même idée que la Clean Architecture — isoler le métier, inverser les dépendances techniques — mais avec un vocabulaire plus direct : on ne parle pas de cercles, on parle de ce qui entre (port primaire) et de ce qui sort (port secondaire).\nAnalogie du quotidien #Pensez à une prise électrique murale. La prise (le port) définit un contrat standard — une forme, une tension — sans se soucier de ce qui sera branché dessus. Une lampe, un chargeur de téléphone ou un aspirateur sont autant d\u0026rsquo;adaptateurs différents qui respectent ce même contrat.\nLa prise ne sait pas, et n\u0026rsquo;a pas besoin de savoir, ce qu\u0026rsquo;elle alimente. Vous pouvez débrancher la lampe et brancher l\u0026rsquo;aspirateur sans toucher au mur, au circuit électrique, ni à l\u0026rsquo;installation elle-même. C\u0026rsquo;est exactement le rôle d\u0026rsquo;un port : offrir un contrat stable auquel n\u0026rsquo;importe quel adaptateur compatible peut se connecter, sans jamais exposer les détails internes du système derrière.\nDiagramme # flowchart LR subgraph Primaires[\"Adaptateurs primaires (pilotent le métier)\"] HTTP[Contrôleur HTTP] CLI[Commande CLI] end subgraph Hexagone[\"Métier\"] PortIn[[\"Port primaire : SouscrireNewsletterPort\"]] Core((Logique métier)) PortOut[[\"Port secondaire : AbonneRepositoryPort\"]] PortMail[[\"Port secondaire : NotificationPort\"]] PortIn --\u003e Core Core --\u003e PortOut Core --\u003e PortMail end subgraph Secondaires[\"Adaptateurs secondaires (pilotés par le métier)\"] DB[(Adaptateur PostgreSQL)] Mail[Adaptateur SMTP] end HTTP --\u003e PortIn CLI --\u003e PortIn PortOut -.implémenté par.-\u003e DB PortMail -.implémenté par.-\u003e Mail Exemple de code #PHP #// --- Port primaire : ce que le métier expose au monde extérieur --- interface SouscrireNewsletterPort { public function souscrire(string $email): void; } // --- Port secondaire : ce dont le métier a besoin, sans savoir comment c\u0026#39;est implémenté --- interface AbonneRepositoryPort { public function existe(string $email): bool; public function sauvegarder(string $email): void; } interface NotificationPort { public function envoyerBienvenue(string $email): void; } // --- Métier : implémente le port primaire, dépend uniquement des ports secondaires --- final class SouscrireNewsletterService implements SouscrireNewsletterPort { public function __construct( private AbonneRepositoryPort $repository, private NotificationPort $notifications, ) {} public function souscrire(string $email): void { if ($this-\u0026gt;repository-\u0026gt;existe($email)) { throw new DomainException(\u0026#39;Cet e-mail est déjà inscrit\u0026#39;); } $this-\u0026gt;repository-\u0026gt;sauvegarder($email); $this-\u0026gt;notifications-\u0026gt;envoyerBienvenue($email); } } // --- Adaptateur primaire : traduit une requête HTTP en appel au port primaire --- final class SouscriptionController { public function __construct(private SouscrireNewsletterPort $service) {} public function post(Requete $requete): Reponse { $this-\u0026gt;service-\u0026gt;souscrire($requete-\u0026gt;get(\u0026#39;email\u0026#39;)); return new Reponse(201); } } // --- Adaptateur secondaire : implémente le port avec une technologie précise --- final class PostgresAbonneRepository implements AbonneRepositoryPort { public function __construct(private PDO $pdo) {} public function existe(string $email): bool { $stmt = $this-\u0026gt;pdo-\u0026gt;prepare(\u0026#39;SELECT 1 FROM abonnes WHERE email = ?\u0026#39;); $stmt-\u0026gt;execute([$email]); return (bool) $stmt-\u0026gt;fetchColumn(); } public function sauvegarder(string $email): void { $stmt = $this-\u0026gt;pdo-\u0026gt;prepare(\u0026#39;INSERT INTO abonnes (email) VALUES (?)\u0026#39;); $stmt-\u0026gt;execute([$email]); } } Java #// --- Port primaire --- public interface SouscrireNewsletterPort { void souscrire(String email); } // --- Ports secondaires --- public interface AbonneRepositoryPort { boolean existe(String email); void sauvegarder(String email); } public interface NotificationPort { void envoyerBienvenue(String email); } // --- Métier --- public class SouscrireNewsletterService implements SouscrireNewsletterPort { private final AbonneRepositoryPort repository; private final NotificationPort notifications; public SouscrireNewsletterService(AbonneRepositoryPort repository, NotificationPort notifications) { this.repository = repository; this.notifications = notifications; } public void souscrire(String email) { if (repository.existe(email)) { throw new IllegalStateException(\u0026#34;Cet e-mail est déjà inscrit\u0026#34;); } repository.sauvegarder(email); notifications.envoyerBienvenue(email); } } // --- Adaptateur primaire --- @RestController public class SouscriptionController { private final SouscrireNewsletterPort service; public SouscriptionController(SouscrireNewsletterPort service) { this.service = service; } @PostMapping(\u0026#34;/newsletter\u0026#34;) public ResponseEntity\u0026lt;Void\u0026gt; souscrire(@RequestBody SouscriptionRequest requete) { service.souscrire(requete.email()); return ResponseEntity.status(201).build(); } } // --- Adaptateur secondaire --- public class JpaAbonneRepository implements AbonneRepositoryPort { private final EntityManager em; public JpaAbonneRepository(EntityManager em) { this.em = em; } public boolean existe(String email) { return em.createQuery(\u0026#34;SELECT 1 FROM Abonne a WHERE a.email = :email\u0026#34;) .setParameter(\u0026#34;email\u0026#34;, email) .getResultList() .size() \u0026gt; 0; } public void sauvegarder(String email) { em.persist(new Abonne(email)); } } JavaScript #// --- Métier : dépend d\u0026#39;abstractions passées en paramètre (pas d\u0026#39;interfaces formelles en JS) --- class SouscrireNewsletterService { constructor(repository, notifications) { this.repository = repository; // port secondaire this.notifications = notifications; // port secondaire } async souscrire(email) { if (await this.repository.existe(email)) { throw new Error(\u0026#34;Cet e-mail est déjà inscrit\u0026#34;); } await this.repository.sauvegarder(email); await this.notifications.envoyerBienvenue(email); } } // --- Adaptateur primaire : route Express qui pilote le métier --- app.post(\u0026#34;/newsletter\u0026#34;, async (req, res) =\u0026gt; { await souscrireNewsletterService.souscrire(req.body.email); res.status(201).end(); }); // --- Adaptateur secondaire : implémentation MongoDB du port repository --- class MongoAbonneRepository { constructor(collection) { this.collection = collection; } async existe(email) { return (await this.collection.countDocuments({ email })) \u0026gt; 0; } async sauvegarder(email) { await this.collection.insertOne({ email }); } } Quand utiliser l\u0026rsquo;architecture hexagonale ? # Quand le même métier doit être exposé par plusieurs points d\u0026rsquo;entrée : API REST, CLI, worker asynchrone, GraphQL. Quand vous voulez tester le métier sans base de données réelle ni service externe, en substituant un adaptateur en mémoire au port secondaire. Quand une dépendance technique externe (fournisseur de paiement, service d\u0026rsquo;e-mail, base de données) a de bonnes chances de changer un jour. À éviter sur un CRUD simple sans logique métier significative : multiplier les ports et adaptateurs pour brancher un formulaire sur une table n\u0026rsquo;apporte rien. Points importants # Un port est toujours défini côté métier, jamais côté technique — c\u0026rsquo;est cette inversion qui permet de changer d\u0026rsquo;adaptateur sans toucher au cœur du système. Ne confondez pas port primaire et port secondaire : le sens de l\u0026rsquo;appel définit la catégorie, pas la technologie utilisée. L\u0026rsquo;architecture hexagonale et la Clean Architecture ne sont pas deux concurrents à départager : ce sont deux formulations du même principe d\u0026rsquo;inversion de dépendance, avec un vocabulaire différent. Un adaptateur en mémoire, écrit uniquement pour les tests, est souvent le meilleur signe que vos ports sont bien conçus : s\u0026rsquo;il est simple à écrire, c\u0026rsquo;est que le contrat du port est propre. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/architecture-logicielle/architecture-hexagonale/","section":"Architecture logicielle","summary":"L\u0026rsquo;architecture hexagonale (Ports \u0026amp; Adapters) place le métier au centre et le connecte au monde extérieur via des ports — des interfaces définies par le métier — implémentés par des adaptateurs interchangeables. La base de données, l\u0026rsquo;API REST ou la CLI ne sont que des détails branchés sur ces ports.","title":"Architecture hexagonale"},{"content":"Comment organiser un système pour qu\u0026rsquo;il reste compréhensible, testable et évolutif dans le temps ? C\u0026rsquo;est la question à laquelle répond l\u0026rsquo;architecture logicielle — bien avant les frameworks et les outils.\n","date":null,"permalink":"https://www.mybearisyellow.me/architecture-logicielle/","section":"Architecture logicielle","summary":"\u003cp\u003eComment organiser un système pour qu\u0026rsquo;il reste compréhensible, testable et évolutif dans le temps ? C\u0026rsquo;est la question à laquelle répond l\u0026rsquo;architecture logicielle — bien avant les frameworks et les outils.\u003c/p\u003e","title":"Architecture logicielle"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/aws/","section":"Tags","summary":"","title":"Aws"},{"content":"La base de données est souvent le premier goulot d\u0026rsquo;étranglement d\u0026rsquo;une application, et le premier sujet abordé en entretien backend. Ces fiches couvrent les mécanismes qu\u0026rsquo;il faut vraiment comprendre — pas juste mémoriser.\n","date":null,"permalink":"https://www.mybearisyellow.me/bases-de-donnees/","section":"Bases de données","summary":"\u003cp\u003eLa base de données est souvent le premier goulot d\u0026rsquo;étranglement d\u0026rsquo;une application, et le premier sujet abordé en entretien backend. Ces fiches couvrent les mécanismes qu\u0026rsquo;il faut vraiment comprendre — pas juste mémoriser.\u003c/p\u003e","title":"Bases de données"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/bases-de-donnees/","section":"Tags","summary":"","title":"Bases-De-Donnees"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/bdd/","section":"Tags","summary":"","title":"Bdd"},{"content":"Le problème #Un product owner décrit une règle métier à l\u0026rsquo;oral : \u0026ldquo;un client fidèle depuis plus d\u0026rsquo;un an obtient une remise de 10%, sauf sur les articles déjà en promotion\u0026rdquo;. Le développeur code cette règle, l\u0026rsquo;équipe QA la teste manuellement selon sa propre compréhension, et le product owner découvre en recette que la règle implémentée diffère légèrement de son intention initiale — la définition de \u0026ldquo;fidèle depuis plus d\u0026rsquo;un an\u0026rdquo; n\u0026rsquo;était pas la même pour tout le monde.\nLe problème n\u0026rsquo;est pas un manque de tests : c\u0026rsquo;est que le comportement attendu n\u0026rsquo;a jamais existé sous une forme unique, précise et partagée entre le métier et la technique. Chacun a sa propre interprétation, et les tests, écrits après coup en code, ne sont lisibles que par les développeurs — impossible pour le product owner de vérifier lui-même que la spécification est correctement traduite.\nL\u0026rsquo;idée générale #Le BDD (Behavior-Driven Development) déplace l\u0026rsquo;attention du \u0026ldquo;comment coder\u0026rdquo; vers le \u0026ldquo;comment le système doit se comporter\u0026rdquo;, exprimé dans un langage naturel structuré et compréhensible par toutes les parties prenantes — pas seulement les développeurs. La syntaxe la plus répandue pour ça s\u0026rsquo;appelle Gherkin, et structure chaque scénario en trois blocs :\nGiven (étant donné) : le contexte initial, l\u0026rsquo;état du système avant l\u0026rsquo;action. When (quand) : l\u0026rsquo;action déclenchée par l\u0026rsquo;utilisateur ou le système. Then (alors) : le résultat attendu, observable, qui doit se produire. Ce scénario, écrit en langage naturel, n\u0026rsquo;est pas qu\u0026rsquo;une documentation : des outils comme Behat (PHP), Cucumber ou Jest-Cucumber (JavaScript) le rendent exécutable. Chaque ligne du scénario (chaque \u0026ldquo;step\u0026rdquo;) est reliée à une fonction de code qui l\u0026rsquo;implémente réellement. Le même texte sert donc à la fois de spécification lisible par le métier et de test automatisé pour la technique — un seul document, deux usages.\nLe BDD ne remplace pas le TDD : il opère à un niveau différent. Le TDD structure la façon d\u0026rsquo;écrire le code, en partant d\u0026rsquo;un test technique. Le BDD structure la façon de définir le comportement attendu, en partant d\u0026rsquo;une conversation avec le métier — et ce comportement peut ensuite être implémenté avec ou sans TDD.\nAnalogie du quotidien #Le BDD, c\u0026rsquo;est comme un cahier des charges rédigé avec un architecte avant de construire une maison, plutôt que des plans techniques compréhensibles uniquement par le maçon. Le client dit : \u0026ldquo;étant donné que le terrain est en pente, quand on accède au garage, alors la rampe ne doit pas dépasser 15% d\u0026rsquo;inclinaison.\u0026rdquo; Cette phrase reste compréhensible par le client, l\u0026rsquo;architecte et le maçon — chacun peut la relire et confirmer qu\u0026rsquo;elle correspond bien à son intention, avant même que la première pierre ne soit posée.\nSans ce langage commun, le maçon travaillerait à partir d\u0026rsquo;un plan technique que le client ne sait pas lire, et la première fois que le client verrait le résultat serait\u0026hellip; une fois la maison construite.\nDiagramme # flowchart LR A[Conversation métierProduct Owner + Dev + QA] --\u003e B[Scénario GherkinGiven / When / Then] B --\u003e C{Deux usages} C --\u003e D[Spécification lisiblepar le métier] C --\u003e E[Steps reliés au codetest automatisé] E --\u003e F{Résultat} F --\u003e|Conforme| G[Scénario vert] F --\u003e|Non conforme| H[Scénario rouge] Exemple de code #Scénario Gherkin #Fonctionnalité: Remise fidélité En tant que client fidèle Je veux obtenir une remise sur mes achats Afin d\u0026#39;être récompensé pour ma fidélité Scénario: Un client fidèle depuis plus d\u0026#39;un an obtient une remise Étant donné un client inscrit depuis 18 mois Et un article à 100 euros non soldé Quand le client ajoute l\u0026#39;article à son panier Alors le total du panier est de 90 euros Scénario: Un client fidèle n\u0026#39;obtient aucune remise sur un article déjà en promotion Étant donné un client inscrit depuis 18 mois Et un article à 100 euros déjà en promotion Quand le client ajoute l\u0026#39;article à son panier Alors le total du panier est de 100 euros PHP (Behat) #use Behat\\Behat\\Context\\Context; use PHPUnit\\Framework\\Assert; final class FidelityContext implements Context { private Client $client; private Panier $panier; /** @Given un client inscrit depuis :mois mois */ public function unClientInscritDepuis(int $mois): void { $this-\u0026gt;client = new Client(ancienneteEnMois: $mois); $this-\u0026gt;panier = new Panier($this-\u0026gt;client); } /** @Given un article à :prix euros non soldé */ public function unArticleNonSolde(float $prix): void { $this-\u0026gt;articleCourant = new Article($prix, enPromotion: false); } /** @Given un article à :prix euros déjà en promotion */ public function unArticleEnPromotion(float $prix): void { $this-\u0026gt;articleCourant = new Article($prix, enPromotion: true); } /** @When le client ajoute l\u0026#39;article à son panier */ public function leClientAjouteLArticle(): void { $this-\u0026gt;panier-\u0026gt;ajouter($this-\u0026gt;articleCourant); } /** @Then le total du panier est de :total euros */ public function leTotalDuPanierEst(float $total): void { Assert::assertSame($total, $this-\u0026gt;panier-\u0026gt;total()); } } JavaScript (jest-cucumber) #const { defineFeature, loadFeature } = require(\u0026#34;jest-cucumber\u0026#34;); const { Client } = require(\u0026#34;./client\u0026#34;); const { Panier } = require(\u0026#34;./panier\u0026#34;); const { Article } = require(\u0026#34;./article\u0026#34;); const feature = loadFeature(\u0026#34;./remiseFidelite.feature\u0026#34;); defineFeature(feature, (test) =\u0026gt; { test(\u0026#34;Un client fidèle depuis plus d\u0026#39;un an obtient une remise\u0026#34;, ({ given, and, when, then, }) =\u0026gt; { let client; let panier; let article; given(/^un client inscrit depuis (\\d+) mois$/, (mois) =\u0026gt; { client = new Client(Number(mois)); panier = new Panier(client); }); and(/^un article à (\\d+) euros non soldé$/, (prix) =\u0026gt; { article = new Article(Number(prix), false); }); when(\u0026#34;le client ajoute l\u0026#39;article à son panier\u0026#34;, () =\u0026gt; { panier.ajouter(article); }); then(/^le total du panier est de (\\d+) euros$/, (total) =\u0026gt; { expect(panier.total()).toBe(Number(total)); }); }); }); Quand utiliser le BDD ? # Quand une règle métier est ambiguë ou sujette à interprétation, et que sa formulation précise mérite d\u0026rsquo;être validée avec le métier avant d\u0026rsquo;être codée. Sur des projets où testeurs, product owners ou clients doivent pouvoir lire — voire écrire — les scénarios de test sans connaître le code. Pour documenter le comportement attendu d\u0026rsquo;une fonctionnalité de façon durable, exécutable, qui ne peut pas devenir obsolète sans que le test échoue. Moins adapté à une logique purement technique (un algorithme de tri, une optimisation de requête) où aucune partie prenante non technique n\u0026rsquo;a d\u0026rsquo;intérêt à lire la spécification en langage naturel — un test unitaire classique suffit alors. Points importants # Gherkin est un format de spécification, pas un niveau de test : les steps peuvent appeler du code isolé, orchestrer plusieurs composants réels, ou piloter un navigateur, selon ce que le scénario doit vérifier. La valeur du BDD dépend de la conversation qui précède l\u0026rsquo;écriture du scénario, pas seulement de la syntaxe — un scénario Gherkin écrit seul par un développeur, sans le métier, perd une grande partie de son intérêt. BDD et TDD ne s\u0026rsquo;opposent pas : le BDD cadre le comportement attendu au niveau métier, le TDD peut ensuite guider l\u0026rsquo;implémentation technique des steps qui réalisent ce comportement. Trop de détails techniques dans un scénario Gherkin (\u0026ldquo;cliquer sur le bouton #submit-btn\u0026rdquo;) le rend illisible pour le métier et fragile aux changements d\u0026rsquo;interface — un bon scénario reste au niveau du comportement, pas de l\u0026rsquo;implémentation. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/tests/bdd-avec-gherkin/","section":"Tests","summary":"Le BDD (Behavior-Driven Development) décrit le comportement attendu d\u0026rsquo;un système en langage naturel structuré, via la syntaxe Gherkin (Given/When/Then), pour que développeurs, testeurs et experts métier partagent une seule source de vérité, exécutable comme un test.","title":"BDD avec Gherkin"},{"content":"Le problème #Une base de données relationnelle bien indexée reste plus lente qu\u0026rsquo;un accès en mémoire, surtout quand la même requête est exécutée des milliers de fois par seconde pour la même donnée peu volatile : le profil d\u0026rsquo;un utilisateur connecté, la liste des catégories d\u0026rsquo;un site, le classement d\u0026rsquo;un jeu. Recalculer ou relire cette information à chaque requête sollicite inutilement la base et peut la faire saturer sous forte charge, alors que la donnée n\u0026rsquo;a probablement pas changé depuis la dernière lecture.\nL\u0026rsquo;idée générale #Redis est une base clé-valeur en mémoire, extrêmement rapide, utilisée le plus souvent comme couche de cache devant une base relationnelle. Le pattern le plus répandu est le cache-aside (ou \u0026ldquo;lazy loading\u0026rdquo;) :\nL\u0026rsquo;application cherche d\u0026rsquo;abord la donnée dans Redis. Si elle est présente (cache hit), elle est retournée directement, sans toucher la base. Si elle est absente (cache miss), l\u0026rsquo;application va la chercher en base, puis la stocke dans Redis avant de la retourner, pour que la prochaine lecture soit un hit. Deux mécanismes gardent le cache cohérent avec la source de vérité (la base) :\nLe TTL (Time To Live) : chaque clé expire automatiquement après une durée définie, garantissant qu\u0026rsquo;une donnée périmée ne reste jamais indéfiniment en cache. L\u0026rsquo;invalidation explicite : à chaque écriture en base, on supprime (ou on met à jour) la clé correspondante dans Redis, pour refléter le changement immédiatement plutôt que d\u0026rsquo;attendre l\u0026rsquo;expiration du TTL. Analogie du quotidien #Redis fonctionne comme un pense-bête collé sur votre bureau plutôt que d\u0026rsquo;aller rouvrir un classeur dans une armoire au fond du couloir. La première fois qu\u0026rsquo;on a besoin d\u0026rsquo;une information, on va la chercher dans l\u0026rsquo;armoire (la base), et on la note sur un post-it collé au bureau pour ne plus avoir à s\u0026rsquo;y déplacer la prochaine fois.\nLe post-it a toutefois une date de péremption griffonnée dessus (le TTL) : passé ce délai, on le jette et on retourne vérifier l\u0026rsquo;armoire, au cas où l\u0026rsquo;information aurait changé. Et si on met soi-même à jour le dossier dans l\u0026rsquo;armoire, le réflexe est de jeter immédiatement le post-it obsolète plutôt que d\u0026rsquo;attendre qu\u0026rsquo;il expire tout seul.\nDiagramme # sequenceDiagram participant App as Application participant Cache as Redis participant DB as Base de données App-\u003e\u003eCache: GET user:42 alt Cache hit Cache--\u003e\u003eApp: Donnée trouvée else Cache miss Cache--\u003e\u003eApp: (vide) App-\u003e\u003eDB: SELECT * FROM users WHERE id = 42 DB--\u003e\u003eApp: Donnée App-\u003e\u003eCache: SET user:42 (valeur) EX 300 end Note over App,DB: Plus tard, à l'écriture App-\u003e\u003eDB: UPDATE users SET nom = 'Kevin' WHERE id = 42 App-\u003e\u003eCache: DEL user:42 Exemple de code #-- La source de vérité reste la base relationnelle SELECT id, nom, email FROM users WHERE id = 42; // Pattern cache-aside en PHP avec l\u0026#39;extension Redis $redis = new Redis(); $redis-\u0026gt;connect(\u0026#39;127.0.0.1\u0026#39;, 6379); function getUser(int $id, Redis $redis, PDO $pdo): array { $cacheKey = \u0026#34;user:{$id}\u0026#34;; $cached = $redis-\u0026gt;get($cacheKey); if ($cached !== false) { return json_decode($cached, true); // cache hit } // cache miss : on va chercher la donnée en base $stmt = $pdo-\u0026gt;prepare(\u0026#39;SELECT id, nom, email FROM users WHERE id = :id\u0026#39;); $stmt-\u0026gt;execute([\u0026#39;id\u0026#39; =\u0026gt; $id]); $user = $stmt-\u0026gt;fetch(PDO::FETCH_ASSOC); $redis-\u0026gt;setex($cacheKey, 300, json_encode($user)); // TTL de 5 minutes return $user; } function updateUserNom(int $id, string $nom, Redis $redis, PDO $pdo): void { $stmt = $pdo-\u0026gt;prepare(\u0026#39;UPDATE users SET nom = :nom WHERE id = :id\u0026#39;); $stmt-\u0026gt;execute([\u0026#39;nom\u0026#39; =\u0026gt; $nom, \u0026#39;id\u0026#39; =\u0026gt; $id]); $redis-\u0026gt;del(\u0026#34;user:{$id}\u0026#34;); // invalidation immédiate du cache } # Quelques structures de données Redis courantes SET session:42 \u0026#34;...\u0026#34; # chaîne simple : session, compteur, cache d\u0026#39;objet sérialisé EXPIRE session:42 3600 # TTL sur n\u0026#39;importe quelle clé HSET user:42 nom \u0026#34;Kevin\u0026#34; role \u0026#34;admin\u0026#34; # hash : objet structuré (équivalent d\u0026#39;une ligne) LPUSH notifications:42 \u0026#34;Nouveau message\u0026#34; # liste : file d\u0026#39;événements récents SADD tags:article:7 \u0026#34;sql\u0026#34; \u0026#34;redis\u0026#34; # set : appartenance sans doublon ZADD classement 1500 \u0026#34;joueur:42\u0026#34; # sorted set : classement trié par score Quand utiliser Redis en cache ? # Sur des données lues très fréquemment et modifiées relativement rarement (profils, configuration, catalogue). Pour soulager une base de données sous forte charge en lecture, sans devoir la faire grossir ou la répliquer immédiatement. Pour des besoins allant au-delà du simple cache : sessions utilisateur, compteurs en temps réel, files d\u0026rsquo;attente légères, classements (sorted sets). Moins adapté comme source de vérité unique pour des données critiques nécessitant des garanties transactionnelles fortes et une persistance stricte comparable à un moteur relationnel — Redis reste avant tout une couche de vitesse, pas un remplacement de la base principale. Points importants # Le cache-aside n\u0026rsquo;élimine pas totalement le risque d\u0026rsquo;incohérence temporaire : entre l\u0026rsquo;écriture en base et l\u0026rsquo;invalidation du cache, une lecture concurrente peut voir une donnée périmée pendant une fenêtre très courte. Toujours définir un TTL, même généreux, comme filet de sécurité en complément de l\u0026rsquo;invalidation explicite : ça évite qu\u0026rsquo;un oubli d\u0026rsquo;invalidation ne laisse une donnée obsolète indéfiniment. Redis persiste optionnellement sur disque (RDB, AOF), mais son cas d\u0026rsquo;usage principal reste la vitesse en mémoire : ne pas s\u0026rsquo;appuyer dessus comme unique copie durable d\u0026rsquo;une donnée critique. Le choix de la structure de données (string, hash, list, set, sorted set) influence directement les opérations possibles et la performance — un hash est souvent préférable à une string JSON quand on doit lire ou modifier un seul champ sans recharger tout l\u0026rsquo;objet. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/bases-de-donnees/cache-redis/","section":"Bases de données","summary":"Redis est une base clé-valeur en mémoire, utilisée principalement comme cache devant une base relationnelle. Le pattern cache-aside, un TTL bien choisi et une stratégie d\u0026rsquo;invalidation claire sont les trois piliers d\u0026rsquo;un cache Redis fiable.","title":"Cache Redis"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/ci-cd/","section":"Tags","summary":"","title":"Ci-Cd"},{"content":"Le problème #Sans automatisation, mettre du code en production suit un rituel manuel et fragile : un développeur récupère les changements, lance les tests à la main (ou les oublie), compile le projet, se connecte au serveur et copie les fichiers, redémarre le service, et croise les doigts. Chaque étape oubliée ou mal exécutée est une source potentielle de bug en production. Et plus l\u0026rsquo;équipe grandit, plus ce rituel devient un goulot d\u0026rsquo;étranglement : une seule personne qui sait \u0026ldquo;comment on déploie\u0026rdquo; devient un point de défaillance critique.\nLe second problème est la détection tardive des régressions. Si les tests ne sont exécutés qu\u0026rsquo;une fois par semaine, ou seulement avant une mise en production, un bug introduit le lundi peut n\u0026rsquo;être découvert que le vendredi — après avoir été mélangé au travail de plusieurs autres développeurs, rendant sa cause beaucoup plus difficile à isoler.\nL\u0026rsquo;idée générale #CI/CD désigne l\u0026rsquo;automatisation du chemin entre l\u0026rsquo;écriture du code et sa mise en production, découpée en plusieurs pratiques complémentaires :\nContinuous Integration (intégration continue) : à chaque commit poussé, un pipeline automatisé compile le projet et exécute les tests. L\u0026rsquo;objectif est de détecter une régression immédiatement, pas plus tard. Continuous Delivery (livraison continue) : le pipeline va plus loin et prépare automatiquement un artefact prêt à être déployé (image Docker, package versionné), mais le déclenchement final vers la production reste une décision humaine. Continuous Deployment (déploiement continu) : dernière étape de l\u0026rsquo;automatisation — chaque changement qui passe l\u0026rsquo;intégralité du pipeline est déployé en production sans validation manuelle. Un pipeline CI/CD typique enchaîne plusieurs étapes séquentielles : récupération du code, installation des dépendances, analyse statique (linting), tests unitaires, tests d\u0026rsquo;intégration, build de l\u0026rsquo;artefact, puis déploiement. Chaque étape doit pouvoir faire échouer le pipeline pour empêcher qu\u0026rsquo;un code défaillant progresse plus loin dans la chaîne.\nAnalogie du quotidien #Le CI/CD, c\u0026rsquo;est comme une chaîne de contrôle qualité dans une usine automobile. Sans contrôle continu, on assemble toutes les pièces d\u0026rsquo;une voiture et on ne teste qu\u0026rsquo;à la toute fin, en espérant que tout fonctionne — si un défaut est détecté sur le moteur, il faut potentiellement démonter toute la carrosserie déjà assemblée pour y accéder, un coût énorme.\nAvec un contrôle qualité continu, chaque pièce est vérifiée dès qu\u0026rsquo;elle est montée : le moteur est testé avant que la carrosserie ne soit posée dessus, les freins sont vérifiés avant que les roues ne soient recouvertes. Un défaut est détecté au plus tôt, là où il coûte le moins cher à corriger — exactement le principe du fail fast en CI/CD.\nDiagramme # flowchart LR Commit[Commit poussé] --\u003e Lint[Analyse statique] Lint --\u003e|OK| Test[Tests unitaires + intégration] Lint --\u003e|échec| Fail1[Pipeline stoppé] Test --\u003e|OK| Build[Build de l'image Docker] Test --\u003e|échec| Fail2[Pipeline stoppé] Build --\u003e Registry[Publication sur le registre d'images] Registry --\u003e Staging[Déploiement en staging] Staging --\u003e Approve{Validation manuelle ?} Approve --\u003e|Livraison continue| Manuel[Déclenchement manuel en production] Approve --\u003e|Déploiement continu| Auto[Déploiement automatique en production] Exemple de code ## .github/workflows/ci-cd.yml — pipeline GitHub Actions name: CI/CD on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Installer Node.js uses: actions/setup-node@v4 with: node-version: \u0026#34;20\u0026#34; cache: \u0026#34;npm\u0026#34; - name: Installer les dépendances run: npm ci - name: Analyse statique run: npm run lint - name: Tests unitaires run: npm test -- --coverage build-and-deploy: needs: test if: github.ref == \u0026#39;refs/heads/main\u0026#39; runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Connexion au registre d\u0026#39;images uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build et publication de l\u0026#39;image uses: docker/build-push-action@v5 with: push: true tags: ghcr.io/monorg/mon-api:${{ github.sha }} - name: Déployer sur le cluster de production run: | kubectl set image deployment/mon-api \\ api=ghcr.io/monorg/mon-api:${{ github.sha }} \\ --record Quand utiliser le CI/CD ? # Dès qu\u0026rsquo;une équipe compte plus d\u0026rsquo;une personne : la détection immédiate des conflits et des régressions justifie à elle seule l\u0026rsquo;investissement initial. Quand la fréquence de mise en production doit augmenter sans augmenter le risque associé à chaque déploiement. Le déploiement continu complet (sans validation manuelle) convient bien aux équipes avec une couverture de tests solide et des mécanismes de rollback automatisés ; à l\u0026rsquo;inverse, la livraison continue avec validation manuelle reste préférable pour des systèmes critiques où une supervision humaine finale est requise (santé, finance réglementée, par exemple). Points importants # Un pipeline CI/CD n\u0026rsquo;a de valeur que si les tests qu\u0026rsquo;il exécute sont fiables et suffisamment complets : automatiser un pipeline qui ne teste presque rien donne une fausse impression de sécurité. La vitesse du pipeline compte autant que sa fiabilité : un pipeline qui prend 45 minutes décourage les commits fréquents et ralentit toute l\u0026rsquo;équipe — la parallélisation des étapes indépendantes (lint et tests en parallèle, par exemple) est une optimisation courante. Les secrets (identifiants de déploiement, clés d\u0026rsquo;API) ne doivent jamais être en clair dans le pipeline : les plateformes CI/CD fournissent un stockage de secrets dédié, chiffré et injecté au runtime. Le CI/CD est indissociable de l\u0026rsquo;Infrastructure as Code : décrire l\u0026rsquo;infrastructure cible dans des fichiers versionnés permet au pipeline de déployer de façon reproductible, sans configuration manuelle du serveur cible. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/cloud-devops/ci-cd/","section":"Cloud \u0026 DevOps","summary":"CI/CD désigne l\u0026rsquo;automatisation du chemin entre un commit de code et sa mise en production : compilation, tests, validation, puis déploiement, exécutés automatiquement à chaque changement plutôt qu\u0026rsquo;à la main.","title":"CI/CD"},{"content":"Le problème #Un projet démarre souvent avec le framework au centre : les routes appellent des contrôleurs, qui appellent directement l\u0026rsquo;ORM, qui parle à la base de données. Ça va vite, au début. Mais la logique métier — les vraies règles de l\u0026rsquo;entreprise — se retrouve éparpillée dans les contrôleurs, les modèles et parfois même les vues.\nLe jour où il faut changer de base de données, exposer la même logique via une API et une CLI, ou simplement écrire un test qui ne dépend pas d\u0026rsquo;une vraie base de données, on découvre que tout est emmêlé. Le métier dépend du framework, qui dépend de la base de données, qui dépend d\u0026rsquo;un service externe — impossible de tirer un seul fil sans faire bouger tout l\u0026rsquo;édifice.\nL\u0026rsquo;idée générale #La Clean Architecture (formalisée par Robert C. Martin) organise le code en cercles concentriques. Plus on va vers le centre, plus le code est stable et indépendant des détails techniques :\nEntities (centre) : les règles métier les plus générales, indépendantes de toute application précise. Use Cases : les règles métier spécifiques à l\u0026rsquo;application — ce que le système fait réellement. Interface Adapters : les contrôleurs, présentateurs, et convertisseurs entre le monde extérieur et les use cases. Frameworks \u0026amp; Drivers (extérieur) : la base de données, le framework web, l\u0026rsquo;UI — les détails. La règle qui tient tout l\u0026rsquo;édifice s\u0026rsquo;appelle la règle de dépendance : le code source ne peut dépendre que vers l\u0026rsquo;intérieur. Rien dans un cercle intérieur ne peut connaître quoi que ce soit d\u0026rsquo;un cercle extérieur — ni son nom, ni son existence.\nAnalogie du quotidien #La Clean Architecture, c\u0026rsquo;est comme l\u0026rsquo;organisation d\u0026rsquo;un restaurant. La recette (le métier) ne dépend pas du fournisseur de légumes précis, ni du modèle de four utilisé. Le chef applique sa recette — faire revenir, assaisonner, dresser — peu importe si les légumes viennent du marché du coin ou d\u0026rsquo;un grossiste, peu importe si le four est à gaz ou électrique.\nLes fournisseurs et les équipements (la base de données, le framework, l\u0026rsquo;API externe) sont interchangeables. La recette, elle, reste stable : c\u0026rsquo;est elle qui définit le résultat, pas les outils utilisés pour l\u0026rsquo;obtenir. Si demain le restaurant change de fournisseur de légumes, la recette ne change pas — seul le \u0026ldquo;branchement\u0026rdquo; avec le nouveau fournisseur doit être adapté.\nDiagramme # flowchart TD subgraph Ext[\"Frameworks \u0026 Drivers\"] DB[(Base de données)] Web[Framework web] end subgraph Adapt[\"Interface Adapters\"] Ctrl[Contrôleur] Repo[Implémentation Repository] end subgraph UC[\"Use Cases\"] Uc[CreerCommandeUseCase] end subgraph Ent[\"Entities\"] Ent1[Commande] end Web --\u003e Ctrl Ctrl --\u003e Uc Uc --\u003e Ent1 Uc --\u003e|dépend d'une interface| RepoI[[CommandeRepositoryInterface]] Repo -.implémente.-\u003e RepoI Repo --\u003e DB Exemple de code #PHP #// --- Domaine (centre) : aucune dépendance technique --- final class Commande { public function __construct( private string $id, private float $montant, ) {} public function estValide(): bool { return $this-\u0026gt;montant \u0026gt; 0; } } interface CommandeRepositoryInterface { public function sauvegarder(Commande $commande): void; } // --- Use case : orchestre le métier, dépend d\u0026#39;une interface, pas d\u0026#39;une implémentation --- final class CreerCommandeUseCase { public function __construct(private CommandeRepositoryInterface $repository) {} public function executer(string $id, float $montant): void { $commande = new Commande($id, $montant); if (!$commande-\u0026gt;estValide()) { throw new DomainException(\u0026#39;Commande invalide\u0026#39;); } $this-\u0026gt;repository-\u0026gt;sauvegarder($commande); } } // --- Détail technique (extérieur) : implémente l\u0026#39;interface, jamais l\u0026#39;inverse --- final class DoctrineCommandeRepository implements CommandeRepositoryInterface { public function __construct(private EntityManagerInterface $em) {} public function sauvegarder(Commande $commande): void { $this-\u0026gt;em-\u0026gt;persist($commande); $this-\u0026gt;em-\u0026gt;flush(); } } Java #// --- Domaine (centre) --- public final class Commande { private final String id; private final double montant; public Commande(String id, double montant) { this.id = id; this.montant = montant; } public boolean estValide() { return montant \u0026gt; 0; } } public interface CommandeRepository { void sauvegarder(Commande commande); } // --- Use case --- public class CreerCommandeUseCase { private final CommandeRepository repository; public CreerCommandeUseCase(CommandeRepository repository) { this.repository = repository; } public void executer(String id, double montant) { Commande commande = new Commande(id, montant); if (!commande.estValide()) { throw new IllegalStateException(\u0026#34;Commande invalide\u0026#34;); } repository.sauvegarder(commande); } } // --- Détail technique (extérieur) --- public class JpaCommandeRepository implements CommandeRepository { private final EntityManager em; public JpaCommandeRepository(EntityManager em) { this.em = em; } public void sauvegarder(Commande commande) { em.persist(commande); } } JavaScript #// --- Domaine (centre) --- class Commande { constructor(id, montant) { this.id = id; this.montant = montant; } estValide() { return this.montant \u0026gt; 0; } } // --- Use case : dépend d\u0026#39;une abstraction (un objet avec la méthode sauvegarder) --- class CreerCommandeUseCase { constructor(repository) { this.repository = repository; } executer(id, montant) { const commande = new Commande(id, montant); if (!commande.estValide()) { throw new Error(\u0026#34;Commande invalide\u0026#34;); } this.repository.sauvegarder(commande); } } // --- Détail technique (extérieur) --- class MongoCommandeRepository { constructor(collection) { this.collection = collection; } async sauvegarder(commande) { await this.collection.insertOne(commande); } } Quand utiliser ce pattern ? # Sur des applications avec une logique métier riche, qui doit rester stable même quand les choix techniques changent (base de données, framework, fournisseur externe). Quand plusieurs interfaces doivent exposer le même métier (API REST, CLI, worker asynchrone) sans dupliquer les règles. Quand la testabilité du métier, indépendamment de toute infrastructure, est une priorité. À éviter sur un petit projet ou un prototype : l\u0026rsquo;indirection ajoutée (interfaces, use cases, mapping) coûte cher si la logique métier est simple ou évolue peu. Points importants # La règle de dépendance se vérifie souvent par une question simple : ce fichier importe-t-il un framework, un ORM ou un client HTTP ? Si oui, ce n\u0026rsquo;est pas du domaine. Les interfaces qui permettent l\u0026rsquo;inversion de dépendance (comme CommandeRepositoryInterface) sont définies dans le domaine, mais implémentées à l\u0026rsquo;extérieur — c\u0026rsquo;est ce détail qui fait fonctionner toute l\u0026rsquo;architecture. Clean Architecture, architecture hexagonale et Ports \u0026amp; Adapters partagent le même objectif : ce sont des variantes d\u0026rsquo;un même principe, pas des concurrents à choisir absolument. Ce n\u0026rsquo;est pas gratuit : plus de fichiers, plus d\u0026rsquo;indirection. Le bénéfice apparaît sur la durée, pas sur le premier commit. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/architecture-logicielle/clean-architecture-expliquee-simplement/","section":"Architecture logicielle","summary":"La Clean Architecture organise le code en cercles concentriques : le métier au centre, les détails techniques (base de données, framework, UI) à l\u0026rsquo;extérieur. La règle est simple — les dépendances ne pointent jamais vers l\u0026rsquo;extérieur.","title":"Clean Architecture expliquée simplement"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/clean-architecture/","section":"Tags","summary":"","title":"Clean-Architecture"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/cloud/","section":"Tags","summary":"","title":"Cloud"},{"content":"Le Cloud et le DevOps ont changé la façon dont on livre du logiciel. Ces fiches démystifient le vocabulaire et les mécanismes qui reviennent le plus souvent en entretien.\n","date":null,"permalink":"https://www.mybearisyellow.me/cloud-devops/","section":"Cloud \u0026 DevOps","summary":"\u003cp\u003eLe Cloud et le DevOps ont changé la façon dont on livre du logiciel. Ces fiches démystifient le vocabulaire et les mécanismes qui reviennent le plus souvent en entretien.\u003c/p\u003e","title":"Cloud \u0026 DevOps"},{"content":"Le problème #Imaginez que vous stockiez toutes les commandes d\u0026rsquo;une boutique en ligne dans un seul grand tableau Excel : une ligne par commande, avec le nom du client, son adresse, son email, le produit acheté, son prix, répétés à chaque ligne. Un client qui passe dix commandes voit son adresse recopiée dix fois. Le jour où il déménage, il faut corriger dix lignes — et si on en oublie une, la donnée devient incohérente.\nCe problème n\u0026rsquo;est pas seulement une question d\u0026rsquo;espace disque gaspillé. C\u0026rsquo;est un problème de vérité unique : quand une même information existe à plusieurs endroits, on ne sait plus laquelle est la bonne le jour où elles divergent.\nL\u0026rsquo;idée générale #Une base de données relationnelle organise les données en tables, chacune représentant un type d\u0026rsquo;entité (clients, commandes, produits\u0026hellip;). Chaque table est composée de lignes (une ligne = une entité, ex. un client précis) et de colonnes (un attribut, ex. le nom, l\u0026rsquo;email).\nDeux mécanismes rendent ce modèle cohérent :\nLa clé primaire (PRIMARY KEY) : une colonne (ou combinaison de colonnes) qui identifie chaque ligne de façon unique et non ambiguë, le plus souvent un identifiant auto-incrémenté (id). La clé étrangère (FOREIGN KEY) : une colonne dans une table qui référence la clé primaire d\u0026rsquo;une autre table, créant ainsi une relation entre les deux — d\u0026rsquo;où le nom \u0026ldquo;relationnel\u0026rdquo;. En dessous de ce modèle logique, un moteur de stockage (InnoDB pour MySQL, le storage engine natif de PostgreSQL\u0026hellip;) se charge de la mécanique physique : écrire les pages sur disque, gérer les index, garantir les verrous. Le développeur n\u0026rsquo;a en général pas besoin d\u0026rsquo;y penser au quotidien — il raisonne en tables et en relations, le moteur s\u0026rsquo;occupe du reste.\nAnalogie du quotidien #Une base relationnelle fonctionne comme un système de classeurs dans une administration. Un classeur \u0026ldquo;Clients\u0026rdquo; contient une fiche par client, chacune avec un numéro de dossier unique. Un classeur \u0026ldquo;Commandes\u0026rdquo; contient une fiche par commande — mais au lieu de recopier toute la fiche du client sur chaque commande, on note simplement son numéro de dossier en référence.\nSi le client déménage, on corrige une seule fiche dans le classeur \u0026ldquo;Clients\u0026rdquo; : toutes les commandes qui référencent son numéro de dossier restent valides et à jour automatiquement, sans qu\u0026rsquo;on ait besoin de rouvrir chaque fiche de commande.\nDiagramme # erDiagram CLIENTS ||--o{ COMMANDES : passe COMMANDES ||--o{ LIGNES_COMMANDE : contient PRODUITS ||--o{ LIGNES_COMMANDE : référencé_par CLIENTS { int id PK string nom string email } COMMANDES { int id PK int client_id FK date date_commande } LIGNES_COMMANDE { int id PK int commande_id FK int produit_id FK int quantite } PRODUITS { int id PK string nom decimal prix } Exemple de code #CREATE TABLE clients ( id SERIAL PRIMARY KEY, nom VARCHAR(100) NOT NULL, email VARCHAR(150) UNIQUE NOT NULL ); CREATE TABLE commandes ( id SERIAL PRIMARY KEY, client_id INT NOT NULL REFERENCES clients(id), date_commande DATE NOT NULL DEFAULT CURRENT_DATE ); -- Récupérer chaque commande avec le nom du client associé SELECT c.id AS commande_id, cl.nom, c.date_commande FROM commandes c JOIN clients cl ON cl.id = c.client_id ORDER BY c.date_commande DESC; // Exemple avec PDO en PHP : lecture d\u0026#39;une commande et de son client $pdo = new PDO(\u0026#39;pgsql:host=localhost;dbname=boutique\u0026#39;, $user, $pass); $stmt = $pdo-\u0026gt;prepare( \u0026#39;SELECT c.id, cl.nom, c.date_commande FROM commandes c JOIN clients cl ON cl.id = c.client_id WHERE c.id = :id\u0026#39; ); $stmt-\u0026gt;execute([\u0026#39;id\u0026#39; =\u0026gt; $commandeId]); $commande = $stmt-\u0026gt;fetch(PDO::FETCH_ASSOC); Quand utiliser une base relationnelle ? # Quand les données ont une structure claire et stable (clients, commandes, produits) avec des relations bien définies entre elles. Quand la cohérence des données est prioritaire : pas de doublons, pas de valeurs orphelines, intégrité garantie par le moteur lui-même. Quand vous avez besoin de requêtes complexes croisant plusieurs entités (jointures, agrégations, filtres combinés). Moins adapté quand la structure des données change constamment ou que le volume impose une distribution horizontale massive — des bases NoSQL peuvent alors être plus pertinentes (voir l\u0026rsquo;article dédié). Points importants # Une clé primaire ne doit jamais être nulle ni dupliquée : c\u0026rsquo;est la garantie qu\u0026rsquo;on peut toujours identifier une ligne sans ambiguïté. Une clé étrangère peut être contrainte (FOREIGN KEY ... REFERENCES) pour empêcher la base d\u0026rsquo;accepter une commande liée à un client qui n\u0026rsquo;existe pas — c\u0026rsquo;est le moteur qui refuse l\u0026rsquo;insertion, pas le code applicatif. \u0026ldquo;Relationnel\u0026rdquo; ne veut pas dire \u0026ldquo;avec des relations compliquées\u0026rdquo; : ça veut simplement dire que les tables sont reliées entre elles par des clés, rien de plus. Le moteur de stockage (InnoDB, MyISAM, le moteur PostgreSQL\u0026hellip;) influence les performances et les garanties (verrouillage, transactions) mais pas la façon d\u0026rsquo;écrire du SQL : ce détail reste en grande partie invisible pour le développeur applicatif. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/bases-de-donnees/comment-fonctionne-une-base-relationnelle/","section":"Bases de données","summary":"Une base relationnelle range les données dans des tables reliées entre elles par des clés. Comprendre ce modèle — lignes, colonnes, clés primaires et étrangères — est le prérequis à tout le reste : requêtes, index, transactions.","title":"Comment fonctionne une base relationnelle ?"},{"content":"Le problème #Une classe GestionnaireCommande qui instancie directement un client MySQL, un client Stripe et un client SMTP à l\u0026rsquo;intérieur de ses méthodes fonctionne très bien\u0026hellip; jusqu\u0026rsquo;à ce qu\u0026rsquo;on essaie d\u0026rsquo;en écrire un test unitaire. Impossible de tester la logique de validation d\u0026rsquo;une commande sans faire tourner une vraie base de données, sans réellement débiter une carte bancaire de test, sans envoyer un vrai e-mail.\nLe même problème apparaît dès qu\u0026rsquo;il faut changer un fournisseur : remplacer Stripe par un autre prestataire de paiement oblige à retrouver et modifier chaque endroit du code qui a écrit new StripeClient(). Plus une classe connaît de détails concrets sur ses dépendances, plus elle devient fragile face à leurs changements — c\u0026rsquo;est ça, le couplage fort.\nL\u0026rsquo;idée générale #Le couplage mesure à quel point un composant dépend des détails internes d\u0026rsquo;un autre. Plus deux composants sont couplés, plus modifier l\u0026rsquo;un impose de modifier l\u0026rsquo;autre.\nCouplage fort : une classe connaît le type concret exact de sa dépendance, l\u0026rsquo;instancie elle-même (new), et souvent connaît aussi ses détails d\u0026rsquo;implémentation (méthodes propres à ce type précis, format de retour spécifique). Couplage faible : une classe dépend d\u0026rsquo;une abstraction — une interface — sans savoir quelle implémentation tourne réellement derrière. La dépendance lui est fournie de l\u0026rsquo;extérieur plutôt que créée en interne. Le levier principal pour passer d\u0026rsquo;un couplage fort à un couplage faible s\u0026rsquo;appelle l\u0026rsquo;injection de dépendance : au lieu qu\u0026rsquo;une classe construise elle-même ce dont elle a besoin, on le lui transmet — généralement via le constructeur. Combinée à une interface, l\u0026rsquo;injection de dépendance permet de changer d\u0026rsquo;implémentation (une vraie base de données contre un stockage en mémoire pour les tests, Stripe contre PayPal) sans toucher une seule ligne de la classe qui l\u0026rsquo;utilise.\nLe couplage faible n\u0026rsquo;est pas gratuit : il ajoute une interface et une indirection. Ce coût se justifie dès qu\u0026rsquo;il existe un vrai besoin de substitution — tester sans dépendance réelle, ou supporter plusieurs implémentations en production.\nAnalogie du quotidien #Le couplage fort, c\u0026rsquo;est comme une télécommande de télévision soudée en usine à un unique modèle de téléviseur, avec un connecteur propriétaire. Si le téléviseur tombe en panne, il faut jeter la télécommande avec — elle ne fonctionne avec rien d\u0026rsquo;autre.\nLe couplage faible, c\u0026rsquo;est une télécommande universelle qui communique avec n\u0026rsquo;importe quel téléviseur respectant le protocole infrarouge standard. La télécommande ne connaît pas la marque du téléviseur en face d\u0026rsquo;elle : elle envoie un signal standard (« allumer », « monter le volume »), et n\u0026rsquo;importe quel appareil compatible avec ce protocole y répond. Changer de téléviseur ne demande pas de changer de télécommande — seul le respect du protocole commun compte.\nDiagramme # classDiagram class GestionnaireCommandeCouple { -client: StripeClient +payer(commande) } class StripeClient { +charge(montant) } GestionnaireCommandeCouple --\u003e StripeClient : dépend directement (couplage fort) class GestionnaireCommandeDecouple { -passerelle: PassionPaiementInterface +payer(commande) } class PassionPaiementInterface { \u003c\u003e +payer(montant) } class StripeAdapter { +payer(montant) } class PaypalAdapter { +payer(montant) } GestionnaireCommandeDecouple --\u003e PassionPaiementInterface : dépend d'une abstraction (couplage faible) PassionPaiementInterface \u003c|.. StripeAdapter PassionPaiementInterface \u003c|.. PaypalAdapter Exemple de code #PHP #// --- AVANT : couplage fort --- final class GestionnaireCommande { public function payer(Commande $commande): void { // La classe connaît et instancie directement une implémentation concrète. $stripe = new \\Stripe\\StripeClient(getenv(\u0026#39;STRIPE_KEY\u0026#39;)); $stripe-\u0026gt;charges-\u0026gt;create([\u0026#39;amount\u0026#39; =\u0026gt; $commande-\u0026gt;montant()]); } } // Impossible de tester sans réellement appeler Stripe. // Impossible de changer de prestataire sans modifier cette classe. // --- APRÈS : couplage faible via interface + injection de dépendance --- interface PasserellePaiement { public function payer(int $montantEnCentimes): void; } final class StripePasserelle implements PasserellePaiement { public function __construct(private \\Stripe\\StripeClient $client) {} public function payer(int $montantEnCentimes): void { $this-\u0026gt;client-\u0026gt;charges-\u0026gt;create([\u0026#39;amount\u0026#39; =\u0026gt; $montantEnCentimes]); } } final class GestionnaireCommande { public function __construct(private PasserellePaiement $passerelle) {} public function payer(Commande $commande): void { $this-\u0026gt;passerelle-\u0026gt;payer($commande-\u0026gt;montant()); } } // Le test unitaire n\u0026#39;a plus besoin de Stripe : une fausse implémentation suffit. final class FaussePasserelle implements PasserellePaiement { public array $appels = []; public function payer(int $montantEnCentimes): void { $this-\u0026gt;appels[] = $montantEnCentimes; } } Java #// --- AVANT : couplage fort --- public class GestionnaireCommande { public void payer(Commande commande) { StripeClient stripe = new StripeClient(System.getenv(\u0026#34;STRIPE_KEY\u0026#34;)); stripe.charge(commande.getMontant()); } } // --- APRÈS : couplage faible via interface + injection de dépendance --- public interface PasserellePaiement { void payer(long montantEnCentimes); } public class StripePasserelle implements PasserellePaiement { private final StripeClient client; public StripePasserelle(StripeClient client) { this.client = client; } public void payer(long montantEnCentimes) { client.charge(montantEnCentimes); } } public class GestionnaireCommande { private final PasserellePaiement passerelle; public GestionnaireCommande(PasserellePaiement passerelle) { this.passerelle = passerelle; } public void payer(Commande commande) { passerelle.payer(commande.getMontant()); } } Quand viser un couplage faible ? # Quand une classe dépend d\u0026rsquo;un service externe susceptible de changer (fournisseur de paiement, base de données, API tierce). Quand vous voulez tester une classe unitairement, sans faire tourner une vraie infrastructure. Quand plusieurs implémentations réelles doivent coexister (plusieurs fournisseurs de notification selon le pays, par exemple). Inutile en revanche pour une dépendance purement interne, stable, sans effet de bord — comme une fonction utilitaire de formatage : y ajouter une interface n\u0026rsquo;apporte que de l\u0026rsquo;indirection sans bénéfice réel. Points importants # Le signal d\u0026rsquo;alarme le plus fiable d\u0026rsquo;un couplage fort : un new NomDeClasseConcrete() au milieu d\u0026rsquo;une méthode métier qui parle à une technologie précise. L\u0026rsquo;injection de dépendance n\u0026rsquo;est qu\u0026rsquo;une technique — le vrai principe est de dépendre d\u0026rsquo;abstractions plutôt que d\u0026rsquo;implémentations concrètes. Un couplage faible mal utilisé (interface partout, même sans besoin réel de substitution) ajoute de la complexité sans bénéfice : le but n\u0026rsquo;est pas zéro couplage, mais un couplage maîtrisé aux bons endroits. Ce principe est au cœur du D de SOLID (Dependency Inversion), détaillé dans l\u0026rsquo;article SOLID expliqué simplement. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/architecture-logicielle/couplage-fort-vs-faible/","section":"Architecture logicielle","summary":"Le couplage mesure à quel point un composant dépend d\u0026rsquo;un autre. Un couplage fort rend le système fragile et difficile à faire évoluer ; un couplage faible, obtenu via des interfaces et l\u0026rsquo;injection de dépendance, permet de changer une implémentation sans toucher au code qui l\u0026rsquo;utilise.","title":"Couplage fort vs faible"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/ddd/","section":"Tags","summary":"","title":"Ddd"},{"content":"Un design pattern n\u0026rsquo;est pas une formule magique : c\u0026rsquo;est une solution éprouvée à un problème récurrent. Chaque fiche suit la même trame — le problème, l\u0026rsquo;idée, une analogie, un diagramme, du code — pour que la logique reste avec vous longtemps après la lecture.\n","date":null,"permalink":"https://www.mybearisyellow.me/design-patterns/","section":"Design Patterns","summary":"\u003cp\u003eUn design pattern n\u0026rsquo;est pas une formule magique : c\u0026rsquo;est une solution éprouvée à un problème récurrent. Chaque fiche suit la même trame — le problème, l\u0026rsquo;idée, une analogie, un diagramme, du code — pour que la logique reste avec vous longtemps après la lecture.\u003c/p\u003e","title":"Design Patterns"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/design-patterns/","section":"Tags","summary":"","title":"Design-Patterns"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/devops/","section":"Tags","summary":"","title":"Devops"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/docker/","section":"Tags","summary":"","title":"Docker"},{"content":"Le problème #\u0026ldquo;Ça marche sur ma machine\u0026rdquo; est sans doute la phrase la plus redoutée en développement logiciel. Un développeur teste son application en local avec la version 18 de Node.js, une certaine version d\u0026rsquo;une bibliothèque système, une configuration précise — et tout fonctionne. Une fois déployée en production, sur une machine avec une version différente de Node.js ou une dépendance système manquante, l\u0026rsquo;application plante, ou pire, se comporte différemment sans erreur visible.\nAvant les conteneurs, la solution consistait à documenter méticuleusement chaque dépendance et chaque étape d\u0026rsquo;installation — un processus fragile, jamais totalement à jour, et impossible à automatiser fiablement. Recréer un environnement identique en production, en CI, ou sur la machine d\u0026rsquo;un nouveau collègue, prenait des heures et échouait souvent sur un détail oublié.\nL\u0026rsquo;idée générale #Docker résout ce problème en empaquetant une application avec tout ce dont elle a besoin pour fonctionner — code, runtime, bibliothèques système, variables de configuration — dans une unité autonome appelée conteneur. Ce conteneur s\u0026rsquo;exécute de façon identique sur n\u0026rsquo;importe quelle machine dotée du moteur Docker, qu\u0026rsquo;il s\u0026rsquo;agisse du poste du développeur, d\u0026rsquo;un serveur CI ou d\u0026rsquo;une machine de production.\nDeux concepts sont centraux :\nL\u0026rsquo;image : un modèle figé et versionné, construit à partir d\u0026rsquo;un fichier de description (Dockerfile), qui contient tout le système de fichiers nécessaire à l\u0026rsquo;application. Le conteneur : une instance en cours d\u0026rsquo;exécution de cette image, isolée du reste du système grâce aux fonctionnalités du noyau Linux (namespaces, cgroups). Contrairement à une machine virtuelle, un conteneur ne virtualise pas de matériel ni de système d\u0026rsquo;exploitation complet : il partage le noyau de la machine hôte et n\u0026rsquo;isole que l\u0026rsquo;espace utilisateur. C\u0026rsquo;est ce qui le rend nettement plus léger et rapide à démarrer qu\u0026rsquo;une VM.\nAnalogie du quotidien #Docker, c\u0026rsquo;est comme un conteneur de fret maritime. Avant la conteneurisation du transport, chaque cargaison (des sacs de café, des machines, des meubles) était chargée et déchargée manuellement, avec un arrimage différent selon le bateau, le camion ou le train utilisé — lent, coûteux, et source d\u0026rsquo;erreurs. Le conteneur standardisé a changé la donne : peu importe ce qu\u0026rsquo;il contient à l\u0026rsquo;intérieur, sa forme extérieure est toujours la même, et n\u0026rsquo;importe quel bateau, camion ou grue conçu pour manipuler des conteneurs sait le charger, le transporter et le décharger sans adaptation.\nDe la même façon, un conteneur Docker a une interface standard : peu importe ce qu\u0026rsquo;il y a à l\u0026rsquo;intérieur (une API Node.js, un service Python, une base de données), n\u0026rsquo;importe quelle machine avec Docker installé sait l\u0026rsquo;exécuter de la même manière.\nDiagramme # flowchart TD subgraph Hote[\"Machine hôte (un seul OS, un seul noyau)\"] Docker[Moteur Docker] subgraph C1[\"Conteneur API\"] App1[Code + dépendances Node.js] end subgraph C2[\"Conteneur Base de données\"] App2[PostgreSQL + config] end subgraph C3[\"Conteneur Worker\"] App3[Script Python + libs] end Docker --\u003e C1 Docker --\u003e C2 Docker --\u003e C3 end Image[(Image Docker versionnée)] --\u003e|docker run| C1 Exemple de code ## Dockerfile — image d\u0026#39;une API Node.js # Étape 1 : build, avec toutes les dépendances de développement FROM node:20-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Étape 2 : image finale, allégée, sans les outils de build FROM node:20-alpine WORKDIR /app ENV NODE_ENV=production COPY --from=build /app/dist ./dist COPY --from=build /app/node_modules ./node_modules COPY package*.json ./ EXPOSE 3000 USER node CMD [\u0026#34;node\u0026#34;, \u0026#34;dist/index.js\u0026#34;] # Construire l\u0026#39;image, puis lancer un conteneur à partir de celle-ci docker build -t mon-api:1.0 . docker run -d -p 3000:3000 --name mon-api-container mon-api:1.0 # Inspecter les logs du conteneur en cours d\u0026#39;exécution docker logs -f mon-api-container # docker-compose.yml — orchestrer l\u0026#39;API et sa base de données ensemble version: \u0026#34;3.9\u0026#34; services: api: build: . ports: - \u0026#34;3000:3000\u0026#34; environment: DATABASE_URL: postgres://user:pass@db:5432/app depends_on: - db db: image: postgres:16-alpine environment: POSTGRES_USER: user POSTGRES_PASSWORD: pass POSTGRES_DB: app volumes: - db_data:/var/lib/postgresql/data volumes: db_data: Quand utiliser Docker ? # Pour garantir qu\u0026rsquo;une application se comporte de façon identique entre le poste du développeur, l\u0026rsquo;environnement de CI et la production. Pour isoler les dépendances de plusieurs services qui tournent sur la même machine, sans conflit de versions. Comme brique de base avant d\u0026rsquo;aller vers l\u0026rsquo;orchestration (Kubernetes) quand une application est composée de plusieurs services à faire coopérer. Moins pertinent pour une application monolithique simple sans contrainte de portabilité, où la charge d\u0026rsquo;apprentissage et de maintenance des images peut ne pas se justifier immédiatement — même si l\u0026rsquo;usage s\u0026rsquo;est largement généralisé, y compris pour de petits projets. Points importants # Chaque instruction du Dockerfile crée une couche mise en cache : placer les instructions qui changent le moins souvent (installation des dépendances) avant celles qui changent souvent (copie du code source) accélère nettement les rebuilds. Un build multi-étapes (multi-stage build, comme dans l\u0026rsquo;exemple ci-dessus) permet de garder l\u0026rsquo;image finale légère en excluant les outils utilisés uniquement pendant la compilation. Un conteneur est éphémère par nature : toute donnée écrite à l\u0026rsquo;intérieur disparaît à sa suppression, sauf si elle est stockée dans un volume monté depuis l\u0026rsquo;extérieur. Docker seul suffit pour un service unique ; dès que plusieurs conteneurs doivent être orchestrés, redémarrés automatiquement ou répartis sur plusieurs machines, c\u0026rsquo;est le rôle de Kubernetes ou d\u0026rsquo;un orchestrateur équivalent. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/cloud-devops/docker-explique-simplement/","section":"Cloud \u0026 DevOps","summary":"Docker empaquette une application avec toutes ses dépendances dans un conteneur : un environnement isolé, portable et identique du poste du développeur à la production. Fini le \u0026ldquo;ça marche sur ma machine\u0026rdquo;.","title":"Docker expliqué simplement"},{"content":"Le problème #Un système qui grossit finit presque toujours par accumuler des mots ambigus. Le terme « Client » désigne, selon l\u0026rsquo;endroit du code, soit un compte de facturation, soit un contact commercial, soit un utilisateur connecté à l\u0026rsquo;application. Chaque équipe ajoute des champs à un même objet Client pour répondre à ses propres besoins, jusqu\u0026rsquo;à obtenir une classe de 40 attributs dont personne ne comprend plus vraiment le rôle exact.\nLe second symptôme est linguistique : les développeurs parlent de « records », de « rows », de « DTOs », pendant que les experts métier parlent de « contrats », de « résiliations », de « sinistres ». La traduction entre ces deux vocabulaires se perd à chaque réunion, et chaque malentendu se traduit tôt ou tard par un bug ou une fonctionnalité mal comprise.\nL\u0026rsquo;idée générale #Le Domain Driven Design, formalisé par Eric Evans, part d\u0026rsquo;un principe simple : le code doit refléter le métier aussi fidèlement que possible, avec exactement le même vocabulaire. C\u0026rsquo;est autant une démarche de modélisation qu\u0026rsquo;une technique de code — le DDD se divise d\u0026rsquo;ailleurs traditionnellement en deux volets :\nLe DDD stratégique, qui s\u0026rsquo;occupe des frontières :\nUbiquitous language (langage omniprésent) : un vocabulaire unique, défini avec les experts métier, utilisé aussi bien dans les conversations que dans le nom des classes et des méthodes. Si les experts métier disent « résilier un contrat », le code contient une méthode resilier(), pas updateStatus(). Bounded context (contexte borné) : une frontière explicite à l\u0026rsquo;intérieur de laquelle un mot a un sens unique et précis. Le mot « Client » peut légitimement désigner des choses différentes dans le contexte Facturation et dans le contexte Support — tant que chaque contexte a sa propre définition cohérente, il n\u0026rsquo;y a pas de conflit. Le DDD tactique, qui s\u0026rsquo;occupe de la modélisation dans le code :\nEntité : un objet défini par une identité stable dans le temps, même si ses attributs changent. Value object : un objet défini uniquement par sa valeur, immuable, sans identité propre. Agrégat : un regroupement d\u0026rsquo;entités et de value objects traité comme une seule unité de cohérence, accessible uniquement via sa racine (aggregate root). Analogie du quotidien #Le bounded context, c\u0026rsquo;est comme le mot « pièce » selon qui vous parle. Pour un architecte, une pièce est un espace délimité par des murs, avec une surface et des ouvertures. Pour un comptable qui évalue un bien immobilier, une pièce est une unité de valorisation qui influence le prix au mètre carré. Pour un déménageur, une pièce est une unité de charge — un volume de cartons à transporter.\nPersonne n\u0026rsquo;a tort. Chacun utilise le mot « pièce » avec une définition cohérente dans son propre contexte de travail, et personne n\u0026rsquo;essaie de faire tenir les trois définitions dans un seul modèle universel. Le DDD applique exactement cette logique au logiciel : plutôt que de forcer un mot unique à satisfaire tout le monde, on accepte des définitions différentes dans des contextes différents, explicitement délimités.\nDiagramme # classDiagram class Commande { \u003c\u003e -id: CommandeId -lignes: LigneCommande[] -montantTotal: Money +ajouterLigne(produit, quantite) +valider() } class LigneCommande { \u003c\u003e -id: LigneId -produit: ProduitId -quantite: int } class Money { \u003c\u003e -montant: decimal -devise: string +additionner(m: Money) Money } class CommandeId { \u003c\u003e -valeur: UUID } Commande \"1\" *-- \"1..*\" LigneCommande : contient Commande --\u003e Money : montantTotal Commande --\u003e CommandeId : id Exemple de code #PHP #// --- Value Object : immuable, identifié par sa valeur --- final class Money { public function __construct( private readonly int $montantEnCentimes, private readonly string $devise, ) {} public function additionner(Money $autre): self { if ($this-\u0026gt;devise !== $autre-\u0026gt;devise) { throw new DomainException(\u0026#39;Devises incompatibles\u0026#39;); } return new self($this-\u0026gt;montantEnCentimes + $autre-\u0026gt;montantEnCentimes, $this-\u0026gt;devise); } public function egale(Money $autre): bool { return $this-\u0026gt;montantEnCentimes === $autre-\u0026gt;montantEnCentimes \u0026amp;\u0026amp; $this-\u0026gt;devise === $autre-\u0026gt;devise; } } // --- Entité : identité stable, mutable --- final class LigneCommande { public function __construct( private readonly string $id, private readonly string $produitId, private int $quantite, ) {} public function sousTotal(Money $prixUnitaire): Money { return new Money($prixUnitaire-\u0026gt;montant() * $this-\u0026gt;quantite, $prixUnitaire-\u0026gt;devise()); } } // --- Aggregate Root : seul point d\u0026#39;entrée autorisé --- final class Commande { /** @var LigneCommande[] */ private array $lignes = []; public function __construct(private readonly string $id) {} public function ajouterLigne(string $produitId, int $quantite): void { if ($quantite \u0026lt;= 0) { throw new DomainException(\u0026#39;Quantité invalide\u0026#39;); } $this-\u0026gt;lignes[] = new LigneCommande(uniqid(), $produitId, $quantite); } public function nombreDeLignes(): int { return count($this-\u0026gt;lignes); } } Java #// --- Value Object --- public final class Money { private final long montantEnCentimes; private final String devise; public Money(long montantEnCentimes, String devise) { this.montantEnCentimes = montantEnCentimes; this.devise = devise; } public Money additionner(Money autre) { if (!devise.equals(autre.devise)) { throw new IllegalArgumentException(\u0026#34;Devises incompatibles\u0026#34;); } return new Money(montantEnCentimes + autre.montantEnCentimes, devise); } @Override public boolean equals(Object o) { if (!(o instanceof Money)) return false; Money autre = (Money) o; return montantEnCentimes == autre.montantEnCentimes \u0026amp;\u0026amp; devise.equals(autre.devise); } } // --- Entité --- public final class LigneCommande { private final String id; private final String produitId; private final int quantite; public LigneCommande(String id, String produitId, int quantite) { this.id = id; this.produitId = produitId; this.quantite = quantite; } } // --- Aggregate Root --- public final class Commande { private final String id; private final List\u0026lt;LigneCommande\u0026gt; lignes = new ArrayList\u0026lt;\u0026gt;(); public Commande(String id) { this.id = id; } public void ajouterLigne(String produitId, int quantite) { if (quantite \u0026lt;= 0) { throw new IllegalArgumentException(\u0026#34;Quantité invalide\u0026#34;); } lignes.add(new LigneCommande(UUID.randomUUID().toString(), produitId, quantite)); } public int nombreDeLignes() { return lignes.size(); } } Quand utiliser le DDD ? # Quand la logique métier est riche, pleine de règles et d\u0026rsquo;exceptions, et pas un simple CRUD autour d\u0026rsquo;une base de données. Quand plusieurs équipes ou plusieurs sous-domaines utilisent les mêmes mots avec des sens différents, source de confusion et de bugs. Quand le système est amené à durer et à évoluer plusieurs années, avec des règles métier qui se complexifient au fil du temps. À éviter sur un projet simple ou un prototype : construire des agrégats, des value objects et des bounded contexts explicites pour un CRUD basique ajoute un coût de modélisation qui ne sera jamais rentabilisé. Points importants # Le DDD stratégique (langage, bounded context) apporte de la valeur dès le premier jour, même sans écrire une ligne de code tactique — c\u0026rsquo;est avant tout un exercice de clarification avec le métier. Un value object ne se modifie jamais : on en crée un nouveau. C\u0026rsquo;est ce qui le rend prévisible et sûr à partager entre plusieurs entités. Une racine d\u0026rsquo;agrégat est la seule porte d\u0026rsquo;entrée : on ne récupère et modifie jamais une LigneCommande isolément, toujours en passant par Commande. Le DDD partage sa philosophie avec la Clean Architecture et l\u0026rsquo;architecture hexagonale — isoler et protéger le métier — mais y ajoute une dimension : la rigueur du vocabulaire partagé avec les experts métier eux-mêmes. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/architecture-logicielle/domain-driven-design-ddd/","section":"Architecture logicielle","summary":"Le Domain Driven Design (DDD) est autant une démarche de modélisation métier qu\u0026rsquo;une technique de code. Il propose un vocabulaire partagé avec les experts métier, des frontières explicites entre sous-domaines (bounded context), et des règles précises pour modéliser entités, value objects et agrégats.","title":"Domain Driven Design (DDD)"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/event-driven/","section":"Tags","summary":"","title":"Event-Driven"},{"content":"Le problème #Toutes les entreprises n\u0026rsquo;ont pas besoin du même niveau de contrôle sur leur infrastructure. Une startup qui veut mettre en ligne une API le plus vite possible n\u0026rsquo;a pas envie de configurer un système d\u0026rsquo;exploitation, un pare-feu et un load balancer à la main. À l\u0026rsquo;inverse, une entreprise avec des contraintes réglementaires strictes ou une architecture réseau très spécifique a besoin de maîtriser chaque couche, jusqu\u0026rsquo;au système d\u0026rsquo;exploitation.\nSans vocabulaire commun pour désigner ces différents niveaux d\u0026rsquo;abstraction, les discussions entre équipes techniques et décideurs deviennent confuses : \u0026ldquo;on migre vers le cloud\u0026rdquo; ne veut rien dire tant qu\u0026rsquo;on n\u0026rsquo;a pas précisé quel type de cloud, et donc qui est responsable de quoi.\nL\u0026rsquo;idée générale #IaaS, PaaS et SaaS désignent trois niveaux d\u0026rsquo;abstraction dans la consommation du cloud, qui se distinguent par la frontière entre ce que le fournisseur gère et ce que vous gérez.\nIaaS (Infrastructure as a Service) : le fournisseur donne accès à des ressources brutes — machines virtuelles, stockage, réseau. Vous installez et configurez tout, à partir du système d\u0026rsquo;exploitation. Exemples : AWS EC2, Azure Virtual Machines, Google Compute Engine. PaaS (Platform as a Service) : le fournisseur gère le système d\u0026rsquo;exploitation, le runtime et l\u0026rsquo;infrastructure de déploiement. Vous vous concentrez sur votre code applicatif. Exemples : Heroku, AWS Elastic Beanstalk, Render, Vercel. SaaS (Software as a Service) : le fournisseur gère tout, y compris l\u0026rsquo;application elle-même. Vous utilisez le logiciel fini via un navigateur ou une API. Exemples : Gmail, Salesforce, Notion. Une image classique aide à retenir la répartition des responsabilités : dans un tableau où chaque ligne est une couche technique (application, données, runtime, middleware, OS, virtualisation, serveur, stockage, réseau), le nombre de lignes gérées \u0026ldquo;par vous\u0026rdquo; diminue à mesure qu\u0026rsquo;on avance de IaaS vers SaaS.\nAnalogie du quotidien #C\u0026rsquo;est comme se loger. Le IaaS, c\u0026rsquo;est louer un terrain nu : vous devez construire la maison, l\u0026rsquo;électricité, la plomberie, tout vous-même — mais vous avez une liberté totale sur l\u0026rsquo;architecture. Le PaaS, c\u0026rsquo;est louer un appartement meublé : les fondations, la structure et les équipements de base sont déjà là, vous n\u0026rsquo;avez qu\u0026rsquo;à emménager et vivre votre vie, avec quelques contraintes sur ce que vous pouvez modifier (pas question d\u0026rsquo;abattre un mur porteur). Le SaaS, c\u0026rsquo;est réserver une chambre d\u0026rsquo;hôtel : vous n\u0026rsquo;avez rien à gérer, ni le ménage ni la maintenance, vous utilisez le service tel quel, avec très peu de personnalisation possible.\nDiagramme # flowchart TB subgraph IaaS[\"IaaS — vous gérez tout à partir de l'OS\"] direction LR I1[Application] --- I2[Données] --- I3[Runtime] --- I4[OS] I5[Réseau / Stockage / Serveurs : fournisseur] end subgraph PaaS[\"PaaS — vous gérez le code applicatif\"] direction LR P1[Application] --- P2[Données] P3[Runtime / OS / Réseau / Serveurs : fournisseur] end subgraph SaaS[\"SaaS — vous utilisez le produit fini\"] direction LR S1[Tout est géré par le fournisseur] end IaaS --\u003e PaaS --\u003e SaaS Exemple de code #Le même objectif — déployer une API Node.js — illustre bien la différence de responsabilité entre IaaS et PaaS.\n# --- IaaS : vous configurez tout, à partir de zéro, sur une VM louée --- sudo apt update \u0026amp;\u0026amp; sudo apt install -y nodejs npm nginx git clone https://github.com/moncompte/mon-api.git cd mon-api \u0026amp;\u0026amp; npm install # Configuration manuelle de nginx en reverse proxy, du service systemd, # des certificats TLS, du firewall... sudo systemctl enable --now mon-api # --- PaaS : vous décrivez juste votre besoin, la plateforme fait le reste --- # app.yaml (exemple simplifié pour un PaaS type Elastic Beanstalk / Render) name: mon-api runtime: nodejs20 build: command: npm install start: command: npm start instances: min: 1 max: 5 # le PaaS gère lui-même le scaling automatique Quand utiliser IaaS, PaaS ou SaaS ? # IaaS : quand vous avez des besoins précis de configuration réseau, de conformité, ou une stack technique non standard qu\u0026rsquo;aucune plateforme ne supporte nativement. PaaS : quand l\u0026rsquo;objectif est de livrer vite un produit sans réinventer l\u0026rsquo;infrastructure de déploiement, et que les contraintes du PaaS (runtimes supportés, scaling automatique standard) sont acceptables. SaaS : quand le besoin est générique (messagerie, CRM, suivi de projet) et qu\u0026rsquo;il n\u0026rsquo;y a aucune valeur ajoutée à développer sa propre solution. Une même entreprise combine généralement les trois selon le besoin : impossible de tout faire en SaaS quand on développe un produit sur-mesure, et rarement pertinent de tout faire en IaaS quand des solutions PaaS ou SaaS existent déjà pour des besoins standards. Points importants # La frontière entre les modèles n\u0026rsquo;est pas toujours nette : Kubernetes managé, par exemple, se situe entre IaaS et PaaS, parfois appelé CaaS (Container as a Service). Monter en abstraction (vers le PaaS ou le SaaS) réduit la charge opérationnelle mais augmente la dépendance au fournisseur (vendor lock-in) : migrer hors d\u0026rsquo;un PaaS propriétaire peut être coûteux. Le choix du modèle a un impact direct sur le profil de l\u0026rsquo;équipe nécessaire : plus on descend vers IaaS, plus il faut des compétences systèmes et réseau en interne. En entretien, ce sujet est souvent utilisé pour vérifier que le candidat comprend que \u0026ldquo;le cloud\u0026rdquo; n\u0026rsquo;est pas un bloc monolithique, mais un ensemble de niveaux de responsabilité partagée entre client et fournisseur. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/cloud-devops/iaas-paas-saas/","section":"Cloud \u0026 DevOps","summary":"IaaS, PaaS et SaaS sont trois façons de consommer le cloud, qui se distinguent par la part de la stack technique gérée par le fournisseur. Plus on monte dans l\u0026rsquo;abstraction, moins on a de contrôle — mais moins on a aussi à maintenir.","title":"IaaS, PaaS, SaaS"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/index/","section":"Tags","summary":"","title":"Index"},{"content":"Le problème #Sans index, retrouver une ligne précise dans une table oblige le moteur à faire un scan complet : lire ligne par ligne, du début à la fin, en comparant chaque valeur au critère recherché. Sur une table de 100 lignes, ce n\u0026rsquo;est pas un problème. Sur une table de 50 millions de lignes, une requête qui devrait répondre en quelques millisecondes peut prendre plusieurs secondes, voire minutes.\nLe problème grossit avec les données : plus la table grandit, plus le coût d\u0026rsquo;un scan complet augmente linéairement. Une application qui semblait rapide en développement (peu de données) peut devenir inutilisable en production (des millions de lignes) si personne n\u0026rsquo;a pensé aux index.\nL\u0026rsquo;idée générale #Un index est une structure de données auxiliaire, maintenue à part de la table, qui permet de retrouver rapidement les lignes correspondant à une valeur sans les parcourir toutes. La structure la plus répandue est le B-Tree (arbre équilibré) : les valeurs indexées y sont organisées de façon triée et hiérarchique, ce qui permet de localiser une valeur en un petit nombre de comparaisons, plutôt qu\u0026rsquo;en parcourant chaque ligne.\nCe gain a un coût. À chaque INSERT, UPDATE ou DELETE, le moteur doit non seulement modifier la table, mais aussi mettre à jour chaque index concerné pour qu\u0026rsquo;il reste cohérent. Plus une table a d\u0026rsquo;index, plus les écritures sont lentes — c\u0026rsquo;est un compromis lecture/écriture, pas un gain gratuit.\nAnalogie du quotidien #Un index, c\u0026rsquo;est comme l\u0026rsquo;index alphabétique à la fin d\u0026rsquo;un livre technique. Sans lui, pour trouver toutes les pages qui parlent de \u0026ldquo;transactions\u0026rdquo;, il faudrait feuilleter le livre entier page par page. Avec l\u0026rsquo;index, on va directement à la lettre T, on trouve \u0026ldquo;transactions, p. 42, 108, 203\u0026rdquo; et on saute directement aux bonnes pages.\nMais cet index n\u0026rsquo;existe pas par magie : quelqu\u0026rsquo;un doit le maintenir à jour. Si l\u0026rsquo;auteur ajoute un chapitre au milieu du livre, il doit aussi mettre à jour l\u0026rsquo;index pour que les numéros de page restent corrects. C\u0026rsquo;est exactement le coût qu\u0026rsquo;un moteur de base de données paie à chaque écriture sur une table indexée.\nDiagramme # flowchart TD Root[\"Racine\\n(id 1-1000000)\"] Root --\u003e N1[\"Nœud\\nid 1-300000\"] Root --\u003e N2[\"Nœud\\nid 300001-700000\"] Root --\u003e N3[\"Nœud\\nid 700001-1000000\"] N1 --\u003e L1[\"Feuille\\nid 1-100000 → pointeurs vers lignes\"] N1 --\u003e L2[\"Feuille\\nid 100001-300000 → pointeurs vers lignes\"] N2 --\u003e L3[\"Feuille\\nid 300001-500000 → pointeurs vers lignes\"] N3 --\u003e L4[\"Feuille\\nid 700001-850000 → pointeurs vers lignes\"] Exemple de code #-- Sans index : recherche par email = scan complet sur des millions de lignes SELECT * FROM utilisateurs WHERE email = \u0026#39;kevin@example.com\u0026#39;; -- Créer un index sur la colonne fréquemment filtrée CREATE INDEX idx_utilisateurs_email ON utilisateurs (email); -- La même requête peut désormais utiliser l\u0026#39;index au lieu du scan complet -- Vérifier si le moteur utilise bien l\u0026#39;index EXPLAIN ANALYZE SELECT * FROM utilisateurs WHERE email = \u0026#39;kevin@example.com\u0026#39;; -- Index composite : utile si les requêtes filtrent sur plusieurs colonnes ensemble CREATE INDEX idx_commandes_client_date ON commandes (client_id, date_commande); // L\u0026#39;index est transparent côté code applicatif : la requête ne change pas, // seul le temps de réponse change radicalement selon qu\u0026#39;il existe ou non. $stmt = $pdo-\u0026gt;prepare(\u0026#39;SELECT * FROM utilisateurs WHERE email = :email\u0026#39;); $stmt-\u0026gt;execute([\u0026#39;email\u0026#39; =\u0026gt; $email]); $utilisateur = $stmt-\u0026gt;fetch(PDO::FETCH_ASSOC); Quand utiliser un index ? # Sur les colonnes fréquemment utilisées dans un WHERE, un JOIN, ou un ORDER BY — surtout sur de grandes tables. Sur les clés étrangères : la plupart des moteurs n\u0026rsquo;indexent pas automatiquement les colonnes FOREIGN KEY, ce qui peut rendre les jointures lentes si on l\u0026rsquo;oublie. Sur les colonnes sélectives (beaucoup de valeurs distinctes, comme un email) : un index sur une colonne peu sélective (comme un booléen actif) apporte souvent peu de gain. À éviter sur les tables à très forte fréquence d\u0026rsquo;écriture et peu lues, où le coût de maintenance de l\u0026rsquo;index dépasse le bénéfice en lecture. Points importants # Un index accélère la lecture mais ralentit l\u0026rsquo;écriture — ce n\u0026rsquo;est jamais un gain sans contrepartie, c\u0026rsquo;est un arbitrage à faire consciemment. EXPLAIN (ou EXPLAIN ANALYZE) permet de vérifier si le moteur utilise réellement un index plutôt que de le supposer. Un index composite (sur plusieurs colonnes) est efficace surtout si les colonnes sont utilisées dans le même ordre dans les requêtes — l\u0026rsquo;ordre des colonnes dans l\u0026rsquo;index compte. La clé primaire crée presque toujours un index automatiquement (souvent unique) : pas besoin de le recréer manuellement dans la plupart des moteurs. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/bases-de-donnees/index-bdd/","section":"Bases de données","summary":"Un index permet au moteur de retrouver des lignes sans parcourir toute la table, au prix d\u0026rsquo;un espace disque et d\u0026rsquo;un coût supplémentaire à chaque écriture. Comprendre ce compromis est essentiel pour optimiser des requêtes en entretien comme en production.","title":"Index BDD"},{"content":"Le problème #Configurer une infrastructure cloud à la main — cliquer dans une console pour créer un serveur, un réseau, une base de données — fonctionne pour un premier test, mais devient vite ingérable. Rien n\u0026rsquo;est documenté nulle part sauf dans la mémoire de la personne qui a fait les clics. Reproduire exactement le même environnement pour la pré-production, ou après un incident qui a détruit des ressources, devient un exercice hasardeux basé sur des souvenirs approximatifs et de la documentation jamais à jour.\nCe mode de fonctionnement (parfois appelé \u0026ldquo;ClickOps\u0026rdquo;) pose aussi un problème de traçabilité : impossible de savoir qui a changé quoi, quand, et pourquoi, ni de faire relire une modification d\u0026rsquo;infrastructure avant qu\u0026rsquo;elle ne soit appliquée — contrairement à du code applicatif, passé systématiquement en revue avant d\u0026rsquo;être fusionné.\nL\u0026rsquo;idée générale #L\u0026rsquo;Infrastructure as Code (IaC) consiste à décrire l\u0026rsquo;infrastructure — serveurs, réseaux, bases de données, permissions — dans des fichiers de configuration texte, versionnés dans un dépôt Git comme n\u0026rsquo;importe quel code applicatif. Un outil IaC (Terraform, Pulumi, AWS CloudFormation) lit ces fichiers et provisionne les ressources correspondantes chez le fournisseur cloud.\nDeux propriétés distinguent une bonne approche IaC :\nDéclarative plutôt qu\u0026rsquo;impérative : on décrit l\u0026rsquo;état final voulu (\u0026ldquo;je veux 3 serveurs derrière un load balancer\u0026rdquo;), pas la séquence d\u0026rsquo;étapes pour y arriver. L\u0026rsquo;outil compare cet état désiré à l\u0026rsquo;état réel du cloud et calcule lui-même les changements à appliquer. Idempotente : appliquer la même configuration plusieurs fois de suite produit toujours le même résultat, sans dupliquer de ressources ni provoquer d\u0026rsquo;effets de bord. Ces deux propriétés rendent l\u0026rsquo;infrastructure reproductible à l\u0026rsquo;identique (recréer un environnement de test conforme à la production en quelques minutes), relisable (une pull request sur un fichier Terraform se revoit comme du code), et auditable (l\u0026rsquo;historique Git devient l\u0026rsquo;historique de l\u0026rsquo;infrastructure).\nAnalogie du quotidien #L\u0026rsquo;Infrastructure as Code, c\u0026rsquo;est comme la différence entre construire un meuble à partir d\u0026rsquo;un plan IKEA versus improviser en atelier sans aucune notice. Sans plan, chaque meuble construit à la main varie légèrement, et si le premier casse, personne ne se souvient exactement comment le reproduire à l\u0026rsquo;identique. Avec un plan écrit et numéroté, n\u0026rsquo;importe qui peut reconstruire exactement le même meuble, vérifier le plan avant de commencer le montage, et corriger une erreur dans le plan une fois pour toutes plutôt que de la refaire à chaque nouvelle construction.\nLe plan IKEA, c\u0026rsquo;est le fichier Terraform : une description déclarative du résultat voulu, que n\u0026rsquo;importe qui peut suivre pour obtenir un résultat identique et reproductible.\nDiagramme # flowchart LR Dev[Développeur] --\u003e|écrit| Code[Fichiers .tf versionnés dans Git] Code --\u003e|pull request relue| Merge[Merge sur main] Merge --\u003e|déclenche| Plan[terraform plan : calcule les changements] Plan --\u003e|revue des changements| Apply[terraform apply] Apply --\u003e|provisionne| Cloud[(Infrastructure cloud réelle)] Cloud -.compare état réel vs désiré à chaque run.-\u003e Plan Exemple de code ## main.tf — provisionne un serveur web et son groupe de sécurité sur AWS terraform { required_providers { aws = { source = \u0026#34;hashicorp/aws\u0026#34; version = \u0026#34;~\u0026gt; 5.0\u0026#34; } } } provider \u0026#34;aws\u0026#34; { region = \u0026#34;eu-west-3\u0026#34; } resource \u0026#34;aws_security_group\u0026#34; \u0026#34;web\u0026#34; { name = \u0026#34;web-sg\u0026#34; description = \u0026#34;Autorise HTTP et HTTPS entrants\u0026#34; ingress { from_port = 443 to_port = 443 protocol = \u0026#34;tcp\u0026#34; cidr_blocks = [\u0026#34;0.0.0.0/0\u0026#34;] } egress { from_port = 0 to_port = 0 protocol = \u0026#34;-1\u0026#34; cidr_blocks = [\u0026#34;0.0.0.0/0\u0026#34;] } } resource \u0026#34;aws_instance\u0026#34; \u0026#34;web\u0026#34; { ami = \u0026#34;ami-0c94855ba95c71c99\u0026#34; instance_type = \u0026#34;t3.micro\u0026#34; security_groups = [aws_security_group.web.name] tags = { Name = \u0026#34;serveur-web-01\u0026#34; } } output \u0026#34;adresse_ip_publique\u0026#34; { value = aws_instance.web.public_ip } # Prévisualiser les changements avant de les appliquer (idempotent) terraform plan # Appliquer réellement les changements calculés terraform apply # Rejouer la même commande ne recrée rien si l\u0026#39;état n\u0026#39;a pas changé terraform apply # → \u0026#34;No changes. Infrastructure is up-to-date.\u0026#34; Quand utiliser l\u0026rsquo;Infrastructure as Code ? # Dès qu\u0026rsquo;une infrastructure doit être reproduite à l\u0026rsquo;identique dans plusieurs environnements (développement, staging, production). Quand la traçabilité et la revue des changements d\u0026rsquo;infrastructure sont importantes, en particulier en équipe. Quand l\u0026rsquo;infrastructure évolue fréquemment et que la configuration manuelle devient une source d\u0026rsquo;erreurs ou d\u0026rsquo;oublis. Moins critique pour une expérimentation ponctuelle et jetable, où la vitesse de mise en place prime sur la reproductibilité — mais même dans ce cas, l\u0026rsquo;habitude de l\u0026rsquo;IaC évite les mauvaises surprises si l\u0026rsquo;expérimentation devient permanente. Points importants # Le principal risque de l\u0026rsquo;IaC est le drift : une modification manuelle faite en dehors du code (directement dans la console) désynchronise l\u0026rsquo;état réel de l\u0026rsquo;état décrit, et peut être écrasée sans prévenir à la prochaine application. L\u0026rsquo;état (state) de Terraform, qui mémorise la correspondance entre le code et les ressources réelles, doit lui-même être stocké et partagé de façon sécurisée (souvent dans un bucket distant avec verrouillage), jamais laissé uniquement en local. L\u0026rsquo;IaC ne se limite pas à Terraform : Kubernetes avec ses manifestes YAML est également une forme d\u0026rsquo;Infrastructure as Code, déclarative par nature. Une erreur dans du code IaC peut avoir un impact bien plus large et rapide qu\u0026rsquo;une erreur de clic manuel isolée : une revue de code rigoureuse sur les changements d\u0026rsquo;infrastructure est donc au moins aussi importante que sur le code applicatif. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/cloud-devops/infrastructure-as-code/","section":"Cloud \u0026 DevOps","summary":"L\u0026rsquo;Infrastructure as Code consiste à décrire l\u0026rsquo;infrastructure cloud (serveurs, réseaux, bases de données) dans des fichiers de configuration versionnés, plutôt que de la configurer manuellement via une console. L\u0026rsquo;infrastructure devient reproductible, relisable et testable comme du code applicatif.","title":"Infrastructure as Code"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/infrastructure-as-code/","section":"Tags","summary":"","title":"Infrastructure-as-Code"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/integration/","section":"Tags","summary":"","title":"Integration"},{"content":"Le problème #Deux transactions peuvent s\u0026rsquo;exécuter en même temps sur les mêmes données : un utilisateur qui consulte le solde de son compte pendant qu\u0026rsquo;un virement est en cours de traitement, deux clients qui tentent de réserver la dernière place d\u0026rsquo;un événement au même instant. Sans règle claire, une transaction pourrait lire des données que l\u0026rsquo;autre est en train de modifier — et potentiellement s\u0026rsquo;annuler ensuite — menant à des décisions prises sur des informations qui n\u0026rsquo;ont jamais réellement existé.\nLe SQL standard définit quatre niveaux d\u0026rsquo;isolation pour arbitrer ce compromis entre cohérence stricte et performance : plus l\u0026rsquo;isolation est forte, plus les garanties sont solides, mais plus le coût en verrous et en contention augmente.\nL\u0026rsquo;idée générale #Trois anomalies classiques peuvent survenir quand des transactions concurrentes ne sont pas suffisamment isolées :\nDirty read (lecture sale) : lire une donnée modifiée par une autre transaction non encore validée — si cette transaction fait un ROLLBACK, la donnée lue n\u0026rsquo;a jamais réellement existé. Non-repeatable read (lecture non répétable) : relire la même ligne deux fois dans la même transaction et obtenir des valeurs différentes, parce qu\u0026rsquo;une autre transaction l\u0026rsquo;a modifiée et validée entre-temps. Phantom read (lecture fantôme) : ré-exécuter la même requête avec critère et obtenir un ensemble de lignes différent, parce que d\u0026rsquo;autres lignes correspondant au critère ont été insérées ou supprimées entre-temps. Les quatre niveaux d\u0026rsquo;isolation standard SQL, du plus permissif au plus strict :\nNiveau Dirty read Non-repeatable read Phantom read Read Uncommitted Possible Possible Possible Read Committed Empêché Possible Possible Repeatable Read Empêché Empêché Possible (souvent atténué en pratique) Serializable Empêché Empêché Empêché Analogie du quotidien #L\u0026rsquo;isolation transactionnelle, c\u0026rsquo;est comme consulter un document partagé pendant qu\u0026rsquo;un collègue le modifie. Au niveau le plus permissif (Read Uncommitted), vous voyez ses modifications en direct, même celles qu\u0026rsquo;il n\u0026rsquo;a pas encore enregistrées — et s\u0026rsquo;il annule son brouillon, vous avez lu des informations qui n\u0026rsquo;ont jamais existé officiellement. Au niveau Read Committed, vous ne voyez que ses versions déjà enregistrées, mais si vous rouvrez le document cinq minutes plus tard, il a peut-être changé entre-temps. Au niveau Serializable, c\u0026rsquo;est comme si vous aviez emprunté le document en exclusivité : personne d\u0026rsquo;autre ne peut le modifier tant que vous travaillez dessus, garantissant que ce que vous voyez au début et à la fin de votre lecture reste parfaitement cohérent.\nDiagramme # sequenceDiagram participant T1 as Transaction 1 participant DB as Base de données participant T2 as Transaction 2 T1-\u003e\u003eDB: BEGIN T1-\u003e\u003eDB: SELECT solde FROM comptes WHERE id = 'A' (lit 100€) T2-\u003e\u003eDB: BEGIN T2-\u003e\u003eDB: UPDATE comptes SET solde = 50 WHERE id = 'A' T2-\u003e\u003eDB: COMMIT T1-\u003e\u003eDB: SELECT solde FROM comptes WHERE id = 'A' (relit) Note over T1,DB: Read Committed : relit 50€ (non-repeatable read)Repeatable Read / Serializable : relit toujours 100€ T1-\u003e\u003eDB: COMMIT Exemple de code #-- Définir le niveau d\u0026#39;isolation pour une transaction en PostgreSQL BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; SELECT solde FROM comptes WHERE id = \u0026#39;A\u0026#39;; -- lit 100€ -- (une autre transaction modifie et valide la ligne ici) SELECT solde FROM comptes WHERE id = \u0026#39;A\u0026#39;; -- relit toujours 100€ grâce à Repeatable Read COMMIT; -- Niveau Serializable : la transaction échoue explicitement en cas de conflit sérialisable BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- ... opérations ... COMMIT; -- peut lever une erreur \u0026#34;could not serialize access\u0026#34;, à réessayer côté application // Définir le niveau d\u0026#39;isolation via JDBC Connection conn = dataSource.getConnection(); conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); conn.setAutoCommit(false); try { // ... opérations métier ... conn.commit(); } catch (SQLException e) { conn.rollback(); // En Serializable, prévoir une logique de retry sur les erreurs de sérialisation throw e; } Quand utiliser chaque niveau d\u0026rsquo;isolation ? # Read Uncommitted : quasiment jamais en production sur des données critiques ; utile tout au plus pour des rapports approximatifs tolérant l\u0026rsquo;incohérence, sur des moteurs qui l\u0026rsquo;implémentent réellement (MySQL le fait, PostgreSQL le traite comme Read Committed). Read Committed : le bon défaut pour la majorité des applications — évite les lectures sales à un coût de performance raisonnable. Repeatable Read : quand une transaction doit lire plusieurs fois les mêmes données et exige qu\u0026rsquo;elles ne changent pas en cours de route (calculs financiers, rapports cohérents). Serializable : pour les opérations les plus sensibles (réservation de stock limité, transferts financiers à fort enjeu), où même un phantom read est inacceptable — au prix d\u0026rsquo;une gestion de conflits à réessayer côté application. Points importants # Le niveau d\u0026rsquo;isolation par défaut varie selon le moteur : Read Committed pour PostgreSQL, Oracle et SQL Server ; Repeatable Read pour MySQL/InnoDB. Une isolation plus stricte n\u0026rsquo;élimine pas la nécessité de gérer les erreurs : à Serializable, le moteur peut faire échouer une transaction en conflit plutôt que de la bloquer indéfiniment — l\u0026rsquo;application doit prévoir une logique de nouvelle tentative. L\u0026rsquo;isolation ne remplace pas les contraintes métier : un SELECT ... FOR UPDATE verrouille explicitement des lignes pour éviter des écritures concurrentes, complémentaire au niveau d\u0026rsquo;isolation choisi. Comprendre ces niveaux est une question classique d\u0026rsquo;entretien technique senior, car elle révèle si le candidat sait raisonner sur la concurrence, pas seulement sur des requêtes isolées. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/bases-de-donnees/isolation-transactionnelle/","section":"Bases de données","summary":"Plusieurs transactions peuvent s\u0026rsquo;exécuter en même temps sur les mêmes données. Le niveau d\u0026rsquo;isolation choisi détermine ce qu\u0026rsquo;une transaction a le droit de voir des autres transactions en cours — un arbitrage entre cohérence stricte et performance.","title":"Isolation transactionnelle"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/java/","section":"Tags","summary":"","title":"Java"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/javascript/","section":"Tags","summary":"","title":"Javascript"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/kubernetes/","section":"Tags","summary":"","title":"Kubernetes"},{"content":"Le problème #Docker résout le problème de la portabilité d\u0026rsquo;une application : empaqueter un conteneur et le faire tourner de façon identique n\u0026rsquo;importe où. Mais en production réelle, une seule instance d\u0026rsquo;un conteneur sur une seule machine ne suffit presque jamais. Il faut plusieurs instances pour absorber la charge, réparties sur plusieurs machines pour survivre à la panne d\u0026rsquo;un serveur, avec un mécanisme pour rediriger le trafic vers les instances en bonne santé.\nGérer ça manuellement devient vite intenable : lancer des conteneurs à la main sur chaque machine, surveiller lesquels sont tombés, les relancer, ajuster leur nombre selon le trafic, déployer une nouvelle version sans interrompre le service — tout cela demande une automatisation que Docker seul ne fournit pas. C\u0026rsquo;est exactement le rôle de Kubernetes.\nL\u0026rsquo;idée générale #Kubernetes est un orchestrateur de conteneurs : un système qui gère l\u0026rsquo;exécution de conteneurs sur un ensemble de machines (un cluster), en automatisant leur déploiement, leur mise à l\u0026rsquo;échelle et leur résilience.\nLe concept central est la boucle de réconciliation : vous décrivez l\u0026rsquo;état désiré du système (par exemple, \u0026ldquo;je veux 3 instances de mon API, avec cette image Docker\u0026rdquo;), et Kubernetes travaille en continu pour que l\u0026rsquo;état réel du cluster corresponde à cet état désiré — en recréant un Pod qui crashe, en répartissant la charge, en déplaçant des workloads d\u0026rsquo;une machine en panne vers une machine saine.\nQuelques objets clés composent ce modèle :\nPod : l\u0026rsquo;unité d\u0026rsquo;exécution la plus petite, un ou plusieurs conteneurs partageant réseau et stockage. Deployment : décrit combien de réplicas d\u0026rsquo;un Pod doivent tourner, et gère les mises à jour progressives (rolling updates). Service : une adresse réseau stable pour accéder à un ensemble de Pods, qui eux sont éphémères et changent d\u0026rsquo;IP à chaque redémarrage. Node : une machine (physique ou virtuelle) du cluster, sur laquelle des Pods sont planifiés. Analogie du quotidien #Kubernetes, c\u0026rsquo;est comme le régulateur d\u0026rsquo;une flotte de taxis dans une grande ville. Chaque taxi (un Pod) part du dépôt et roule en autonomie, mais le régulateur central décide combien de taxis doivent être en service selon l\u0026rsquo;heure de la journée, redirige immédiatement un client vers un autre taxi si le sien tombe en panne, et envoie automatiquement un taxi de remplacement pour maintenir le nombre de véhicules actifs prévu.\nLe client (une requête utilisateur) n\u0026rsquo;appelle jamais un taxi précis par son numéro de plaque : il appelle un numéro central (le Service) qui le met en relation avec n\u0026rsquo;importe quel taxi disponible. Peu importe lequel des taxis répond, tant que le service est assuré.\nDiagramme # flowchart TD Client[Client / Utilisateur] --\u003e Svc[Service : adresse stable] Svc --\u003e P1[Pod 1 : conteneur API] Svc --\u003e P2[Pod 2 : conteneur API] Svc --\u003e P3[Pod 3 : conteneur API] Dep[Deployment : état désiré = 3 réplicas] -.surveille et recrée.-\u003e P1 Dep -.surveille et recrée.-\u003e P2 Dep -.surveille et recrée.-\u003e P3 subgraph Node1[\"Node A\"] P1 P2 end subgraph Node2[\"Node B\"] P3 end P2 x--x Panne[Pod tombe en panne] Panne -.détecté par la boucle de réconciliation.-\u003e Dep Exemple de code ## deployment.yaml — décrit l\u0026#39;état désiré : 3 réplicas de l\u0026#39;API apiVersion: apps/v1 kind: Deployment metadata: name: mon-api labels: app: mon-api spec: replicas: 3 selector: matchLabels: app: mon-api strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 template: metadata: labels: app: mon-api spec: containers: - name: api image: monregistre/mon-api:1.4.0 ports: - containerPort: 3000 resources: requests: cpu: \u0026#34;250m\u0026#34; memory: \u0026#34;256Mi\u0026#34; limits: cpu: \u0026#34;500m\u0026#34; memory: \u0026#34;512Mi\u0026#34; readinessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 5 livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 15 --- # service.yaml — une adresse réseau stable vers les Pods du Deployment apiVersion: v1 kind: Service metadata: name: mon-api-service spec: selector: app: mon-api ports: - port: 80 targetPort: 3000 type: ClusterIP # Appliquer la configuration déclarative au cluster kubectl apply -f deployment.yaml -f service.yaml # Observer la boucle de réconciliation en action kubectl get pods --watch kubectl scale deployment mon-api --replicas=5 Quand utiliser Kubernetes ? # Quand une application est composée de plusieurs services conteneurisés qui doivent être déployés, mis à l\u0026rsquo;échelle et surveillés ensemble. Quand la disponibilité est critique et qu\u0026rsquo;il faut une résilience automatique aux pannes de conteneurs ou de machines. Quand les déploiements doivent se faire sans interruption de service (rolling updates, rollback automatique en cas d\u0026rsquo;échec). À éviter pour une application simple à un seul service avec un trafic stable et prévisible : la complexité opérationnelle de Kubernetes (cluster à maintenir, courbe d\u0026rsquo;apprentissage) dépasse largement le bénéfice tant qu\u0026rsquo;un simple PaaS ou quelques conteneurs Docker suffisent. Points importants # Kubernetes ne remplace pas Docker : il orchestre des conteneurs, que ceux-ci soient construits avec Docker ou un autre moteur compatible (containerd, par exemple). Le nombre de Pods n\u0026rsquo;est pas fixe : le Horizontal Pod Autoscaler peut ajuster automatiquement le nombre de réplicas selon des métriques comme l\u0026rsquo;utilisation CPU. Les Pods sont éphémères et interchangeables par nature : aucune donnée persistante ne doit dépendre d\u0026rsquo;un Pod précis, d\u0026rsquo;où l\u0026rsquo;usage de volumes externes (PersistentVolume) pour tout état à conserver. Les fournisseurs cloud proposent des offres Kubernetes managées (EKS sur AWS, GKE sur Google Cloud, AKS sur Azure) qui déchargent l\u0026rsquo;équipe de la gestion du plan de contrôle, réduisant une bonne partie de la complexité opérationnelle initiale. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/cloud-devops/kubernetes-explique-simplement/","section":"Cloud \u0026 DevOps","summary":"Kubernetes est un orchestrateur de conteneurs : il décide où et comment exécuter des conteneurs Docker sur un ensemble de machines, les redémarre automatiquement en cas de panne, et ajuste leur nombre selon la charge.","title":"Kubernetes expliqué simplement"},{"content":"Le problème #Une application doit supporter plusieurs thèmes graphiques — clair et sombre — chacun avec ses propres composants (un bouton, une case à cocher) qui doivent rester visuellement cohérents entre eux. Si on utilise une Factory simple par composant (une pour les boutons, une pour les cases à cocher), rien n\u0026rsquo;empêche d\u0026rsquo;assembler par erreur un BoutonClair avec une CaseSombre. Le thème n\u0026rsquo;est plus cohérent, et ce genre de bug — un mélange discret entre deux familles — est particulièrement difficile à repérer en relecture de code.\nL\u0026rsquo;idée générale #L\u0026rsquo;Abstract Factory définit une interface (UIFactory) qui déclare une méthode de création par type de produit de la famille : creerBouton(), creerCaseACocher(). Chaque factory concrète (ThemeClairFactory, ThemeSombreFactory) implémente toutes ces méthodes, en retournant systématiquement des composants issus de la même famille.\nLe code client manipule uniquement l\u0026rsquo;UIFactory abstraite. Il n\u0026rsquo;a jamais à choisir individuellement chaque composant : la cohérence de la famille entière est garantie par construction, dès qu\u0026rsquo;on choisit la bonne factory concrète.\nAnalogie du quotidien #C\u0026rsquo;est comme un fabricant de meubles qui propose des collections complètes : la \u0026ldquo;Collection Scandinave\u0026rdquo; (chaise, table et lampe scandinaves) et la \u0026ldquo;Collection Industrielle\u0026rdquo; (chaise, table et lampe industrielles). Vous ne choisissez pas chaque meuble séparément dans des catalogues différents en espérant qu\u0026rsquo;ils s\u0026rsquo;accordent visuellement une fois chez vous — vous choisissez une collection entière, et tous les meubles fournis sont garantis assortis entre eux.\nDiagramme # classDiagram class UIFactory { \u003c\u003e +creerBouton() Bouton +creerCaseACocher() CaseACocher } class ThemeClairFactory { +creerBouton() Bouton +creerCaseACocher() CaseACocher } class ThemeSombreFactory { +creerBouton() Bouton +creerCaseACocher() CaseACocher } class Bouton { \u003c\u003e +afficher() string } class CaseACocher { \u003c\u003e +afficher() string } class BoutonClair class BoutonSombre class CaseClaire class CaseSombre UIFactory \u003c|.. ThemeClairFactory UIFactory \u003c|.. ThemeSombreFactory Bouton \u003c|.. BoutonClair Bouton \u003c|.. BoutonSombre CaseACocher \u003c|.. CaseClaire CaseACocher \u003c|.. CaseSombre ThemeClairFactory ..\u003e BoutonClair ThemeClairFactory ..\u003e CaseClaire ThemeSombreFactory ..\u003e BoutonSombre ThemeSombreFactory ..\u003e CaseSombre Exemple de code #PHP #interface Bouton { public function afficher(): string; } interface CaseACocher { public function afficher(): string; } final class BoutonClair implements Bouton { public function afficher(): string { return \u0026#34;Bouton clair\u0026#34;; } } final class BoutonSombre implements Bouton { public function afficher(): string { return \u0026#34;Bouton sombre\u0026#34;; } } final class CaseClaire implements CaseACocher { public function afficher(): string { return \u0026#34;Case claire\u0026#34;; } } final class CaseSombre implements CaseACocher { public function afficher(): string { return \u0026#34;Case sombre\u0026#34;; } } interface UIFactory { public function creerBouton(): Bouton; public function creerCaseACocher(): CaseACocher; } final class ThemeClairFactory implements UIFactory { public function creerBouton(): Bouton { return new BoutonClair(); } public function creerCaseACocher(): CaseACocher { return new CaseClaire(); } } final class ThemeSombreFactory implements UIFactory { public function creerBouton(): Bouton { return new BoutonSombre(); } public function creerCaseACocher(): CaseACocher { return new CaseSombre(); } } // Utilisation function construireFormulaire(UIFactory $factory): void { echo $factory-\u0026gt;creerBouton()-\u0026gt;afficher() . PHP_EOL; echo $factory-\u0026gt;creerCaseACocher()-\u0026gt;afficher() . PHP_EOL; } construireFormulaire(new ThemeSombreFactory()); // Bouton sombre + Case sombre : jamais de mélange possible Java #interface Bouton { String afficher(); } interface CaseACocher { String afficher(); } class BoutonClair implements Bouton { public String afficher() { return \u0026#34;Bouton clair\u0026#34;; } } class BoutonSombre implements Bouton { public String afficher() { return \u0026#34;Bouton sombre\u0026#34;; } } class CaseClaire implements CaseACocher { public String afficher() { return \u0026#34;Case claire\u0026#34;; } } class CaseSombre implements CaseACocher { public String afficher() { return \u0026#34;Case sombre\u0026#34;; } } interface UIFactory { Bouton creerBouton(); CaseACocher creerCaseACocher(); } class ThemeClairFactory implements UIFactory { public Bouton creerBouton() { return new BoutonClair(); } public CaseACocher creerCaseACocher() { return new CaseClaire(); } } class ThemeSombreFactory implements UIFactory { public Bouton creerBouton() { return new BoutonSombre(); } public CaseACocher creerCaseACocher() { return new CaseSombre(); } } // Utilisation static void construireFormulaire(UIFactory factory) { System.out.println(factory.creerBouton().afficher()); System.out.println(factory.creerCaseACocher().afficher()); } construireFormulaire(new ThemeSombreFactory()); // Bouton sombre + Case sombre : jamais de mélange possible JavaScript #class BoutonClair { afficher() { return \u0026#34;Bouton clair\u0026#34;; } } class BoutonSombre { afficher() { return \u0026#34;Bouton sombre\u0026#34;; } } class CaseClaire { afficher() { return \u0026#34;Case claire\u0026#34;; } } class CaseSombre { afficher() { return \u0026#34;Case sombre\u0026#34;; } } class ThemeClairFactory { creerBouton() { return new BoutonClair(); } creerCaseACocher() { return new CaseClaire(); } } class ThemeSombreFactory { creerBouton() { return new BoutonSombre(); } creerCaseACocher() { return new CaseSombre(); } } // Utilisation function construireFormulaire(factory) { console.log(factory.creerBouton().afficher()); console.log(factory.creerCaseACocher().afficher()); } construireFormulaire(new ThemeSombreFactory()); // Bouton sombre + Case sombre : jamais de mélange possible Quand utiliser ce pattern ? # Quand l\u0026rsquo;application doit supporter plusieurs familles de produits liés (thèmes, environnements, fournisseurs) qui doivent rester cohérents entre eux. Quand on veut garantir, par construction, qu\u0026rsquo;on ne mélange jamais des composants issus de familles différentes. Quand on veut pouvoir changer une famille entière de produits en un seul point du code : la ligne où on choisit quelle factory injecter. Évitez ce pattern si une seule famille de produits existe, ou si les objets créés n\u0026rsquo;ont pas de contrainte de cohérence entre eux — une Factory simple suffit alors largement.\nPoints importants # La question d\u0026rsquo;entretien la plus fréquente sur ce sujet : Factory crée un objet, Abstract Factory crée une famille d\u0026rsquo;objets qui doivent rester cohérents entre eux. Abstract Factory est d\u0026rsquo;ailleurs souvent implémentée en combinant plusieurs Factory Method, une par produit de la famille. Ajouter une nouvelle famille (par exemple un \u0026ldquo;Thème contraste élevé\u0026rdquo;) revient à ajouter une nouvelle factory concrète, sans toucher au code existant — le principe ouvert/fermé est respecté. En revanche, ajouter un nouveau type de produit à la famille (par exemple un composant \u0026ldquo;Menu déroulant\u0026rdquo;) oblige à modifier l\u0026rsquo;interface UIFactory, puis toutes les factories concrètes existantes : c\u0026rsquo;est le principal point faible du pattern. Abstract Factory ajoute une couche d\u0026rsquo;indirection non négligeable : à réserver aux cas où plusieurs familles de produits liés coexistent réellement dans l\u0026rsquo;application. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/design-patterns/abstract-factory/","section":"Design Patterns","summary":"L\u0026rsquo;Abstract Factory va plus loin que la Factory simple : elle garantit qu\u0026rsquo;un ensemble d\u0026rsquo;objets liés entre eux (une famille) reste toujours cohérent, en regroupant plusieurs méthodes de création derrière une seule interface.","title":"Le Pattern Abstract Factory expliqué simplement"},{"content":"Le problème #Votre domaine a défini un contrat clair : pour encaisser un paiement, il attend une méthode payer(Commande $commande): bool. Simple, lisible, indépendant de tout prestataire.\nMais le prestataire de paiement choisi — un SDK tiers — expose une API totalement différente : charge(int $montantEnCentimes, string $devise): StripeResponse. Les noms ne correspondent pas, les unités non plus (centimes contre euros), et le type de retour n\u0026rsquo;est pas celui attendu.\nDeux mauvaises solutions s\u0026rsquo;offrent à vous : réécrire tout le domaine pour coller à l\u0026rsquo;API du SDK (et le recommencer au prochain changement de prestataire), ou éparpiller la logique de conversion partout où le SDK est appelé. Aucune des deux ne protège le domaine des détails d\u0026rsquo;implémentation d\u0026rsquo;une librairie externe.\nL\u0026rsquo;idée générale #Le Adapter Pattern introduit une classe intermédiaire qui traduit un appel d\u0026rsquo;une interface vers une autre. Le domaine continue de parler son propre langage (payer()), et l\u0026rsquo;adapter se charge de convertir cet appel dans les termes attendus par la librairie tierce (charge()).\nTrois éléments composent le pattern :\nTarget : l\u0026rsquo;interface attendue par le code appelant — celle que le domaine connaît. Adaptee : la classe existante, incompatible, qu\u0026rsquo;on ne peut ou ne veut pas modifier (souvent une librairie tierce). Adapter : la classe qui implémente Target et délègue le travail à Adaptee, en traduisant les appels au passage. Le code appelant ne sait même pas qu\u0026rsquo;un adapter existe : il continue à programmer contre l\u0026rsquo;interface qu\u0026rsquo;il connaît.\nAnalogie du quotidien #Le Adapter Pattern, c\u0026rsquo;est l\u0026rsquo;adaptateur de prise électrique que vous glissez dans votre valise avant un voyage. Votre chargeur attend une prise européenne à deux broches rondes ; la prise murale de l\u0026rsquo;hôtel, à l\u0026rsquo;étranger, est complètement différente.\nVous ne modifiez ni votre chargeur, ni la prise murale : vous intercalez un petit boîtier qui traduit une forme de prise vers une autre. Le courant qui circule est le même des deux côtés — seule l\u0026rsquo;interface physique change. C\u0026rsquo;est exactement le rôle de l\u0026rsquo;adapter en code : il ne change pas ce qui se passe réellement, seulement la façon de le déclencher.\nDiagramme # classDiagram class PaymentGatewayInterface { \u003c\u003e +payer(commande) bool } class StripeAdapter { -stripeClient: StripeClient +payer(commande) bool } class StripeClient { +charge(montantCentimes, devise) StripeResponse } class ServiceCommande { -gateway: PaymentGatewayInterface +valider(commande) } PaymentGatewayInterface \u003c|.. StripeAdapter StripeAdapter --\u003e StripeClient ServiceCommande --\u003e PaymentGatewayInterface Exemple de code #PHP #// --- Target : l\u0026#39;interface attendue par le domaine --- interface PaymentGatewayInterface { public function payer(Commande $commande): bool; } // --- Adaptee : le SDK tiers, incompatible, non modifiable --- final class StripeClient { public function charge(int $montantEnCentimes, string $devise): StripeResponse { // Appel réel à l\u0026#39;API Stripe... return new StripeResponse(status: \u0026#39;succeeded\u0026#39;); } } // --- Adapter : traduit Target vers Adaptee --- final class StripeAdapter implements PaymentGatewayInterface { public function __construct(private StripeClient $stripeClient) {} public function payer(Commande $commande): bool { $montantEnCentimes = (int) round($commande-\u0026gt;montant() * 100); $response = $this-\u0026gt;stripeClient-\u0026gt;charge($montantEnCentimes, \u0026#39;eur\u0026#39;); return $response-\u0026gt;status === \u0026#39;succeeded\u0026#39;; } } // --- Utilisation : le domaine ne connaît que PaymentGatewayInterface --- final class ServiceCommande { public function __construct(private PaymentGatewayInterface $gateway) {} public function valider(Commande $commande): void { if (!$this-\u0026gt;gateway-\u0026gt;payer($commande)) { throw new DomainException(\u0026#39;Paiement refusé\u0026#39;); } } } $service = new ServiceCommande(new StripeAdapter(new StripeClient())); Java #// --- Target --- interface PaymentGateway { boolean payer(Commande commande); } // --- Adaptee : SDK tiers, incompatible --- class StripeClient { StripeResponse charge(int montantEnCentimes, String devise) { // Appel réel à l\u0026#39;API Stripe... return new StripeResponse(\u0026#34;succeeded\u0026#34;); } } // --- Adapter --- class StripeAdapter implements PaymentGateway { private final StripeClient stripeClient; public StripeAdapter(StripeClient stripeClient) { this.stripeClient = stripeClient; } public boolean payer(Commande commande) { int montantEnCentimes = Math.round(commande.getMontant() * 100); StripeResponse response = stripeClient.charge(montantEnCentimes, \u0026#34;eur\u0026#34;); return response.getStatus().equals(\u0026#34;succeeded\u0026#34;); } } // --- Utilisation --- class ServiceCommande { private final PaymentGateway gateway; public ServiceCommande(PaymentGateway gateway) { this.gateway = gateway; } public void valider(Commande commande) { if (!gateway.payer(commande)) { throw new IllegalStateException(\u0026#34;Paiement refusé\u0026#34;); } } } ServiceCommande service = new ServiceCommande(new StripeAdapter(new StripeClient())); JavaScript #// --- Adaptee : SDK tiers, incompatible --- class StripeClient { charge(montantEnCentimes, devise) { // Appel réel à l\u0026#39;API Stripe... return { status: \u0026#34;succeeded\u0026#34; }; } } // --- Adapter : expose payer(), traduit vers charge() --- class StripeAdapter { constructor(stripeClient) { this.stripeClient = stripeClient; } payer(commande) { const montantEnCentimes = Math.round(commande.montant * 100); const response = this.stripeClient.charge(montantEnCentimes, \u0026#34;eur\u0026#34;); return response.status === \u0026#34;succeeded\u0026#34;; } } // --- Utilisation : le domaine ne connaît que payer() --- class ServiceCommande { constructor(gateway) { this.gateway = gateway; } valider(commande) { if (!this.gateway.payer(commande)) { throw new Error(\u0026#34;Paiement refusé\u0026#34;); } } } const service = new ServiceCommande(new StripeAdapter(new StripeClient())); Quand utiliser ce pattern ? # Quand vous devez intégrer une librairie tierce dont l\u0026rsquo;interface ne correspond pas à celle attendue par votre domaine. Quand vous migrez progressivement d\u0026rsquo;une ancienne API vers une nouvelle, et que vous voulez que le code appelant continue de fonctionner sans modification pendant la transition. Quand deux systèmes doivent cohabiter (un legacy et un nouveau) sans que l\u0026rsquo;un ne dépende directement des détails de l\u0026rsquo;autre. Évitez Adapter si vous contrôlez les deux côtés de l\u0026rsquo;interface : dans ce cas, il est souvent plus simple d\u0026rsquo;aligner directement les deux contrats plutôt que d\u0026rsquo;ajouter une couche de traduction. Points importants # L\u0026rsquo;adapter doit rester mince : sa seule responsabilité est de traduire un appel, jamais d\u0026rsquo;ajouter de la logique métier. Il protège le domaine d\u0026rsquo;un couplage direct à une librairie tierce : changer de prestataire de paiement revient à écrire un nouvel adapter, sans toucher au domaine. L\u0026rsquo;adapter d\u0026rsquo;objet (composition) est presque toujours préférable à l\u0026rsquo;adapter de classe (héritage) : plus flexible, il fonctionne même avec des classes finales ou tierces. Adapter est souvent le premier pattern à connaître un projet vieillissant : c\u0026rsquo;est lui qui permet de faire cohabiter du code ancien et du code neuf sans tout réécrire d\u0026rsquo;un coup. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/design-patterns/adapter/","section":"Design Patterns","summary":"Le Adapter Pattern permet à deux interfaces incompatibles de fonctionner ensemble, sans modifier ni l\u0026rsquo;une ni l\u0026rsquo;autre. Il s\u0026rsquo;intercale entre le code appelant et une librairie tierce pour traduire les appels d\u0026rsquo;un contrat vers un autre.","title":"Le Pattern Adapter expliqué simplement"},{"content":"Le problème #Une classe RequeteHttp doit gérer une URL obligatoire, mais aussi une multitude d\u0026rsquo;éléments optionnels : méthode HTTP (GET par défaut), en-têtes, corps de la requête, timeout, authentification. Un constructeur avec dix paramètres optionnels devient vite illisible — c\u0026rsquo;est ce qu\u0026rsquo;on appelle un « constructeur télescope ». Impossible de savoir à l\u0026rsquo;appel quel argument correspond à quel paramètre sans consulter la signature complète, et chaque appelant doit passer null pour tout ce qu\u0026rsquo;il n\u0026rsquo;utilise pas.\nL\u0026rsquo;idée générale #Le Builder sépare la construction complexe d\u0026rsquo;un objet en une série d\u0026rsquo;étapes chaînées, chacune configurant une partie de l\u0026rsquo;objet. Une méthode finale (build() ou construire()) valide l\u0026rsquo;ensemble et retourne l\u0026rsquo;objet fini, généralement immuable une fois construit.\nDeux éléments composent le pattern :\nBuilder : la classe qui expose des méthodes pour configurer chaque partie de l\u0026rsquo;objet, plus une méthode finale de construction. Product : l\u0026rsquo;objet final construit, inconnu de l\u0026rsquo;appelant tant que la construction n\u0026rsquo;est pas terminée. Le client enchaîne uniquement les étapes dont il a besoin ; tout le reste conserve une valeur par défaut sensée.\nAnalogie du quotidien #C\u0026rsquo;est comme commander un sandwich dans une chaîne de restauration rapide où l\u0026rsquo;on compose son plat au comptoir : on choisit d\u0026rsquo;abord le pain, puis la viande, puis les légumes un par un, puis la sauce — chaque étape est optionnelle ou dispose d\u0026rsquo;un choix par défaut, et rien n\u0026rsquo;est réellement prêt tant qu\u0026rsquo;on n\u0026rsquo;a pas validé la commande à la fin. Le vendeur (le Builder) assemble le sandwich (le Product) étape par étape selon les choix exprimés, dans l\u0026rsquo;ordre où ils sont donnés, et ne le remet qu\u0026rsquo;une fois la commande finalisée.\nDiagramme # classDiagram class RequeteHttpBuilder { -url: string -methode: string -headers: map -body: string -timeout: int +methode(m) RequeteHttpBuilder +header(cle, valeur) RequeteHttpBuilder +body(contenu) RequeteHttpBuilder +timeout(ms) RequeteHttpBuilder +construire() RequeteHttp } class RequeteHttp { +url: string +methode: string +headers: map +body: string +timeout: int } RequeteHttpBuilder ..\u003e RequeteHttp : construit Exemple de code #PHP #final class RequeteHttp { public function __construct( public readonly string $url, public readonly string $methode, public readonly array $headers, public readonly ?string $body, public readonly int $timeout, ) {} } final class RequeteHttpBuilder { private string $methode = \u0026#39;GET\u0026#39;; private array $headers = []; private ?string $body = null; private int $timeout = 5000; public function __construct(private readonly string $url) {} public function methode(string $methode): self { $this-\u0026gt;methode = $methode; return $this; } public function header(string $cle, string $valeur): self { $this-\u0026gt;headers[$cle] = $valeur; return $this; } public function body(string $contenu): self { $this-\u0026gt;body = $contenu; return $this; } public function timeout(int $ms): self { $this-\u0026gt;timeout = $ms; return $this; } public function construire(): RequeteHttp { return new RequeteHttp($this-\u0026gt;url, $this-\u0026gt;methode, $this-\u0026gt;headers, $this-\u0026gt;body, $this-\u0026gt;timeout); } } // Utilisation $requete = (new RequeteHttpBuilder(\u0026#39;https://api.exemple.com/commandes\u0026#39;)) -\u0026gt;methode(\u0026#39;POST\u0026#39;) -\u0026gt;header(\u0026#39;Content-Type\u0026#39;, \u0026#39;application/json\u0026#39;) -\u0026gt;body(\u0026#39;{\u0026#34;produit\u0026#34;:\u0026#34;livre\u0026#34;}\u0026#39;) -\u0026gt;timeout(3000) -\u0026gt;construire(); Java #final class RequeteHttp { private final String url; private final String methode; private final Map\u0026lt;String, String\u0026gt; headers; private final String body; private final int timeout; private RequeteHttp(RequeteHttpBuilder builder) { this.url = builder.url; this.methode = builder.methode; this.headers = builder.headers; this.body = builder.body; this.timeout = builder.timeout; } static class RequeteHttpBuilder { private final String url; private String methode = \u0026#34;GET\u0026#34;; private Map\u0026lt;String, String\u0026gt; headers = new HashMap\u0026lt;\u0026gt;(); private String body; private int timeout = 5000; public RequeteHttpBuilder(String url) { this.url = url; } public RequeteHttpBuilder methode(String methode) { this.methode = methode; return this; } public RequeteHttpBuilder header(String cle, String valeur) { this.headers.put(cle, valeur); return this; } public RequeteHttpBuilder body(String body) { this.body = body; return this; } public RequeteHttpBuilder timeout(int timeout) { this.timeout = timeout; return this; } public RequeteHttp build() { return new RequeteHttp(this); } } } // Utilisation RequeteHttp requete = new RequeteHttp.RequeteHttpBuilder(\u0026#34;https://api.exemple.com/commandes\u0026#34;) .methode(\u0026#34;POST\u0026#34;) .header(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) .body(\u0026#34;{\\\u0026#34;produit\\\u0026#34;:\\\u0026#34;livre\\\u0026#34;}\u0026#34;) .timeout(3000) .build(); JavaScript #class RequeteHttp { constructor({ url, methode, headers, body, timeout }) { this.url = url; this.methode = methode; this.headers = headers; this.body = body; this.timeout = timeout; } } class RequeteHttpBuilder { #methode = \u0026#34;GET\u0026#34;; #headers = {}; #body = null; #timeout = 5000; constructor(url) { this.url = url; } methode(methode) { this.#methode = methode; return this; } header(cle, valeur) { this.#headers[cle] = valeur; return this; } body(contenu) { this.#body = contenu; return this; } timeout(ms) { this.#timeout = ms; return this; } construire() { return new RequeteHttp({ url: this.url, methode: this.#methode, headers: this.#headers, body: this.#body, timeout: this.#timeout, }); } } // Utilisation const requete = new RequeteHttpBuilder(\u0026#34;https://api.exemple.com/commandes\u0026#34;) .methode(\u0026#34;POST\u0026#34;) .header(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) .body(\u0026#39;{\u0026#34;produit\u0026#34;:\u0026#34;livre\u0026#34;}\u0026#39;) .timeout(3000) .construire(); Quand utiliser ce pattern ? # Quand un objet a beaucoup de paramètres optionnels et qu\u0026rsquo;un constructeur classique deviendrait illisible (« constructeur télescope »). Quand la construction doit se faire en plusieurs étapes, parfois conditionnelles, avant d\u0026rsquo;obtenir un objet valide. Quand on veut que l\u0026rsquo;objet final soit immuable, tout en gardant une API de construction flexible et lisible. Évitez Builder pour un objet simple à deux ou trois champs obligatoires : un constructeur classique, ou un named constructor, reste plus direct.\nPoints importants # Contrairement à Factory, Builder ne choisit pas quel type d\u0026rsquo;objet créer : il se concentre sur comment assembler un objet complexe, souvent unique, étape par étape. L\u0026rsquo;objet final est généralement immuable une fois construire()/build() appelé — c\u0026rsquo;est ce qui distingue un vrai Builder d\u0026rsquo;un simple enchaînement de setters. En PHP 8, les arguments nommés (new RequeteHttp(url: ..., timeout: ...)) couvrent une partie des cas d\u0026rsquo;usage du Builder pour des objets moins complexes ; le pattern garde son intérêt dès que la construction implique de la logique conditionnelle ou des étapes dépendantes entre elles. Un Builder peut aussi servir à construire des structures composites — un document avec des sections imbriquées, par exemple — pas seulement des objets à plat. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/design-patterns/builder/","section":"Design Patterns","summary":"Le Builder Pattern sépare la construction d\u0026rsquo;un objet complexe en une série d\u0026rsquo;étapes chaînées, chacune configurant une partie de l\u0026rsquo;objet. Il évite les constructeurs à rallonge et rend le code de construction lisible, même avec de nombreux paramètres optionnels.","title":"Le Pattern Builder expliqué simplement"},{"content":"Le problème #Un service de notification envoie des messages par e-mail. Au départ, une seule classe suffit. Puis on demande de journaliser chaque envoi. Puis de chiffrer le contenu pour certains destinataires sensibles. Puis de combiner journalisation et chiffrement, mais pas systématiquement — cela dépend du contexte d\u0026rsquo;appel.\nAvec de l\u0026rsquo;héritage classique, il faudrait une sous-classe par combinaison : EmailNotifier, EmailNotifierAvecLog, EmailNotifierChiffre, EmailNotifierAvecLogEtChiffre\u0026hellip; Deux options indépendantes donnent déjà quatre classes. Trois options en donneraient huit. Le nombre de sous-classes croît de façon exponentielle, pour un gain fonctionnel qui, lui, reste linéaire.\nL\u0026rsquo;idée générale #Le Decorator Pattern propose d\u0026rsquo;envelopper l\u0026rsquo;objet dans des couches successives, chacune respectant la même interface que l\u0026rsquo;objet d\u0026rsquo;origine. Chaque couche ajoute un comportement, puis délègue l\u0026rsquo;appel à la couche qu\u0026rsquo;elle enveloppe.\nQuatre éléments composent le pattern :\nComponent : l\u0026rsquo;interface commune, partagée par l\u0026rsquo;objet de base et tous les decorators. ConcreteComponent : l\u0026rsquo;implémentation de base, sans aucun enrichissement. Decorator : une classe abstraite qui implémente Component et détient une référence vers un autre Component — celui qu\u0026rsquo;elle enveloppe. ConcreteDecorator : chaque enrichissement concret (journalisation, chiffrement\u0026hellip;), qui ajoute son comportement puis délègue à l\u0026rsquo;objet enveloppé. Comme chaque decorator respecte la même interface que l\u0026rsquo;objet enveloppé, on peut les empiler dans n\u0026rsquo;importe quel ordre, à l\u0026rsquo;exécution, sans jamais créer de nouvelle sous-classe.\nAnalogie du quotidien #Le Decorator Pattern, c\u0026rsquo;est comme s\u0026rsquo;habiller pour l\u0026rsquo;hiver. Vous partez d\u0026rsquo;un t-shirt, puis vous ajoutez un pull, puis une veste, puis un manteau si nécessaire. Chaque couche s\u0026rsquo;ajoute par-dessus la précédente, sans la remplacer, et chacune ajoute sa propre qualité — chaleur, imperméabilité, visibilité avec des bandes réfléchissantes.\nÀ aucun moment vous n\u0026rsquo;avez besoin d\u0026rsquo;un vêtement unique et sur-mesure combinant toutes les options possibles selon la météo du jour. Vous empilez des couches indépendantes, dans l\u0026rsquo;ordre qui vous convient, et vous restez toujours reconnaissable en dessous — la personne (l\u0026rsquo;interface) ne change pas, seules les couches ajoutées varient.\nDiagramme # classDiagram class NotificationInterface { \u003c\u003e +envoyer(message) } class EmailNotifier { +envoyer(message) } class NotificationDecorator { \u003c\u003e -wrappee: NotificationInterface +envoyer(message) } class NotificationAvecLog { +envoyer(message) } class NotificationChiffree { +envoyer(message) } NotificationInterface \u003c|.. EmailNotifier NotificationInterface \u003c|.. NotificationDecorator NotificationDecorator \u003c|-- NotificationAvecLog NotificationDecorator \u003c|-- NotificationChiffree NotificationDecorator o--\u003e NotificationInterface Exemple de code #PHP #interface NotificationInterface { public function envoyer(string $message): void; } final class EmailNotifier implements NotificationInterface { public function envoyer(string $message): void { // Envoi réel de l\u0026#39;e-mail... echo \u0026#34;Email envoyé : {$message}\\n\u0026#34;; } } abstract class NotificationDecorator implements NotificationInterface { public function __construct(protected NotificationInterface $wrappee) {} } final class NotificationAvecLog extends NotificationDecorator { public function envoyer(string $message): void { error_log(\u0026#34;Envoi en cours : {$message}\u0026#34;); $this-\u0026gt;wrappee-\u0026gt;envoyer($message); } } final class NotificationChiffree extends NotificationDecorator { public function envoyer(string $message): void { $this-\u0026gt;wrappee-\u0026gt;envoyer($this-\u0026gt;chiffrer($message)); } private function chiffrer(string $message): string { return base64_encode($message); // simplifié pour l\u0026#39;exemple } } // Utilisation : les couches se combinent librement, à l\u0026#39;exécution $notifier = new NotificationAvecLog( new NotificationChiffree( new EmailNotifier() ) ); $notifier-\u0026gt;envoyer(\u0026#39;Votre commande est confirmée\u0026#39;); Java #interface Notification { void envoyer(String message); } class EmailNotifier implements Notification { public void envoyer(String message) { System.out.println(\u0026#34;Email envoyé : \u0026#34; + message); } } abstract class NotificationDecorator implements Notification { protected final Notification wrappee; protected NotificationDecorator(Notification wrappee) { this.wrappee = wrappee; } } class NotificationAvecLog extends NotificationDecorator { public NotificationAvecLog(Notification wrappee) { super(wrappee); } public void envoyer(String message) { System.out.println(\u0026#34;Log : envoi en cours - \u0026#34; + message); wrappee.envoyer(message); } } class NotificationChiffree extends NotificationDecorator { public NotificationChiffree(Notification wrappee) { super(wrappee); } public void envoyer(String message) { wrappee.envoyer(chiffrer(message)); } private String chiffrer(String message) { return Base64.getEncoder().encodeToString(message.getBytes()); } } // Utilisation Notification notifier = new NotificationAvecLog( new NotificationChiffree( new EmailNotifier() ) ); notifier.envoyer(\u0026#34;Votre commande est confirmée\u0026#34;); JavaScript #class EmailNotifier { envoyer(message) { console.log(`Email envoyé : ${message}`); } } class NotificationDecorator { constructor(wrappee) { this.wrappee = wrappee; } } class NotificationAvecLog extends NotificationDecorator { envoyer(message) { console.log(`Log : envoi en cours - ${message}`); this.wrappee.envoyer(message); } } class NotificationChiffree extends NotificationDecorator { envoyer(message) { this.wrappee.envoyer(this.chiffrer(message)); } chiffrer(message) { return Buffer.from(message).toString(\u0026#34;base64\u0026#34;); } } // Utilisation : les couches se combinent librement, à l\u0026#39;exécution const notifier = new NotificationAvecLog( new NotificationChiffree( new EmailNotifier() ) ); notifier.envoyer(\u0026#34;Votre commande est confirmée\u0026#34;); Quand utiliser ce pattern ? # Quand plusieurs responsabilités optionnelles doivent pouvoir se combiner librement, sans multiplier les sous-classes pour chaque combinaison. Quand vous voulez pouvoir ajouter ou retirer un comportement à l\u0026rsquo;exécution, ce que l\u0026rsquo;héritage statique ne permet pas. Quand la logique ajoutée (journalisation, mise en cache, validation, chiffrement) est transverse et indépendante du comportement métier principal. Évitez Decorator si une seule combinaison de comportements existe réellement : une sous-classe simple suffit, et empiler des couches inutiles complique la lecture sans bénéfice. Points importants # Un decorator garde exactement la même interface que l\u0026rsquo;objet enveloppé — c\u0026rsquo;est ce qui permet de les empiler sans que le code appelant s\u0026rsquo;en aperçoive. L\u0026rsquo;ordre d\u0026rsquo;empilement peut avoir un impact réel : chiffrer avant de journaliser ne produit pas le même log que journaliser avant de chiffrer. Le pattern est à la base du concept de middleware qu\u0026rsquo;on retrouve dans la plupart des frameworks web (Express, PSR-15, ASP.NET) : chaque middleware est un decorator autour du traitement d\u0026rsquo;une requête. Utilisé sans discipline, Decorator peut produire une pile d\u0026rsquo;objets difficile à déboguer : documentez l\u0026rsquo;ordre attendu, ou fournissez une fabrique qui construit la pile correcte pour chaque cas d\u0026rsquo;usage. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/design-patterns/decorator/","section":"Design Patterns","summary":"Le Decorator Pattern permet d\u0026rsquo;ajouter dynamiquement des responsabilités à un objet, en l\u0026rsquo;enveloppant dans une ou plusieurs couches qui respectent la même interface. Une alternative à l\u0026rsquo;héritage quand les combinaisons de comportements se multiplient.","title":"Le Pattern Decorator expliqué simplement"},{"content":"Le problème #Une classe ServiceInscription a besoin d\u0026rsquo;envoyer un e-mail de bienvenue. La solution la plus directe : créer l\u0026rsquo;instance du mailer directement à l\u0026rsquo;intérieur de la classe, avec new SmtpMailer(). Ça fonctionne, mais ce choix a un coût caché.\nServiceInscription dépend maintenant d\u0026rsquo;une implémentation SMTP précise, câblée en dur. Impossible de tester la classe sans envoyer un vrai e-mail à chaque test. Impossible de changer de fournisseur d\u0026rsquo;e-mail (passer à un service tiers, ou à un mailer factice en environnement de développement) sans modifier le code de ServiceInscription lui-même. La classe qui devrait se concentrer sur la logique d\u0026rsquo;inscription se retrouve responsable, en plus, de savoir comment construire un mailer SMTP.\nL\u0026rsquo;idée générale #L\u0026rsquo;injection de dépendances renverse la responsabilité de construction : au lieu qu\u0026rsquo;une classe construise elle-même ce dont elle a besoin, elle le reçoit de l\u0026rsquo;extérieur, déjà prêt à l\u0026rsquo;emploi. La classe dépend d\u0026rsquo;une interface, jamais d\u0026rsquo;une implémentation concrète qu\u0026rsquo;elle devrait instancier.\nTrois formes principales existent :\nInjection par constructeur : les dépendances sont passées au moment de la création de l\u0026rsquo;objet. C\u0026rsquo;est la forme à privilégier pour les dépendances obligatoires — l\u0026rsquo;objet n\u0026rsquo;existe jamais dans un état incomplet. Injection par setter : les dépendances sont fournies après la construction, via une méthode dédiée. Utile pour des dépendances optionnelles ou remplaçables en cours de vie de l\u0026rsquo;objet. Injection par interface : la classe implémente une interface qui impose une méthode d\u0026rsquo;injection, moins courante en pratique. Un conteneur DI (comme le composant Symfony DependencyInjection, Spring en Java, ou InversifyJS/NestJS en JavaScript) automatise ce câblage : il sait construire l\u0026rsquo;arbre complet des dépendances et les injecter au bon endroit. Le conteneur est un outil pratique, pas le principe lui-même — on peut faire de l\u0026rsquo;injection de dépendances à la main, sans aucun conteneur, en câblant tout au point d\u0026rsquo;entrée de l\u0026rsquo;application.\nAnalogie du quotidien #L\u0026rsquo;injection de dépendances, c\u0026rsquo;est comme une lampe de chevet branchée sur le secteur. La lampe a besoin d\u0026rsquo;électricité pour fonctionner, mais elle ne la produit pas elle-même : elle se contente d\u0026rsquo;un fil et d\u0026rsquo;une prise standard.\nPeu importe que le courant vienne d\u0026rsquo;une centrale nucléaire, d\u0026rsquo;un panneau solaire ou d\u0026rsquo;un groupe électrogène — la lampe fonctionne exactement pareil, sans rien savoir de la source. Si vous changez de fournisseur d\u0026rsquo;électricité, vous ne démontez pas la lampe : vous changez ce qui est branché derrière la prise. La lampe (la classe) dépend d\u0026rsquo;une interface standard (la prise), jamais d\u0026rsquo;une centrale précise (une implémentation concrète).\nDiagramme # classDiagram class MailerInterface { \u003c\u003e +envoyer(destinataire, message) } class SmtpMailer { +envoyer(destinataire, message) } class MailerDeTest { +envoyer(destinataire, message) } class ServiceInscription { -mailer: MailerInterface +inscrire(email) } MailerInterface \u003c|.. SmtpMailer MailerInterface \u003c|.. MailerDeTest ServiceInscription --\u003e MailerInterface Exemple de code #PHP #interface MailerInterface { public function envoyer(string $destinataire, string $message): void; } final class SmtpMailer implements MailerInterface { public function envoyer(string $destinataire, string $message): void { // Envoi réel via SMTP... mail($destinataire, \u0026#39;Bienvenue\u0026#39;, $message); } } // --- Injection par constructeur : dépendance obligatoire --- final class ServiceInscription { public function __construct(private MailerInterface $mailer) {} public function inscrire(string $email): void { // ... logique d\u0026#39;inscription ... $this-\u0026gt;mailer-\u0026gt;envoyer($email, \u0026#39;Bienvenue parmi nous !\u0026#39;); } } // Câblage manuel, sans conteneur $service = new ServiceInscription(new SmtpMailer()); // Avec un conteneur DI (exemple Symfony), le câblage est automatique : // $container-\u0026gt;set(MailerInterface::class, SmtpMailer::class); // $service = $container-\u0026gt;get(ServiceInscription::class); // En test, on injecte un double sans jamais toucher SmtpMailer final class MailerDeTest implements MailerInterface { public array $messagesEnvoyes = []; public function envoyer(string $destinataire, string $message): void { $this-\u0026gt;messagesEnvoyes[] = [$destinataire, $message]; } } Java #public interface Mailer { void envoyer(String destinataire, String message); } public class SmtpMailer implements Mailer { public void envoyer(String destinataire, String message) { // Envoi réel via SMTP... System.out.println(\u0026#34;Email envoyé à \u0026#34; + destinataire); } } // --- Injection par constructeur : dépendance obligatoire --- public class ServiceInscription { private final Mailer mailer; public ServiceInscription(Mailer mailer) { this.mailer = mailer; } public void inscrire(String email) { // ... logique d\u0026#39;inscription ... mailer.envoyer(email, \u0026#34;Bienvenue parmi nous !\u0026#34;); } } // Câblage manuel ServiceInscription service = new ServiceInscription(new SmtpMailer()); // Avec Spring, le conteneur automatise l\u0026#39;injection : // @Service // public class ServiceInscription { // public ServiceInscription(Mailer mailer) { this.mailer = mailer; } // @Autowired implicite // } // --- Injection par setter : dépendance optionnelle ou remplaçable --- public class ServiceNotification { private Mailer mailer; public void setMailer(Mailer mailer) { this.mailer = mailer; } } JavaScript #class SmtpMailer { envoyer(destinataire, message) { // Envoi réel via SMTP... console.log(`Email envoyé à ${destinataire}`); } } // --- Injection par constructeur : dépendance obligatoire --- class ServiceInscription { constructor(mailer) { this.mailer = mailer; } inscrire(email) { // ... logique d\u0026#39;inscription ... this.mailer.envoyer(email, \u0026#34;Bienvenue parmi nous !\u0026#34;); } } // Câblage manuel, sans conteneur const service = new ServiceInscription(new SmtpMailer()); // Avec un conteneur DI (ex. NestJS), le câblage est automatique via décorateurs : // @Injectable() // class ServiceInscription { // constructor(private mailer) {} // } // En test, on injecte un double sans jamais toucher SmtpMailer class MailerDeTest { constructor() { this.messagesEnvoyes = []; } envoyer(destinataire, message) { this.messagesEnvoyes.push({ destinataire, message }); } } const serviceDeTest = new ServiceInscription(new MailerDeTest()); Quand utiliser ce pattern ? # Dès qu\u0026rsquo;une classe dépend d\u0026rsquo;un service dont l\u0026rsquo;implémentation peut varier entre les environnements (production, test, développement local) : mailer, logger, client HTTP, repository. Quand vous voulez tester une classe en isolation, sans dépendre d\u0026rsquo;une infrastructure réelle (base de données, SMTP, API externe). Quand le graphe de dépendances devient large : c\u0026rsquo;est là qu\u0026rsquo;un conteneur DI devient utile, pour éviter de câbler des dizaines d\u0026rsquo;objets à la main. En pratique, c\u0026rsquo;est une bonne pratique par défaut plutôt qu\u0026rsquo;un choix ponctuel : instancier une dépendance en dur avec new à l\u0026rsquo;intérieur d\u0026rsquo;une classe doit rester l\u0026rsquo;exception, justifiée, pas la norme. Points importants # Ne confondez pas le principe (injecter plutôt que construire) et l\u0026rsquo;outil (un conteneur DI) : le premier s\u0026rsquo;applique même dans un script de dix lignes, le second n\u0026rsquo;est utile qu\u0026rsquo;à partir d\u0026rsquo;un graphe de dépendances conséquent. L\u0026rsquo;injection par constructeur est la forme par défaut : elle rend les dépendances visibles dans la signature, et garantit qu\u0026rsquo;un objet mal construit ne peut pas exister. Le \u0026ldquo;point de composition\u0026rdquo; (l\u0026rsquo;endroit où toutes les dépendances sont assemblées) doit rester unique et proche du point d\u0026rsquo;entrée de l\u0026rsquo;application — jamais éparpillé dans la logique métier. Attention au Service Locator, souvent présenté comme une alternative pratique à DI : il masque les dépendances réelles d\u0026rsquo;une classe et rend le code plus difficile à tester et à comprendre à la simple lecture d\u0026rsquo;un constructeur. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/design-patterns/dependency-injection/","section":"Design Patterns","summary":"L\u0026rsquo;injection de dépendances (Dependency Injection) consiste à fournir à un objet ce dont il a besoin depuis l\u0026rsquo;extérieur, plutôt que de le laisser construire lui-même ses dépendances. Un principe simple qui rend le code testable et remplaçable.","title":"Le Pattern Dependency Injection expliqué simplement"},{"content":"Le problème #Une application doit envoyer des notifications aux utilisateurs : par email, par SMS, ou par notification push, selon leurs préférences. Le code appelant écrit quelque chose comme :\nif ($type === \u0026#39;email\u0026#39;) { $notification = new EmailNotification(); } elseif ($type === \u0026#39;sms\u0026#39;) { $notification = new SmsNotification(); } elseif ($type === \u0026#39;push\u0026#39;) { $notification = new PushNotification(); } Ce bloc de code, on le retrouve bientôt copié-collé à cinq endroits différents de l\u0026rsquo;application. Le jour où un nouveau canal de notification apparaît (WhatsApp, par exemple), il faut retrouver et modifier chacun de ces endroits — avec le risque d\u0026rsquo;en oublier un, ou de les faire diverger légèrement les uns des autres.\nL\u0026rsquo;idée générale #Le Factory Pattern déplace cette logique de décision dans un seul endroit dédié : une méthode ou une classe dont le seul travail est de créer le bon objet selon un paramètre, et de le retourner sous la forme d\u0026rsquo;une interface commune.\nLe code client ne fait plus jamais new EmailNotification() lui-même : il demande à la Factory \u0026ldquo;donne-moi une notification de type email\u0026rdquo;, sans jamais savoir précisément quelle classe concrète il reçoit en retour.\nAnalogie du quotidien #C\u0026rsquo;est comme commander au comptoir d\u0026rsquo;un fast-food. Vous demandez \u0026ldquo;un menu numéro 3\u0026rdquo; — vous n\u0026rsquo;avez pas besoin de savoir comment la cuisine assemble le burger, quelles sauces elle utilise ni dans quel ordre elle empile les ingrédients. Le comptoir (la Factory) prend votre commande, décide en interne comment la préparer, et vous remet un plat fini, prêt à consommer. Si demain le restaurant change sa recette du menu 3, vous n\u0026rsquo;avez rien à changer dans votre façon de commander.\nDiagramme # classDiagram class NotificationFactory { +creer(type: string) Notification$ } class Notification { \u003c\u003e +envoyer(message: string) } class EmailNotification { +envoyer(message: string) } class SmsNotification { +envoyer(message: string) } class PushNotification { +envoyer(message: string) } NotificationFactory ..\u003e Notification Notification \u003c|.. EmailNotification Notification \u003c|.. SmsNotification Notification \u003c|.. PushNotification Exemple de code #PHP #interface Notification { public function envoyer(string $message): void; } final class EmailNotification implements Notification { public function envoyer(string $message): void { echo \u0026#34;Email envoyé : {$message}\u0026#34; . PHP_EOL; } } final class SmsNotification implements Notification { public function envoyer(string $message): void { echo \u0026#34;SMS envoyé : {$message}\u0026#34; . PHP_EOL; } } final class PushNotification implements Notification { public function envoyer(string $message): void { echo \u0026#34;Push envoyé : {$message}\u0026#34; . PHP_EOL; } } final class NotificationFactory { public static function creer(string $type): Notification { return match ($type) { \u0026#39;email\u0026#39; =\u0026gt; new EmailNotification(), \u0026#39;sms\u0026#39; =\u0026gt; new SmsNotification(), \u0026#39;push\u0026#39; =\u0026gt; new PushNotification(), default =\u0026gt; throw new InvalidArgumentException(\u0026#34;Type inconnu : {$type}\u0026#34;), }; } } // Utilisation $notification = NotificationFactory::creer(\u0026#39;sms\u0026#39;); $notification-\u0026gt;envoyer(\u0026#34;Votre commande est expédiée\u0026#34;); Java #interface Notification { void envoyer(String message); } class EmailNotification implements Notification { public void envoyer(String message) { System.out.println(\u0026#34;Email envoyé : \u0026#34; + message); } } class SmsNotification implements Notification { public void envoyer(String message) { System.out.println(\u0026#34;SMS envoyé : \u0026#34; + message); } } class PushNotification implements Notification { public void envoyer(String message) { System.out.println(\u0026#34;Push envoyé : \u0026#34; + message); } } class NotificationFactory { public static Notification creer(String type) { return switch (type) { case \u0026#34;email\u0026#34; -\u0026gt; new EmailNotification(); case \u0026#34;sms\u0026#34; -\u0026gt; new SmsNotification(); case \u0026#34;push\u0026#34; -\u0026gt; new PushNotification(); default -\u0026gt; throw new IllegalArgumentException(\u0026#34;Type inconnu : \u0026#34; + type); }; } } // Utilisation Notification notification = NotificationFactory.creer(\u0026#34;sms\u0026#34;); notification.envoyer(\u0026#34;Votre commande est expédiée\u0026#34;); JavaScript #class EmailNotification { envoyer(message) { console.log(`Email envoyé : ${message}`); } } class SmsNotification { envoyer(message) { console.log(`SMS envoyé : ${message}`); } } class PushNotification { envoyer(message) { console.log(`Push envoyé : ${message}`); } } class NotificationFactory { static creer(type) { switch (type) { case \u0026#34;email\u0026#34;: return new EmailNotification(); case \u0026#34;sms\u0026#34;: return new SmsNotification(); case \u0026#34;push\u0026#34;: return new PushNotification(); default: throw new Error(`Type inconnu : ${type}`); } } } // Utilisation const notification = NotificationFactory.creer(\u0026#34;sms\u0026#34;); notification.envoyer(\u0026#34;Votre commande est expédiée\u0026#34;); Quand utiliser ce pattern ? # Quand la logique de création dépend d\u0026rsquo;une condition (type, configuration, environnement) et qu\u0026rsquo;on veut la centraliser en un seul endroit. Quand on veut isoler le code client des classes concrètes, pour qu\u0026rsquo;il ne dépende que d\u0026rsquo;une interface commune. Quand ajouter un nouveau type d\u0026rsquo;objet ne doit pas obliger à modifier le code appelant partout où l\u0026rsquo;objet est utilisé. Points importants # Factory ne concerne qu\u0026rsquo;un seul type d\u0026rsquo;objet à la fois. Dès qu\u0026rsquo;il faut créer plusieurs objets liés entre eux qui doivent rester cohérents (une famille complète), il faut envisager un Abstract Factory. Une Factory Method est souvent une simple méthode statique ou une méthode d\u0026rsquo;une petite classe dédiée ; dans certains langages, une closure ou une fonction fabrique légère peut la remplacer pour des cas triviaux. Ne transformez pas chaque new du code en Factory par principe : le pattern se justifie quand la logique de création varie réellement ou devient complexe, pas systématiquement. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/design-patterns/factory/","section":"Design Patterns","summary":"Le Factory Pattern encapsule la logique de création d\u0026rsquo;un objet dans une méthode ou une classe dédiée, plutôt que de disperser des \u003ccode\u003enew\u003c/code\u003e conditionnels dans tout le code. Le client dépend d\u0026rsquo;une interface, jamais des classes concrètes créées derrière.","title":"Le Pattern Factory expliqué simplement"},{"content":"Le problème #Quand le statut d\u0026rsquo;une commande change — payée, expédiée, livrée — plusieurs parties du système doivent réagir : envoyer un email au client, envoyer un SMS, notifier l\u0026rsquo;entrepôt pour préparer l\u0026rsquo;envoi. Si la classe Commande appelle directement chacun de ces services dans sa méthode changerStatut(), elle devient couplée à tous ses « abonnés ». Chaque nouveau besoin de notification oblige alors à rouvrir cette classe centrale et à la modifier, encore et encore.\nL\u0026rsquo;idée générale #L\u0026rsquo;Observer définit une relation un-à-plusieurs : quand l\u0026rsquo;état d\u0026rsquo;un objet (le Subject) change, tous les objets qui s\u0026rsquo;y sont abonnés (les Observers) sont automatiquement notifiés, sans que le Subject ait besoin de connaître leur nature exacte.\nTrois éléments composent le pattern :\nSubject : l\u0026rsquo;objet observé, qui maintient une liste d\u0026rsquo;observateurs et les notifie à chaque changement pertinent. Observer : l\u0026rsquo;interface commune que chaque abonné implémente, souvent une seule méthode (notifier()). ConcreteObserver : chaque implémentation concrète, qui réagit à sa manière à la notification reçue. Analogie du quotidien #C\u0026rsquo;est comme s\u0026rsquo;abonner à une chaîne YouTube. Le créateur de contenu (le Subject) ne connaît pas individuellement chaque abonné : il publie simplement une nouvelle vidéo. Tous les abonnés (les Observers) reçoivent une notification automatiquement, chacun réagissant à sa façon — l\u0026rsquo;un regarde immédiatement, l\u0026rsquo;autre ignore la notification et regardera plus tard. Le créateur peut gagner ou perdre des abonnés à tout moment sans jamais avoir à changer sa façon de publier du contenu.\nDiagramme # classDiagram class SuiviCommande { -observers: List~Observer~ +abonner(o: Observer) +desabonner(o: Observer) +changerStatut(statut) } class Observer { \u003c\u003e +notifier(statut) } class NotificationEmail { +notifier(statut) } class NotificationSms { +notifier(statut) } class ServiceEntrepot { +notifier(statut) } SuiviCommande o--\u003e Observer Observer \u003c|.. NotificationEmail Observer \u003c|.. NotificationSms Observer \u003c|.. ServiceEntrepot Exemple de code #PHP #interface Observer { public function notifier(string $statut): void; } final class NotificationEmail implements Observer { public function notifier(string $statut): void { echo \u0026#34;Email : votre commande est maintenant \u0026#39;{$statut}\u0026#39;\u0026#34; . PHP_EOL; } } final class NotificationSms implements Observer { public function notifier(string $statut): void { echo \u0026#34;SMS : statut mis à jour -\u0026gt; {$statut}\u0026#34; . PHP_EOL; } } final class ServiceEntrepot implements Observer { public function notifier(string $statut): void { if ($statut === \u0026#39;payee\u0026#39;) { echo \u0026#34;Entrepôt : préparation de l\u0026#39;envoi lancée\u0026#34; . PHP_EOL; } } } final class SuiviCommande { /** @var Observer[] */ private array $observers = []; public function abonner(Observer $observer): void { $this-\u0026gt;observers[] = $observer; } public function desabonner(Observer $observer): void { $this-\u0026gt;observers = array_filter($this-\u0026gt;observers, fn ($o) =\u0026gt; $o !== $observer); } public function changerStatut(string $statut): void { foreach ($this-\u0026gt;observers as $observer) { $observer-\u0026gt;notifier($statut); } } } // Utilisation $commande = new SuiviCommande(); $commande-\u0026gt;abonner(new NotificationEmail()); $commande-\u0026gt;abonner(new NotificationSms()); $commande-\u0026gt;abonner(new ServiceEntrepot()); $commande-\u0026gt;changerStatut(\u0026#39;payee\u0026#39;); // Les trois observateurs réagissent, chacun à sa manière Java #interface Observer { void notifier(String statut); } class NotificationEmail implements Observer { public void notifier(String statut) { System.out.println(\u0026#34;Email : votre commande est maintenant \u0026#39;\u0026#34; + statut + \u0026#34;\u0026#39;\u0026#34;); } } class NotificationSms implements Observer { public void notifier(String statut) { System.out.println(\u0026#34;SMS : statut mis à jour -\u0026gt; \u0026#34; + statut); } } class ServiceEntrepot implements Observer { public void notifier(String statut) { if (statut.equals(\u0026#34;payee\u0026#34;)) { System.out.println(\u0026#34;Entrepôt : préparation de l\u0026#39;envoi lancée\u0026#34;); } } } class SuiviCommande { private final List\u0026lt;Observer\u0026gt; observers = new ArrayList\u0026lt;\u0026gt;(); public void abonner(Observer observer) { observers.add(observer); } public void desabonner(Observer observer) { observers.remove(observer); } public void changerStatut(String statut) { for (Observer observer : observers) { observer.notifier(statut); } } } // Utilisation SuiviCommande commande = new SuiviCommande(); commande.abonner(new NotificationEmail()); commande.abonner(new NotificationSms()); commande.abonner(new ServiceEntrepot()); commande.changerStatut(\u0026#34;payee\u0026#34;); // Les trois observateurs réagissent, chacun à sa manière JavaScript #class NotificationEmail { notifier(statut) { console.log(`Email : votre commande est maintenant \u0026#39;${statut}\u0026#39;`); } } class NotificationSms { notifier(statut) { console.log(`SMS : statut mis à jour -\u0026gt; ${statut}`); } } class ServiceEntrepot { notifier(statut) { if (statut === \u0026#34;payee\u0026#34;) { console.log(\u0026#34;Entrepôt : préparation de l\u0026#39;envoi lancée\u0026#34;); } } } class SuiviCommande { #observers = []; abonner(observer) { this.#observers.push(observer); } desabonner(observer) { this.#observers = this.#observers.filter((o) =\u0026gt; o !== observer); } changerStatut(statut) { this.#observers.forEach((observer) =\u0026gt; observer.notifier(statut)); } } // Utilisation const commande = new SuiviCommande(); commande.abonner(new NotificationEmail()); commande.abonner(new NotificationSms()); commande.abonner(new ServiceEntrepot()); commande.changerStatut(\u0026#34;payee\u0026#34;); // Les trois observateurs réagissent, chacun à sa manière Quand utiliser ce pattern ? # Quand plusieurs parties du système doivent réagir à un même événement, sans que l\u0026rsquo;émetteur ait besoin de les connaître individuellement. Quand le nombre d\u0026rsquo;abonnés peut varier dans le temps, avec des ajouts ou des retraits à l\u0026rsquo;exécution. Quand on veut découpler l\u0026rsquo;objet qui déclenche un changement de ceux qui y réagissent, pour pouvoir les faire évoluer indépendamment. Évitez Observer si un seul destinataire réagit toujours de la même façon : un appel de méthode direct est plus simple et plus facile à suivre.\nPoints importants # Observer est la base théorique des event listeners du DOM (addEventListener), des EventEmitter de Node.js, et des systèmes pub/sub — le pattern est omniprésent, même sans être nommé explicitement. Attention à l\u0026rsquo;ordre de notification : si les observateurs doivent s\u0026rsquo;exécuter dans un ordre précis ou en parallèle, c\u0026rsquo;est au Subject de le gérer explicitement. Un Observer qui lève une exception peut interrompre la notification des suivants si rien n\u0026rsquo;est prévu pour l\u0026rsquo;isoler : pensez à englober chaque notification dans un try/catch si l\u0026rsquo;un des abonnés ne doit pas bloquer les autres. Toujours prévoir un moyen de se désabonner (desabonner) : l\u0026rsquo;oublier est une cause fréquente de fuite mémoire, notamment en JavaScript avec les écouteurs d\u0026rsquo;événements jamais retirés. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/design-patterns/observer/","section":"Design Patterns","summary":"L\u0026rsquo;Observer Pattern définit une relation un-à-plusieurs : quand un objet change d\u0026rsquo;état, tous ses abonnés sont notifiés automatiquement, sans que l\u0026rsquo;émetteur ait besoin de connaître leur nature exacte. C\u0026rsquo;est le pattern derrière les event listeners et les systèmes pub/sub.","title":"Le Pattern Observer expliqué simplement"},{"content":"Le problème #Un contrôleur d\u0026rsquo;inscription construit directement une requête SQL, ou appelle un ORM, pour vérifier si un e-mail existe déjà avant de créer un utilisateur. Six mois plus tard, cette même vérification est dupliquée dans trois endroits différents : le contrôleur web, une commande CLI d\u0026rsquo;import, et un job asynchrone.\nImpossible de tester la logique d\u0026rsquo;inscription sans une vraie base de données en marche. Impossible de changer d\u0026rsquo;ORM ou de moteur de stockage sans traquer tous les appels techniques éparpillés dans le code métier. La logique de comment on stocke et la logique de ce que fait l\u0026rsquo;application sont mélangées, et chaque évolution technique risque de casser une règle métier qu\u0026rsquo;on ne voit même plus.\nL\u0026rsquo;idée générale #Le Repository Pattern introduit une interface orientée métier qui cache entièrement la façon dont les données sont stockées et récupérées. Le domaine ne connaît que cette interface : trouverParEmail(), sauvegarder() — jamais une requête SQL, jamais un client ORM.\nDeux éléments composent le pattern :\nRepositoryInterface : définie dans le domaine, elle déclare les opérations dont le métier a besoin, dans son propre vocabulaire. Implémentation concrète : vit à l\u0026rsquo;extérieur du domaine (couche infrastructure), et traduit ces opérations vers un mécanisme de stockage précis — base SQL, NoSQL, API externe, ou même une simple liste en mémoire pour les tests. C\u0026rsquo;est une application directe du principe d\u0026rsquo;inversion de dépendance déjà rencontré dans l\u0026rsquo;article sur la Clean Architecture : l\u0026rsquo;interface est définie par le domaine, mais implémentée en dehors de lui. Le domaine dépend d\u0026rsquo;une abstraction qu\u0026rsquo;il possède, jamais d\u0026rsquo;un détail technique qu\u0026rsquo;il ne maîtrise pas.\nAnalogie du quotidien #Le Repository Pattern, c\u0026rsquo;est comme demander un livre à un bibliothécaire. Vous dites « Je cherche tel roman » — vous ne précisez jamais dans quelle salle, sur quelle étagère, ni même si le livre est physiquement dans ce bâtiment ou dans une réserve externe.\nLe bibliothécaire (le Repository) sait où et comment chercher : il traduit votre demande en une action de recherche précise, peu importe le système de rangement utilisé derrière. Si la bibliothèque change d\u0026rsquo;organisation, ou numérise sa réserve, votre façon de demander un livre ne change pas — seul le travail du bibliothécaire s\u0026rsquo;adapte.\nDiagramme # classDiagram class UtilisateurRepositoryInterface { \u003c\u003e +trouverParEmail(email) Utilisateur +sauvegarder(utilisateur) } class ServiceInscription { -repository: UtilisateurRepositoryInterface +inscrire(email) Utilisateur } class PdoUtilisateurRepository { +trouverParEmail(email) Utilisateur +sauvegarder(utilisateur) } class InMemoryUtilisateurRepository { +trouverParEmail(email) Utilisateur +sauvegarder(utilisateur) } UtilisateurRepositoryInterface \u003c|.. PdoUtilisateurRepository UtilisateurRepositoryInterface \u003c|.. InMemoryUtilisateurRepository ServiceInscription --\u003e UtilisateurRepositoryInterface Exemple de code #PHP #// --- Domaine : l\u0026#39;interface vit ici, aucune dépendance technique --- interface UtilisateurRepositoryInterface { public function trouverParEmail(string $email): ?Utilisateur; public function sauvegarder(Utilisateur $utilisateur): void; } final class ServiceInscription { public function __construct(private UtilisateurRepositoryInterface $repository) {} public function inscrire(string $email): Utilisateur { if ($this-\u0026gt;repository-\u0026gt;trouverParEmail($email) !== null) { throw new DomainException(\u0026#39;Cet e-mail est déjà utilisé\u0026#39;); } $utilisateur = new Utilisateur($email); $this-\u0026gt;repository-\u0026gt;sauvegarder($utilisateur); return $utilisateur; } } // --- Infrastructure : implémentation réelle, en dehors du domaine --- final class PdoUtilisateurRepository implements UtilisateurRepositoryInterface { public function __construct(private PDO $pdo) {} public function trouverParEmail(string $email): ?Utilisateur { $stmt = $this-\u0026gt;pdo-\u0026gt;prepare(\u0026#39;SELECT * FROM utilisateurs WHERE email = ?\u0026#39;); $stmt-\u0026gt;execute([$email]); $row = $stmt-\u0026gt;fetch(); return $row ? Utilisateur::depuisLigne($row) : null; } public function sauvegarder(Utilisateur $utilisateur): void { $stmt = $this-\u0026gt;pdo-\u0026gt;prepare(\u0026#39;INSERT INTO utilisateurs (email) VALUES (?)\u0026#39;); $stmt-\u0026gt;execute([$utilisateur-\u0026gt;email()]); } } // --- Tests : implémentation en mémoire, aucune base de données requise --- final class InMemoryUtilisateurRepository implements UtilisateurRepositoryInterface { private array $utilisateurs = []; public function trouverParEmail(string $email): ?Utilisateur { return $this-\u0026gt;utilisateurs[$email] ?? null; } public function sauvegarder(Utilisateur $utilisateur): void { $this-\u0026gt;utilisateurs[$utilisateur-\u0026gt;email()] = $utilisateur; } } Java #// --- Domaine : l\u0026#39;interface vit ici --- public interface UtilisateurRepository { Optional\u0026lt;Utilisateur\u0026gt; trouverParEmail(String email); void sauvegarder(Utilisateur utilisateur); } public class ServiceInscription { private final UtilisateurRepository repository; public ServiceInscription(UtilisateurRepository repository) { this.repository = repository; } public Utilisateur inscrire(String email) { if (repository.trouverParEmail(email).isPresent()) { throw new IllegalStateException(\u0026#34;Cet e-mail est déjà utilisé\u0026#34;); } Utilisateur utilisateur = new Utilisateur(email); repository.sauvegarder(utilisateur); return utilisateur; } } // --- Infrastructure : implémentation JPA, en dehors du domaine --- public class JpaUtilisateurRepository implements UtilisateurRepository { private final EntityManager em; public JpaUtilisateurRepository(EntityManager em) { this.em = em; } public Optional\u0026lt;Utilisateur\u0026gt; trouverParEmail(String email) { return em.createQuery(\u0026#34;SELECT u FROM Utilisateur u WHERE u.email = :email\u0026#34;, Utilisateur.class) .setParameter(\u0026#34;email\u0026#34;, email) .getResultStream() .findFirst(); } public void sauvegarder(Utilisateur utilisateur) { em.persist(utilisateur); } } // --- Tests : implémentation en mémoire --- public class InMemoryUtilisateurRepository implements UtilisateurRepository { private final Map\u0026lt;String, Utilisateur\u0026gt; utilisateurs = new HashMap\u0026lt;\u0026gt;(); public Optional\u0026lt;Utilisateur\u0026gt; trouverParEmail(String email) { return Optional.ofNullable(utilisateurs.get(email)); } public void sauvegarder(Utilisateur utilisateur) { utilisateurs.put(utilisateur.getEmail(), utilisateur); } } JavaScript #// --- Domaine : ServiceInscription ne dépend que d\u0026#39;un \u0026#34;contrat\u0026#34; implicite --- class ServiceInscription { constructor(repository) { this.repository = repository; } async inscrire(email) { const existant = await this.repository.trouverParEmail(email); if (existant) { throw new Error(\u0026#34;Cet e-mail est déjà utilisé\u0026#34;); } const utilisateur = new Utilisateur(email); await this.repository.sauvegarder(utilisateur); return utilisateur; } } // --- Infrastructure : implémentation réelle, via un client de base de données --- class SqlUtilisateurRepository { constructor(db) { this.db = db; } async trouverParEmail(email) { const row = await this.db.get(\u0026#34;SELECT * FROM utilisateurs WHERE email = ?\u0026#34;, [email]); return row ? Utilisateur.depuisLigne(row) : null; } async sauvegarder(utilisateur) { await this.db.run(\u0026#34;INSERT INTO utilisateurs (email) VALUES (?)\u0026#34;, [utilisateur.email]); } } // --- Tests : implémentation en mémoire, aucune base de données requise --- class InMemoryUtilisateurRepository { constructor() { this.utilisateurs = new Map(); } async trouverParEmail(email) { return this.utilisateurs.get(email) ?? null; } async sauvegarder(utilisateur) { this.utilisateurs.set(utilisateur.email, utilisateur); } } Quand utiliser ce pattern ? # Quand la logique métier doit rester indépendante du mécanisme de stockage (SQL, NoSQL, API externe, fichier). Quand vous voulez tester votre logique métier sans dépendre d\u0026rsquo;une vraie base de données, grâce à une implémentation en mémoire. Quand plusieurs sources de données doivent pouvoir être interchangeables (migration de SGBD, ajout d\u0026rsquo;un cache, bascule progressive vers un nouveau système). Évitez d\u0026rsquo;ajouter un Repository pour un CRUD trivial sans logique métier autour : l\u0026rsquo;indirection ajoutée n\u0026rsquo;apporte rien si le code appelant se contente déjà de faire du SQL basique sans règle à protéger. Points importants # L\u0026rsquo;interface du Repository est définie dans le domaine, son implémentation vit à l\u0026rsquo;extérieur — exactement le mécanisme d\u0026rsquo;inversion de dépendance détaillé dans l\u0026rsquo;article sur la Clean Architecture. Un bon Repository expose un vocabulaire métier, pas un vocabulaire de table : préférez trouverCommandesEnAttente() à des méthodes génériques calquées sur le SQL. Un Repository correspond en général à un agrégat métier complet, pas à une table brute — le mapping entre les deux peut être complexe et doit rester caché dans l\u0026rsquo;implémentation. L\u0026rsquo;implémentation en mémoire n\u0026rsquo;est pas un détail secondaire : c\u0026rsquo;est elle qui rend vos tests rapides et fiables, sans base de données à démarrer ni à nettoyer entre chaque test. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/design-patterns/repository/","section":"Design Patterns","summary":"Le Repository Pattern cache les détails de stockage (SQL, ORM, API externe) derrière une interface orientée métier. Le domaine manipule des objets, pas des requêtes — l\u0026rsquo;implémentation concrète du stockage reste un détail interchangeable.","title":"Le Pattern Repository expliqué simplement"},{"content":"Le problème #Une application a besoin d\u0026rsquo;un point d\u0026rsquo;accès unique à une ressource partagée : un journal d\u0026rsquo;application (logger), une configuration lue une seule fois au démarrage, une connexion à un pilote de log. Si chaque partie du code crée sa propre instance de cette classe, on se retrouve avec plusieurs fichiers de logs distincts, des états incohérents, et des ressources dupliquées inutilement.\nLe besoin est simple à formuler : il ne doit exister qu\u0026rsquo;une seule instance de cette classe dans toute l\u0026rsquo;application, et tout le monde doit accéder à la même.\nL\u0026rsquo;idée générale #Le Singleton répond à ce besoin de deux façons combinées :\nIl rend le constructeur privé, pour empêcher quiconque de faire new MaClasse() depuis l\u0026rsquo;extérieur. Il expose une méthode statique (généralement getInstance()) qui crée l\u0026rsquo;instance la première fois qu\u0026rsquo;elle est appelée, puis retourne toujours cette même instance ensuite. Le résultat : peu importe combien de fois getInstance() est appelée, et depuis quel endroit du code, c\u0026rsquo;est toujours le même objet qui est retourné.\nAnalogie du quotidien #C\u0026rsquo;est comme la caisse enregistreuse unique d\u0026rsquo;un petit commerce. Si chaque vendeur utilisait sa propre caisse pour encaisser les clients, les comptes de fin de journée ne correspondraient jamais : chacun aurait sa propre version de l\u0026rsquo;historique des ventes. En n\u0026rsquo;ayant qu\u0026rsquo;une seule caisse partagée par tous les vendeurs, il n\u0026rsquo;existe qu\u0026rsquo;une seule source de vérité, cohérente à tout moment.\nDiagramme # classDiagram class Singleton { -instance: Singleton$ -Singleton() +getInstance() Singleton$ +log(message) } Exemple de code #PHP #final class Logger { private static ?Logger $instance = null; private array $logs = []; private function __construct() {} public static function getInstance(): self { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } public function log(string $message): void { $this-\u0026gt;logs[] = $message; echo \u0026#34;[LOG] {$message}\u0026#34; . PHP_EOL; } } // Utilisation Logger::getInstance()-\u0026gt;log(\u0026#34;Application démarrée\u0026#34;); Logger::getInstance()-\u0026gt;log(\u0026#34;Utilisateur connecté\u0026#34;); // Les deux appels utilisent exactement la même instance Java #public final class Logger { private static Logger instance; private final List\u0026lt;String\u0026gt; logs = new ArrayList\u0026lt;\u0026gt;(); private Logger() {} public static synchronized Logger getInstance() { if (instance == null) { instance = new Logger(); } return instance; } public void log(String message) { logs.add(message); System.out.println(\u0026#34;[LOG] \u0026#34; + message); } } // Utilisation Logger.getInstance().log(\u0026#34;Application démarrée\u0026#34;); Logger.getInstance().log(\u0026#34;Utilisateur connecté\u0026#34;); // synchronized évite que deux threads créent deux instances en même temps JavaScript #class Logger { static #instance; #logs = []; constructor() { if (Logger.#instance) { return Logger.#instance; } Logger.#instance = this; } log(message) { this.#logs.push(message); console.log(`[LOG] ${message}`); } } // Utilisation new Logger().log(\u0026#34;Application démarrée\u0026#34;); new Logger().log(\u0026#34;Utilisateur connecté\u0026#34;); // new Logger() renvoie toujours la même instance Quand utiliser ce pattern ? # Quand une ressource doit véritablement être unique par nature dans l\u0026rsquo;application : un cache en mémoire partagé, un accès à un pilote de log, un registre de configuration en lecture seule. Quand la création répétée de l\u0026rsquo;objet serait coûteuse ou incohérente si elle existait en plusieurs exemplaires. Dans la grande majorité des autres cas, préférez l\u0026rsquo;injection de dépendances : demandez à votre framework ou conteneur de gérer une instance unique avec un scope adapté, plutôt que de coder l\u0026rsquo;unicité en dur dans la classe elle-même.\nPoints importants # Le principal reproche fait au Singleton : il introduit un état global caché, ce qui crée des dépendances invisibles entre des parties du code qui semblent pourtant indépendantes. Il rend les tests unitaires plus fragiles : l\u0026rsquo;état du Singleton peut fuiter d\u0026rsquo;un test à l\u0026rsquo;autre si rien n\u0026rsquo;est prévu pour le réinitialiser entre deux exécutions. Ne confondez pas le Singleton codé en dur avec une instance unique gérée par un conteneur d\u0026rsquo;injection de dépendances : dans ce second cas, la classe reste un objet ordinaire, injectable et remplaçable par un mock en test — seule sa portée (son scope) est configurée comme singleton. En environnement multithread (Java, notamment), la création de l\u0026rsquo;instance doit être protégée contre les accès concurrents, sous peine de voir deux instances créées simultanément. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/design-patterns/singleton/","section":"Design Patterns","summary":"Le Singleton garantit qu\u0026rsquo;une classe n\u0026rsquo;a qu\u0026rsquo;une seule instance, accessible depuis n\u0026rsquo;importe où dans l\u0026rsquo;application. Pratique en apparence, mais c\u0026rsquo;est aussi l\u0026rsquo;un des patterns les plus critiqués : il introduit un état global qui rend le code difficile à tester et à faire évoluer.","title":"Le Pattern Singleton expliqué simplement"},{"content":"Le problème #Imaginez un service de calcul de frais de livraison. Au départ, une seule règle : livraison standard. Puis on ajoute la livraison express. Puis la livraison internationale. Puis un partenaire propose ses propres tarifs. Six mois plus tard, la méthode calculerFrais() ressemble à un mur de if/else (ou de switch) impossible à lire, où chaque nouvelle règle métier ajoute un risque de casser les précédentes.\nLe problème n\u0026rsquo;est pas la logique elle-même : c\u0026rsquo;est qu\u0026rsquo;elle est figée dans une seule méthode. Impossible de tester un mode de livraison isolément, impossible d\u0026rsquo;en ajouter un nouveau sans rouvrir un fichier que tout le monde touche, impossible de changer de comportement à l\u0026rsquo;exécution proprement.\nL\u0026rsquo;idée générale #Le Strategy Pattern propose d\u0026rsquo;extraire chaque variante du comportement dans sa propre classe, toutes ces classes respectant la même interface. Le code appelant ne connaît que l\u0026rsquo;interface — il ignore totalement laquelle des implémentations tourne réellement derrière.\nTrois éléments composent le pattern :\nStrategy : l\u0026rsquo;interface commune, qui déclare la méthode que toutes les variantes doivent implémenter. ConcreteStrategy : chaque implémentation concrète (une classe par comportement). Context : la classe qui utilise une Strategy, sans savoir laquelle précisément — elle lui délègue simplement le travail. Ajouter un nouveau comportement revient à ajouter une nouvelle classe qui implémente l\u0026rsquo;interface. Rien d\u0026rsquo;existant n\u0026rsquo;a besoin d\u0026rsquo;être modifié.\nAnalogie du quotidien #Le Strategy Pattern, c\u0026rsquo;est comme choisir différents moyens de transport selon la situation. Pour aller travailler, vous pouvez prendre le vélo, la voiture ou le métro. L\u0026rsquo;objectif reste le même — se déplacer d\u0026rsquo;un point A à un point B — mais la manière de l\u0026rsquo;atteindre change selon le contexte (météo, distance, budget).\nVous (le Context) ne réinventez pas la façon de vous déplacer à chaque fois : vous choisissez un moyen de transport (ConcreteStrategy) qui respecte toujours le même contrat — m\u0026rsquo;emmener à destination (Strategy). Changer de moyen de transport ne change rien à votre objectif final, seulement à la façon de l\u0026rsquo;atteindre.\nDiagramme # classDiagram class Context { -strategy: ShippingStrategy +setStrategy(s: ShippingStrategy) +calculerFrais(commande) float } class ShippingStrategy { \u003c\u003e +calculer(commande) float } class LivraisonStandard { +calculer(commande) float } class LivraisonExpress { +calculer(commande) float } class LivraisonInternationale { +calculer(commande) float } Context o--\u003e ShippingStrategy ShippingStrategy \u003c|.. LivraisonStandard ShippingStrategy \u003c|.. LivraisonExpress ShippingStrategy \u003c|.. LivraisonInternationale Exemple de code #PHP #interface ShippingStrategy { public function calculer(Commande $commande): float; } final class LivraisonStandard implements ShippingStrategy { public function calculer(Commande $commande): float { return 4.90; } } final class LivraisonExpress implements ShippingStrategy { public function calculer(Commande $commande): float { return 9.90 + ($commande-\u0026gt;poids() * 0.5); } } final class Context { public function __construct(private ShippingStrategy $strategy) {} public function setStrategy(ShippingStrategy $strategy): void { $this-\u0026gt;strategy = $strategy; } public function calculerFrais(Commande $commande): float { return $this-\u0026gt;strategy-\u0026gt;calculer($commande); } } // Utilisation $context = new Context(new LivraisonStandard()); $context-\u0026gt;calculerFrais($commande); // 4.90 $context-\u0026gt;setStrategy(new LivraisonExpress()); $context-\u0026gt;calculerFrais($commande); // change de comportement, sans changer Context Java #interface ShippingStrategy { double calculer(Commande commande); } class LivraisonStandard implements ShippingStrategy { public double calculer(Commande commande) { return 4.90; } } class LivraisonExpress implements ShippingStrategy { public double calculer(Commande commande) { return 9.90 + (commande.getPoids() * 0.5); } } class Context { private ShippingStrategy strategy; public Context(ShippingStrategy strategy) { this.strategy = strategy; } public void setStrategy(ShippingStrategy strategy) { this.strategy = strategy; } public double calculerFrais(Commande commande) { return strategy.calculer(commande); } } // Utilisation Context context = new Context(new LivraisonStandard()); context.calculerFrais(commande); // 4.90 context.setStrategy(new LivraisonExpress()); context.calculerFrais(commande); // change de comportement, sans changer Context JavaScript #class LivraisonStandard { calculer(commande) { return 4.90; } } class LivraisonExpress { calculer(commande) { return 9.90 + commande.poids * 0.5; } } class Context { constructor(strategy) { this.strategy = strategy; } setStrategy(strategy) { this.strategy = strategy; } calculerFrais(commande) { return this.strategy.calculer(commande); } } // Utilisation const context = new Context(new LivraisonStandard()); context.calculerFrais(commande); // 4.90 context.setStrategy(new LivraisonExpress()); context.calculerFrais(commande); // change de comportement, sans changer Context Quand utiliser ce pattern ? # Quand une méthode contient plusieurs variantes d\u0026rsquo;un même algorithme, sélectionnées par un if/else ou un switch qui grossit avec le temps. Quand vous voulez pouvoir changer de comportement à l\u0026rsquo;exécution, sans redéployer ni modifier le code appelant. Quand plusieurs classes partagent une logique presque identique, avec seulement quelques variations de comportement. Quand vous voulez pouvoir tester chaque comportement isolément, sans dépendre du reste de la logique métier. Évitez Strategy si vous n\u0026rsquo;avez qu\u0026rsquo;une seule variante prévisible : l\u0026rsquo;indirection ajoutée par le pattern coûte plus qu\u0026rsquo;elle ne rapporte tant qu\u0026rsquo;un vrai besoin de variation n\u0026rsquo;existe pas.\nPoints importants # Strategy respecte le principe ouvert/fermé (open/closed) : on étend le comportement en ajoutant une classe, jamais en modifiant l\u0026rsquo;existant. Le Context ne doit jamais faire de instanceof ou de test de type sur la Strategy : s\u0026rsquo;il le fait, l\u0026rsquo;abstraction a une fuite. En PHP et en Java modernes, une closure ou une expression lambda peut parfois remplacer une Strategy triviale à une seule méthode — le pattern reste utile dès que la logique se complexifie ou doit être testée indépendamment. Strategy est la base de nombreux autres patterns : il est souvent combiné à une Factory pour décider quelle stratégie instancier selon le contexte. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/design-patterns/strategy/","section":"Design Patterns","summary":"Le Strategy Pattern permet de faire varier un comportement indépendamment du code qui l\u0026rsquo;utilise. Au lieu d\u0026rsquo;empiler des \u003ccode\u003eif/else\u003c/code\u003e, on encapsule chaque variante dans sa propre classe, interchangeable à tout moment.","title":"Le Pattern Strategy expliqué simplement"},{"content":"Le problème #Un développeur teste un service d\u0026rsquo;envoi de notification qui dépend d\u0026rsquo;un EmailSender. Il crée un double de test pour éviter d\u0026rsquo;envoyer un vrai email pendant les tests, mais hésite : doit-il simplement faire en sorte que l\u0026rsquo;appel ne plante pas et retourne une valeur correcte, ou doit-il vérifier que la méthode envoyer() a bien été appelée avec le bon destinataire ?\nCes deux besoins sont différents et confondre les deux mène à des tests soit incomplets (on ne vérifie jamais qu\u0026rsquo;un email a été envoyé), soit trop rigides (on vérifie des détails d\u0026rsquo;implémentation qui n\u0026rsquo;ont pas d\u0026rsquo;importance pour le comportement réel du système). \u0026ldquo;Mock\u0026rdquo; et \u0026ldquo;stub\u0026rdquo; sont souvent utilisés indistinctement dans le langage courant, mais ils répondent à deux questions de test différentes.\nL\u0026rsquo;idée générale #Un stub est un double de test qui retourne des valeurs prédéfinies quand on l\u0026rsquo;appelle. Son rôle est de fournir des données contrôlées pour que le code testé ait quelque chose à traiter, sans se soucier du nombre ou de la nature exacte des appels qu\u0026rsquo;il reçoit. On l\u0026rsquo;utilise pour poser une situation (\u0026ldquo;le client est fidèle\u0026rdquo;, \u0026ldquo;l\u0026rsquo;API renvoie ce prix\u0026rdquo;) et observer comment le code réagit à cette situation.\nUn mock va plus loin : en plus de pouvoir retourner des valeurs, il enregistre les appels qu\u0026rsquo;il reçoit (méthode appelée, arguments passés, nombre de fois) pour permettre de vérifier après coup que le code testé a bien interagi avec lui comme attendu. On l\u0026rsquo;utilise quand l\u0026rsquo;interaction elle-même est le comportement à valider — par exemple : \u0026ldquo;un email de confirmation a-t-il bien été envoyé ?\u0026rdquo;\nLa distinction se résume à la nature de l\u0026rsquo;assertion :\nStub → assertion sur l\u0026rsquo;état : on vérifie la valeur retournée par le code testé. Mock → assertion sur le comportement : on vérifie qu\u0026rsquo;un appel précis a bien eu lieu sur la dépendance. Analogie du quotidien #Un stub, c\u0026rsquo;est comme un figurant dans un film qui répond toujours la même réplique préparée à l\u0026rsquo;avance, quelle que soit la question posée — il sert le décor, sans qu\u0026rsquo;on s\u0026rsquo;intéresse à combien de fois on lui a parlé. Un mock, c\u0026rsquo;est comme un huissier de justice présent sur le tournage : il note precisément qui a dit quoi, à qui, et combien de fois — pas pour jouer un rôle dans la scène, mais pour produire un constat vérifiable après coup.\nDans un test, le stub joue son rôle sans qu\u0026rsquo;on l\u0026rsquo;interroge sur son passé ; le mock, lui, est interrogé après l\u0026rsquo;action pour confirmer que les bonnes interactions ont bien eu lieu.\nDiagramme # sequenceDiagram participant Test participant Service as ServiceNotification participant Stub as ClientRepository (stub) participant Mock as EmailSender (mock) Test-\u003e\u003eStub: configure : estFidele() retourne true Test-\u003e\u003eService: notifier(clientId) Service-\u003e\u003eStub: estFidele(clientId) Stub--\u003e\u003eService: true (valeur prédéfinie) Service-\u003e\u003eMock: envoyer(email, \"Merci pour votre fidélité\") Mock--\u003e\u003eService: void Test-\u003e\u003eMock: vérifie que envoyer() a été appelé avec les bons arguments Exemple de code #PHP #interface ClientRepository { public function estFidele(int $clientId): bool; } interface EmailSender { public function envoyer(string $destinataire, string $message): void; } final class ServiceNotification { public function __construct( private ClientRepository $repository, private EmailSender $emailSender, ) {} public function notifier(int $clientId, string $email): void { if ($this-\u0026gt;repository-\u0026gt;estFidele($clientId)) { $this-\u0026gt;emailSender-\u0026gt;envoyer($email, \u0026#39;Merci pour votre fidélité !\u0026#39;); } } } use PHPUnit\\Framework\\TestCase; final class ServiceNotificationTest extends TestCase { public function testUnClientFideleRecoitUnEmailDeRemerciement(): void { // STUB : fournit une donnée contrôlée, on ne vérifie jamais comment il est appelé $repository = $this-\u0026gt;createStub(ClientRepository::class); $repository-\u0026gt;method(\u0026#39;estFidele\u0026#39;)-\u0026gt;willReturn(true); // MOCK : on vérifie que envoyer() est appelé exactement une fois, avec les bons arguments $emailSender = $this-\u0026gt;createMock(EmailSender::class); $emailSender-\u0026gt;expects($this-\u0026gt;once()) -\u0026gt;method(\u0026#39;envoyer\u0026#39;) -\u0026gt;with(\u0026#39;client@example.com\u0026#39;, \u0026#39;Merci pour votre fidélité !\u0026#39;); $service = new ServiceNotification($repository, $emailSender); $service-\u0026gt;notifier(clientId: 1, email: \u0026#39;client@example.com\u0026#39;); } public function testUnClientNonFideleNeRecoitAucunEmail(): void { $repository = $this-\u0026gt;createStub(ClientRepository::class); $repository-\u0026gt;method(\u0026#39;estFidele\u0026#39;)-\u0026gt;willReturn(false); // MOCK : on vérifie ici l\u0026#39;ABSENCE d\u0026#39;appel $emailSender = $this-\u0026gt;createMock(EmailSender::class); $emailSender-\u0026gt;expects($this-\u0026gt;never())-\u0026gt;method(\u0026#39;envoyer\u0026#39;); $service = new ServiceNotification($repository, $emailSender); $service-\u0026gt;notifier(clientId: 2, email: \u0026#39;autre@example.com\u0026#39;); } } JavaScript #class ServiceNotification { constructor(clientRepository, emailSender) { this.clientRepository = clientRepository; this.emailSender = emailSender; } async notifier(clientId, email) { const estFidele = await this.clientRepository.estFidele(clientId); if (estFidele) { await this.emailSender.envoyer(email, \u0026#34;Merci pour votre fidélité !\u0026#34;); } } } module.exports = { ServiceNotification }; const { ServiceNotification } = require(\u0026#34;./serviceNotification\u0026#34;); test(\u0026#34;un client fidèle reçoit un email de remerciement\u0026#34;, async () =\u0026gt; { // STUB : jest.fn() configuré pour retourner une valeur, jamais interrogé sur ses appels const repositoryStub = { estFidele: jest.fn().mockResolvedValue(true) }; // MOCK : on va vérifier après coup qu\u0026#39;il a été appelé avec les bons arguments const emailSenderMock = { envoyer: jest.fn().mockResolvedValue(undefined) }; const service = new ServiceNotification(repositoryStub, emailSenderMock); await service.notifier(1, \u0026#34;client@example.com\u0026#34;); expect(emailSenderMock.envoyer).toHaveBeenCalledTimes(1); expect(emailSenderMock.envoyer).toHaveBeenCalledWith( \u0026#34;client@example.com\u0026#34;, \u0026#34;Merci pour votre fidélité !\u0026#34; ); }); test(\u0026#34;un client non fidèle ne reçoit aucun email\u0026#34;, async () =\u0026gt; { const repositoryStub = { estFidele: jest.fn().mockResolvedValue(false) }; const emailSenderMock = { envoyer: jest.fn() }; const service = new ServiceNotification(repositoryStub, emailSenderMock); await service.notifier(2, \u0026#34;autre@example.com\u0026#34;); // Vérification d\u0026#39;ABSENCE d\u0026#39;appel, typique d\u0026#39;un mock expect(emailSenderMock.envoyer).not.toHaveBeenCalled(); }); Quand utiliser mock ou stub ? # Utilisez un stub quand vous avez simplement besoin de contrôler une donnée d\u0026rsquo;entrée pour observer comment le code réagit à un état donné — c\u0026rsquo;est le cas le plus courant. Utilisez un mock quand l\u0026rsquo;appel lui-même est le comportement à valider : un email envoyé, un événement publié, un paiement déclenché — des effets de bord où la sortie n\u0026rsquo;est pas une valeur de retour observable autrement. Évitez de \u0026ldquo;mocker\u0026rdquo; par réflexe une dépendance juste pour vérifier ses appels si le résultat final (une valeur retournée, un état modifié) suffit déjà à valider le comportement — préférez alors un stub, moins fragile. Limitez le nombre de mocks par test : un test qui vérifie dix interactions différentes devient un test d\u0026rsquo;implémentation, pas de comportement. Points importants # Le vocabulaire \u0026ldquo;mock\u0026rdquo; est souvent utilisé au sens large dans le langage courant pour désigner tout double de test (mock, stub, fake, spy) — mais dans un entretien technique, la distinction précise entre stub (état) et mock (comportement) est attendue. Un test qui abuse des mocks devient couplé aux détails d\u0026rsquo;implémentation : un refactoring qui ne change pas le comportement observable peut quand même le casser. PHPUnit distingue explicitement createStub() et createMock() ; Jest, plus permissif, utilise jest.fn() pour les deux usages — la différence tient alors uniquement dans la présence ou l\u0026rsquo;absence d\u0026rsquo;assertions toHaveBeenCalledWith. Ni le mock ni le stub ne remplacent un test d\u0026rsquo;intégration : les deux isolent volontairement le code testé de ses vraies dépendances. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/tests/mock-vs-stub/","section":"Tests","summary":"Un stub et un mock remplacent tous deux une vraie dépendance dans un test, mais pour des raisons différentes : le stub fournit des données prêtes à l\u0026rsquo;emploi, le mock vérifie que le code testé a bien effectué certains appels.","title":"Mock vs Stub"},{"content":"Le problème #Une application qui fonctionne en local ou en test ne garantit rien sur son comportement en production, où le trafic réel, la charge concurrente et les pannes d\u0026rsquo;infrastructure externe créent des situations imprévisibles. Sans monitoring, la première personne informée d\u0026rsquo;un problème en production est souvent\u0026hellip; un utilisateur mécontent, ou pire, un client qui part sans même se plaindre.\nLe problème s\u0026rsquo;aggrave avec la complexité de l\u0026rsquo;architecture. Sur un monolithe unique, un problème est relativement facile à localiser : il n\u0026rsquo;y a qu\u0026rsquo;un seul endroit où chercher. Sur une architecture distribuée avec plusieurs services qui s\u0026rsquo;appellent entre eux, une requête lente peut avoir traversé cinq services différents — sans outillage adapté, savoir lequel des cinq est responsable du ralentissement devient un exercice de devinette.\nL\u0026rsquo;idée générale #Le monitoring s\u0026rsquo;appuie sur trois types de signaux complémentaires, souvent appelés les trois piliers de l\u0026rsquo;observabilité :\nLes métriques : des valeurs numériques agrégées dans le temps (nombre de requêtes par seconde, taux d\u0026rsquo;erreur, latence moyenne, utilisation CPU). Elles sont légères à stocker et parfaites pour détecter une anomalie et définir des seuils d\u0026rsquo;alerte. Les logs : des événements horodatés et détaillés, écrits par l\u0026rsquo;application au fil de son exécution. Ils permettent d\u0026rsquo;investiguer précisément ce qui s\u0026rsquo;est passé à un instant donné, avec le contexte complet. Les traces : le chemin complet d\u0026rsquo;une requête à travers plusieurs services, avec le temps passé dans chacun. Indispensables sur une architecture distribuée pour localiser où un ralentissement ou une erreur se produit réellement. Ces trois signaux répondent à des questions différentes et se complètent : une métrique alerte \u0026ldquo;le taux d\u0026rsquo;erreur a augmenté\u0026rdquo;, une trace montre \u0026ldquo;c\u0026rsquo;est le service de paiement qui répond lentement\u0026rdquo;, et les logs de ce service précisent \u0026ldquo;c\u0026rsquo;est un timeout vers la base de données\u0026rdquo;.\nAu-dessus de ces signaux, l\u0026rsquo;alerting définit les conditions qui déclenchent une notification vers une équipe — l\u0026rsquo;objectif étant d\u0026rsquo;être prévenu avant, ou au pire dès que, l\u0026rsquo;utilisateur est impacté, sans pour autant noyer l\u0026rsquo;équipe sous des alertes non actionnables.\nAnalogie du quotidien #Le monitoring, c\u0026rsquo;est comme le tableau de bord et le carnet d\u0026rsquo;entretien d\u0026rsquo;une voiture. Les métriques, ce sont les jauges du tableau de bord — vitesse, niveau d\u0026rsquo;essence, température moteur — des chiffres qui évoluent en continu et déclenchent un voyant si un seuil est dépassé. Les logs, c\u0026rsquo;est le carnet d\u0026rsquo;entretien détaillé : chaque vidange, chaque réparation, avec la date et le kilométrage exact, consultable après coup pour comprendre l\u0026rsquo;historique complet du véhicule. Les traces, ce serait un système qui suivrait précisément le trajet d\u0026rsquo;un signal électrique depuis la clé de contact jusqu\u0026rsquo;au démarreur, en passant par chaque relais, pour localiser exactement où la panne se situe quand la voiture ne démarre pas.\nLe voyant \u0026ldquo;température moteur\u0026rdquo; (une métrique) vous dit qu\u0026rsquo;il y a un problème. Le carnet d\u0026rsquo;entretien (les logs) et le diagnostic détaillé du circuit (les traces) vous disent pourquoi.\nDiagramme # flowchart TD App[Application en production] --\u003e|émet| Metrics[Métriques : latence, taux d'erreur, CPU] App --\u003e|émet| Logs[Logs applicatifs horodatés] App --\u003e|émet| Traces[Traces distribuées entre services] Metrics --\u003e Dash[Dashboard de supervision] Logs --\u003e Agg[Agrégateur de logs centralisé] Traces --\u003e Viz[Visualisation des traces] Dash --\u003e Alert{Seuil dépassé ?} Alert --\u003e|oui, actionnable| Notif[Notification à l'équipe d'astreinte] Alert --\u003e|non| Dash Notif --\u003e Invest[Investigation via logs + traces] Exemple de code ## alert-rules.yml — règle d\u0026#39;alerte Prometheus sur le taux d\u0026#39;erreur HTTP groups: - name: api-alerts rules: - alert: TauxErreurEleve expr: | ( sum(rate(http_requests_total{status=~\u0026#34;5..\u0026#34;}[5m])) / sum(rate(http_requests_total[5m])) ) \u0026gt; 0.05 for: 5m labels: severity: critical annotations: summary: \u0026#34;Taux d\u0026#39;erreur HTTP au-dessus de 5% depuis 5 minutes\u0026#34; description: \u0026#34;{{ $value | humanizePercentage }} des requêtes échouent sur l\u0026#39;API mon-api.\u0026#34; - alert: LatenceP99Elevee expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) \u0026gt; 1.5 for: 10m labels: severity: warning annotations: summary: \u0026#34;La latence P99 dépasse 1.5s depuis 10 minutes\u0026#34; // Log structuré côté application : facilement filtrable et corrélable logger.info(\u0026#34;commande_creee\u0026#34;, { requestId: req.id, userId: user.id, commandeId: commande.id, montant: commande.montant, duree_ms: Date.now() - debut, }); Quand mettre en place du monitoring ? # Dès la mise en production d\u0026rsquo;un service, même modeste : découvrir un incident via un utilisateur plutôt que via une alerte coûte toujours plus cher en réputation et en temps de résolution. De façon renforcée dès qu\u0026rsquo;une architecture devient distribuée (plusieurs services, microservices) : c\u0026rsquo;est là que les traces deviennent indispensables pour localiser un problème. Avant un événement à fort trafic prévisible (soldes, lancement produit) : le monitoring permet de détecter la dégradation avant la panne complète. Un monitoring minimal (métriques de base + alerting) suffit pour un petit projet ; les traces distribuées deviennent réellement nécessaires à partir du moment où une requête traverse plusieurs services. Points importants # Une alerte doit toujours être actionnable : une alerte qui ne mène à aucune action claire de la part de la personne qui la reçoit crée de la fatigue d\u0026rsquo;alerte et fait ignorer, à terme, les vraies urgences. Les SLO (Service Level Objectives) et SLI (Service Level Indicators) formalisent ce qu\u0026rsquo;on mesure et le seuil acceptable, en s\u0026rsquo;alignant sur l\u0026rsquo;impact utilisateur réel plutôt que sur des métriques purement techniques. Observabilité et monitoring ne sont pas synonymes : le monitoring surveille des indicateurs connus à l\u0026rsquo;avance, l\u0026rsquo;observabilité permet d\u0026rsquo;explorer et de répondre à des questions qu\u0026rsquo;on n\u0026rsquo;avait pas anticipées. Le monitoring a un coût (stockage des logs et métriques, charge de calcul) qu\u0026rsquo;il faut dimensionner : tout logguer en détail indéfiniment devient rapidement coûteux, d\u0026rsquo;où l\u0026rsquo;usage de rétentions et d\u0026rsquo;agrégations différenciées selon l\u0026rsquo;ancienneté des données. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/cloud-devops/monitoring/","section":"Cloud \u0026 DevOps","summary":"Le monitoring consiste à observer en continu l\u0026rsquo;état d\u0026rsquo;un système en production grâce à trois piliers complémentaires — métriques, logs et traces — pour détecter un problème et alerter avant, ou au pire au moment où, les utilisateurs le ressentent.","title":"Monitoring"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/","section":"MyBearIsYellow","summary":"","title":"MyBearIsYellow"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/mysql/","section":"Tags","summary":"","title":"Mysql"},{"content":"Le problème #Prenons une table unique qui stocke des commandes avec toutes les informations à plat :\ncommande_id client_nom client_email produit_nom produit_prix quantite 1 Dupont dupont@mail.fr Clavier 49.90 2 2 Dupont dupont@mail.fr Souris 19.90 1 L\u0026rsquo;email du client \u0026ldquo;Dupont\u0026rdquo; est répété à chaque commande. Si Dupont change d\u0026rsquo;adresse email, il faut mettre à jour toutes ses lignes de commandes — en oublier une seule laisse la base dans un état incohérent, avec deux emails différents pour la même personne. C\u0026rsquo;est ce qu\u0026rsquo;on appelle une anomalie de mise à jour. Il existe aussi des anomalies d\u0026rsquo;insertion (impossible d\u0026rsquo;enregistrer un produit qui n\u0026rsquo;a encore jamais été commandé) et de suppression (supprimer la seule commande d\u0026rsquo;un client fait disparaître ses coordonnées).\nL\u0026rsquo;idée générale #La normalisation est une suite de règles progressives, appelées formes normales, qui structurent une base pour éliminer ces anomalies :\n1NF (première forme normale) : chaque cellule contient une valeur atomique unique — pas de liste de valeurs dans une même colonne, pas de colonnes répétées (produit1, produit2, produit3\u0026hellip;). 2NF (deuxième forme normale) : en plus de 1NF, chaque colonne non-clé dépend de la clé primaire entière, pas d\u0026rsquo;une seule partie d\u0026rsquo;une clé composite. Ne s\u0026rsquo;applique concrètement que si la clé primaire est composée de plusieurs colonnes. 3NF (troisième forme normale) : en plus de 2NF, chaque colonne non-clé dépend uniquement de la clé primaire, jamais d\u0026rsquo;une autre colonne non-clé (pas de \u0026ldquo;dépendance transitive\u0026rdquo;). Dans l\u0026rsquo;exemple ci-dessus, client_email dépend de client_nom, pas directement de commande_id — c\u0026rsquo;est une violation de 3NF. En pratique, atteindre la 3NF suffit pour la grande majorité des applications métier : au-delà (4NF, 5NF, BCNF), les cas sont plus rares et plus académiques.\nAnalogie du quotidien #Normaliser une base, c\u0026rsquo;est comme trier des papiers administratifs mélangés dans une seule pile. Au lieu de garder une feuille par événement où l\u0026rsquo;on recopie à chaque fois son nom, son adresse et sa date de naissance, on crée un dossier \u0026ldquo;identité\u0026rdquo; unique avec ces informations, et chaque feuille d\u0026rsquo;événement se contente d\u0026rsquo;y faire référence par un numéro de dossier.\nSi vous déménagez, vous ne corrigez qu\u0026rsquo;un seul document — le dossier \u0026ldquo;identité\u0026rdquo; — au lieu de rouvrir chaque feuille d\u0026rsquo;événement pour y changer l\u0026rsquo;adresse. Chaque information vit à un seul endroit, référencée partout où elle est utile.\nDiagramme # erDiagram CLIENTS ||--o{ COMMANDES : passe COMMANDES ||--o{ LIGNES_COMMANDE : contient PRODUITS ||--o{ LIGNES_COMMANDE : référencé_par CLIENTS { int id PK string nom string email } COMMANDES { int id PK int client_id FK date date_commande } LIGNES_COMMANDE { int commande_id FK int produit_id FK int quantite } PRODUITS { int id PK string nom decimal prix } Exemple de code #-- Table dénormalisée initiale (viole 1NF et 3NF) CREATE TABLE commandes_denormalisees ( commande_id INT, client_nom VARCHAR(100), client_email VARCHAR(150), produit_nom VARCHAR(100), produit_prix DECIMAL(10,2), quantite INT ); -- Après normalisation (3NF) : chaque information n\u0026#39;existe qu\u0026#39;à un seul endroit CREATE TABLE clients ( id SERIAL PRIMARY KEY, nom VARCHAR(100) NOT NULL, email VARCHAR(150) UNIQUE NOT NULL ); CREATE TABLE produits ( id SERIAL PRIMARY KEY, nom VARCHAR(100) NOT NULL, prix DECIMAL(10,2) NOT NULL ); CREATE TABLE commandes ( id SERIAL PRIMARY KEY, client_id INT NOT NULL REFERENCES clients(id), date_commande DATE NOT NULL DEFAULT CURRENT_DATE ); CREATE TABLE lignes_commande ( commande_id INT NOT NULL REFERENCES commandes(id), produit_id INT NOT NULL REFERENCES produits(id), quantite INT NOT NULL CHECK (quantite \u0026gt; 0), PRIMARY KEY (commande_id, produit_id) ); -- Reconstituer l\u0026#39;information complète nécessite désormais des jointures SELECT cl.nom, p.nom AS produit, lc.quantite, p.prix FROM lignes_commande lc JOIN commandes c ON c.id = lc.commande_id JOIN clients cl ON cl.id = c.client_id JOIN produits p ON p.id = lc.produit_id WHERE c.id = 1; Quand utiliser la normalisation ? # Sur les systèmes transactionnels (OLTP) où les écritures fréquentes doivent rester cohérentes : e-commerce, gestion de comptes, réservation. Quand la même information (nom, email, prix) est susceptible d\u0026rsquo;être répétée dans plusieurs lignes ou tables. Moins prioritaire sur les systèmes orientés lecture intensive (reporting, data warehouse), où une dénormalisation contrôlée peut réduire le nombre de jointures et accélérer les requêtes de lecture, au prix d\u0026rsquo;une gestion plus rigoureuse de la cohérence. À doser selon le contexte : la 3NF est un bon objectif par défaut, pas un dogme absolu à appliquer aveuglément partout. Points importants # Chaque forme normale suppose que la précédente est déjà respectée : impossible d\u0026rsquo;être en 3NF sans être en 2NF, impossible d\u0026rsquo;être en 2NF sans être en 1NF. La normalisation réduit la duplication mais augmente le nombre de jointures nécessaires pour reconstituer une vue complète — c\u0026rsquo;est un compromis, pas un gain sans contrepartie. Une dépendance transitive (colonne A qui dépend de colonne B, qui dépend de la clé primaire) est le piège le plus fréquent qui empêche d\u0026rsquo;atteindre la 3NF. En entretien, on attend souvent qu\u0026rsquo;un candidat sache repérer une table dénormalisée à l\u0026rsquo;œil et proposer spontanément un découpage en tables reliées par des clés. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/bases-de-donnees/normalisation/","section":"Bases de données","summary":"La normalisation est un ensemble de règles progressives (1NF, 2NF, 3NF) qui éliminent la duplication de données dans une base relationnelle. Elle transforme une table fourre-tout en plusieurs tables cohérentes, reliées par des clés.","title":"Normalisation"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/nosql/","section":"Tags","summary":"","title":"Nosql"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/oop/","section":"Tags","summary":"","title":"Oop"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/php/","section":"Tags","summary":"","title":"Php"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/postgresql/","section":"Tags","summary":"","title":"Postgresql"},{"content":"Le problème #Un développeur modifie une fonction de calcul de remise pour ajouter un nouveau cas. Le code compile, l\u0026rsquo;application se lance, tout semble fonctionner. Trois jours plus tard, un client signale que les remises fidélité ne s\u0026rsquo;appliquent plus depuis cette modification — un cas qui fonctionnait avant a silencieusement cassé.\nSans tests, la seule façon de savoir si un changement casse quelque chose est de tout vérifier manuellement, ou d\u0026rsquo;attendre que les utilisateurs le découvrent. Sur un petit script, ça passe. Sur une application qui grossit avec des dizaines de développeurs et des centaines de comportements interdépendants, ça devient impossible à tenir — chaque modification devient un pari.\nL\u0026rsquo;idée générale #Un test automatisé encode, une bonne fois pour toutes, ce qu\u0026rsquo;un bout de code est censé faire. On lui donne une entrée connue, on vérifie que la sortie correspond à ce qui est attendu. Ce test peut ensuite être rejoué en quelques secondes, autant de fois que nécessaire, sans intervention humaine.\nCela donne trois bénéfices concrets :\nUn filet de sécurité : si une modification casse un comportement existant, un test échoue immédiatement, avant que ça n\u0026rsquo;atteigne la production. Une documentation vivante : contrairement à un commentaire ou un fichier README, un test ne peut pas mentir — s\u0026rsquo;il passe, c\u0026rsquo;est que le comportement qu\u0026rsquo;il décrit est bien celui du code actuel. La liberté de refactorer : améliorer la structure interne d\u0026rsquo;un code sans tests est risqué, car on ne sait pas si son comportement externe a changé. Avec des tests, on refactore, on relance la suite, et un feu vert confirme que rien n\u0026rsquo;a bougé. Le coût est réel — écrire un test prend du temps — mais il est payé une fois, alors que le bénéfice (détecter une régression) est payé à chaque exécution future, potentiellement des milliers de fois sur la durée de vie du projet.\nAnalogie du quotidien #Écrire des tests, c\u0026rsquo;est comme installer des détecteurs de fumée dans une maison. Les poser prend du temps et ne rend la maison ni plus grande ni plus belle. Mais le jour où un départ de feu se déclare, c\u0026rsquo;est ce détecteur — pas la vigilance humaine — qui alerte à temps pour agir avant que les dégâts ne deviennent irréversibles.\nSans détecteur, la maison peut très bien fonctionner pendant des années sans incident. Le problème n\u0026rsquo;est pas la probabilité qu\u0026rsquo;un incident arrive un jour donné, c\u0026rsquo;est le coût quand il arrive sans qu\u0026rsquo;on l\u0026rsquo;ait vu venir.\nDiagramme # flowchart LR A[Code sans tests] --\u003e|modification| B{Comportement cassé ?} B --\u003e|Oui, découvert en prod| C[Bug signalé par un client] B --\u003e|Non, mais personne ne le sait avec certitude| D[Confiance faible] E[Code avec tests] --\u003e|modification| F[Suite de tests relancée] F --\u003e|Échec| G[Bug détecté avant le déploiement] F --\u003e|Succès| H[Confiance élevée pour déployer] Exemple de code #PHP #// Code métier à tester final class CalculateurRemise { public function calculer(float $montant, bool $estFidele): float { if ($estFidele) { return $montant * 0.9; } return $montant; } } use PHPUnit\\Framework\\TestCase; final class CalculateurRemiseTest extends TestCase { public function testClientFideleObtientDixPourcentDeRemise(): void { $calculateur = new CalculateurRemise(); $resultat = $calculateur-\u0026gt;calculer(100.0, estFidele: true); $this-\u0026gt;assertSame(90.0, $resultat); } public function testClientNonFideleNObtientAucuneRemise(): void { $calculateur = new CalculateurRemise(); $resultat = $calculateur-\u0026gt;calculer(100.0, estFidele: false); $this-\u0026gt;assertSame(100.0, $resultat); } } JavaScript #// Code métier à tester function calculerRemise(montant, estFidele) { return estFidele ? montant * 0.9 : montant; } module.exports = { calculerRemise }; const { calculerRemise } = require(\u0026#34;./calculateurRemise\u0026#34;); describe(\u0026#34;calculerRemise\u0026#34;, () =\u0026gt; { it(\u0026#34;applique 10% de remise pour un client fidèle\u0026#34;, () =\u0026gt; { expect(calculerRemise(100, true)).toBe(90); }); it(\u0026#34;n\u0026#39;applique aucune remise pour un client non fidèle\u0026#34;, () =\u0026gt; { expect(calculerRemise(100, false)).toBe(100); }); }); Quand utiliser des tests ? # Sur tout code métier qui contient une logique — dès qu\u0026rsquo;il y a une condition, un calcul ou une règle, il y a quelque chose à casser, donc quelque chose à tester. Avant de refactorer un code existant : un filet de tests, même minimal, donne la confiance nécessaire pour changer la structure sans changer le comportement. Sur un code appelé à évoluer souvent ou par plusieurs développeurs : plus le nombre de mains qui touchent le code augmente, plus le risque de régression silencieuse augmente. Moins prioritaire sur un script jetable, utilisé une fois et jamais réutilisé — le coût d\u0026rsquo;écrire les tests dépasse alors le bénéfice attendu. Points importants # Un test ne prouve jamais l\u0026rsquo;absence de bug en général : il prouve seulement qu\u0026rsquo;un comportement précis, pour une entrée précise, produit la sortie attendue. Les tests les plus utiles sont ceux qui documentent une intention métier, pas ceux qui testent des détails d\u0026rsquo;implémentation qui changeront au premier refactoring. La valeur des tests se mesure dans le temps, pas au moment où on les écrit : c\u0026rsquo;est un investissement, pas une dépense immédiate. Une suite de tests qui n\u0026rsquo;est jamais relancée (par exemple absente de la CI) perd une grande partie de sa valeur : le filet de sécurité ne sert à rien s\u0026rsquo;il n\u0026rsquo;est pas systématiquement vérifié. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/tests/pourquoi-ecrire-des-tests/","section":"Tests","summary":"Écrire des tests coûte du temps immédiat, mais ce temps est un investissement : les tests deviennent un filet de sécurité contre les régressions et une documentation toujours à jour du comportement réel du code.","title":"Pourquoi écrire des tests ?"},{"content":"Pourquoi MyBearIsYellow ? #Au départ, notre ours était brun.\nUn ours comme les autres : curieux, mais parfois perdu face à toutes ces notions qui semblent évidentes une fois qu\u0026rsquo;on les maîtrise. Architecture, design patterns, tests, cloud, bases de données… Beaucoup de concepts à découvrir, beaucoup de termes à comprendre.\nPuis il a commencé à coder.\nPas simplement à copier des solutions trouvées ici ou là, mais à chercher ce qui se cache derrière chaque idée.\nPourquoi utiliser ce pattern ?\nPourquoi tester de cette manière ?\nPourquoi choisir cette architecture plutôt qu\u0026rsquo;une autre ?\nPetit à petit, quelque chose a changé.\nSon pelage est devenu jaune.\nLe jaune représente cette petite étincelle qui apparaît quand un concept devient vraiment clair. Ce moment où l\u0026rsquo;on ne connaît plus seulement une définition, mais où l\u0026rsquo;on est capable d\u0026rsquo;expliquer une idée, de l\u0026rsquo;appliquer et de la transmettre à son tour.\nMyBearIsYellow est né de cette idée : apprendre autrement.\nIci, l\u0026rsquo;objectif n\u0026rsquo;est pas d\u0026rsquo;accumuler des dizaines de technologies ou de réciter des réponses d\u0026rsquo;entretien. L\u0026rsquo;objectif est de comprendre les fondamentaux du développement moderne avec des explications simples, des exemples concrets et une approche orientée pratique.\nParce qu\u0026rsquo;un développeur ne progresse pas uniquement en écrivant du code.\nIl progresse quand il comprend pourquoi il l\u0026rsquo;écrit.\n","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/notre-histoire/","section":"MyBearIsYellow","summary":"\u003ch1 id=\"pourquoi-mybearisyellow-\" class=\"relative group\"\u003ePourquoi MyBearIsYellow ? \u003cspan class=\"absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100\"\u003e\u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700\" style=\"text-decoration-line: none !important;\" href=\"#pourquoi-mybearisyellow-\" aria-label=\"Ancre\"\u003e#\u003c/a\u003e\u003c/span\u003e\u003c/h1\u003e\u003cp\u003eAu départ, notre ours était brun.\u003c/p\u003e\n\u003cp\u003eUn ours comme les autres : curieux, mais parfois perdu face à toutes ces notions qui semblent évidentes une fois qu\u0026rsquo;on les maîtrise. Architecture, design patterns, tests, cloud, bases de données… Beaucoup de concepts à découvrir, beaucoup de termes à comprendre.\u003c/p\u003e","title":"Pourquoi MyBearIsYellow ?"},{"content":"Le problème #Une équipe, soucieuse de tester \u0026ldquo;comme un vrai utilisateur\u0026rdquo;, construit sa confiance principalement sur des tests end-to-end qui pilotent un navigateur automatisé de bout en bout. Au fil des mois, la suite de tests grossit, prend plus d\u0026rsquo;une heure à s\u0026rsquo;exécuter, et devient instable : des tests échouent de façon aléatoire à cause de délais réseau ou d\u0026rsquo;animations UI, sans rapport avec un vrai bug. Personne ne fait plus confiance aux échecs, et l\u0026rsquo;équipe finit par relancer les tests \u0026ldquo;jusqu\u0026rsquo;à ce que ça passe\u0026rdquo;.\nLe problème n\u0026rsquo;est pas les tests end-to-end en eux-mêmes, mais leur proportion excessive dans l\u0026rsquo;ensemble de la suite. Toutes les formes de test n\u0026rsquo;ont pas le même coût ni le même rôle, et les mélanger sans stratégie mène à une suite lente, peu fiable, et redoutée plutôt qu\u0026rsquo;utile.\nL\u0026rsquo;idée générale #La pyramide des tests, popularisée par Mike Cohn, propose une répartition de l\u0026rsquo;effort de test en trois niveaux, empilés du plus nombreux et rapide au moins nombreux et lent :\nTests unitaires (base) : testent une unité de code isolée, dépendances mockées. Très rapides (millisecondes), très nombreux, faciles à écrire et à maintenir. Tests d\u0026rsquo;intégration (milieu) : vérifient que plusieurs composants réels collaborent correctement (base de données, appels API internes). Plus lents, moins nombreux. Tests end-to-end (sommet) : simulent un parcours utilisateur complet à travers le système réel (souvent via un navigateur automatisé). Très réalistes, mais lents et plus fragiles ; on en garde peu, réservés aux parcours critiques. L\u0026rsquo;idée n\u0026rsquo;est pas de choisir un seul niveau, mais de doser l\u0026rsquo;investissement selon le compromis rapidité/réalisme de chaque niveau : beaucoup de tests bon marché à la base pour couvrir la logique en détail, quelques tests coûteux au sommet pour valider que tout s\u0026rsquo;assemble correctement du point de vue de l\u0026rsquo;utilisateur final.\nAnalogie du quotidien #La pyramide des tests, c\u0026rsquo;est comme les contrôles qualité dans la fabrication d\u0026rsquo;une voiture. On teste chaque pièce individuellement en très grand nombre — chaque vis, chaque capteur — car ces contrôles sont rapides et bon marché à répéter à chaque pièce produite. On teste ensuite des sous-ensembles assemblés (le moteur complet sur banc d\u0026rsquo;essai) en nombre plus restreint, car c\u0026rsquo;est plus long à mettre en place. Enfin, on ne fait rouler qu\u0026rsquo;un nombre limité de voitures complètes sur circuit d\u0026rsquo;essai réel — un test long, coûteux, mais qui valide l\u0026rsquo;expérience finale telle que le client la vivra.\nPersonne ne teste chaque voiture terminée sur circuit pour vérifier que chaque vis est bien serrée : ce serait beaucoup trop lent. Et personne ne se fie uniquement au contrôle des vis pour garantir que la voiture roule bien une fois assemblée. Chaque niveau de contrôle répond à une question différente.\nDiagramme # flowchart TD subgraph Pyramide[\" \"] direction TB E2E[\"End-to-endpeu nombreux · lents · très réalistes\"] INT[\"Tests d'intégrationnombre modéré · vitesse moyenne\"] UNIT[\"Tests unitairestrès nombreux · très rapides · isolés\"] end E2E --- INT INT --- UNIT Exemple de code #PHP #// --- Niveau 1 : test UNITAIRE, dépendance mockée, exécution en millisecondes --- use PHPUnit\\Framework\\TestCase; final class CalculateurTvaUnitTest extends TestCase { public function testCalculLaTvaAVingtPourcent(): void { $calculateur = new CalculateurTva(tauxRepository: $this-\u0026gt;createStub(TauxRepository::class)); $this-\u0026gt;assertSame(20.0, $calculateur-\u0026gt;calculer(100.0, \u0026#39;FR\u0026#39;)); } } // --- Niveau 2 : test D\u0026#39;INTÉGRATION, vraie base de test, plus lent --- final class CalculateurTvaIntegrationTest extends TestCase { public function testRecupereLeTauxDepuisUneVraieBaseDeDonnees(): void { $pdo = new PDO(\u0026#39;sqlite::memory:\u0026#39;); $pdo-\u0026gt;exec(\u0026#39;CREATE TABLE taux (pays TEXT, taux REAL)\u0026#39;); $pdo-\u0026gt;exec(\u0026#34;INSERT INTO taux VALUES (\u0026#39;FR\u0026#39;, 0.20)\u0026#34;); $calculateur = new CalculateurTva(new PdoTauxRepository($pdo)); $this-\u0026gt;assertSame(20.0, $calculateur-\u0026gt;calculer(100.0, \u0026#39;FR\u0026#39;)); } } // --- Niveau 3 : test END-TO-END (Symfony Panther), parcours navigateur complet --- use Symfony\\Component\\Panther\\PantherTestCase; final class ParcoursCommandeE2ETest extends PantherTestCase { public function testUnClientPeutAllerJusquAuPaiement(): void { $client = static::createPantherClient(); $client-\u0026gt;request(\u0026#39;GET\u0026#39;, \u0026#39;/produit/123\u0026#39;); $client-\u0026gt;clickLink(\u0026#39;Ajouter au panier\u0026#39;); $client-\u0026gt;clickLink(\u0026#39;Passer commande\u0026#39;); $client-\u0026gt;submitForm(\u0026#39;Payer\u0026#39;, [\u0026#39;carte\u0026#39; =\u0026gt; \u0026#39;4242424242424242\u0026#39;]); $this-\u0026gt;assertSelectorTextContains(\u0026#39;h1\u0026#39;, \u0026#39;Commande confirmée\u0026#39;); } } JavaScript #// --- Niveau 1 : test UNITAIRE avec Jest, dépendance mockée --- test(\u0026#34;calcule 20% de TVA (unitaire)\u0026#34;, () =\u0026gt; { const tauxRepositoryMock = { getTaux: jest.fn().mockReturnValue(0.2) }; const calculateur = new CalculateurTva(tauxRepositoryMock); expect(calculateur.calculer(100, \u0026#34;FR\u0026#34;)).toBe(20); }); // --- Niveau 2 : test D\u0026#39;INTÉGRATION, vraie base SQLite en mémoire --- test(\u0026#34;récupère le taux depuis une vraie base de données (intégration)\u0026#34;, async () =\u0026gt; { const db = await openTestDatabase(); await db.run(\u0026#34;INSERT INTO taux VALUES (\u0026#39;FR\u0026#39;, 0.20)\u0026#34;); const calculateur = new CalculateurTva(new SqlTauxRepository(db)); expect(await calculateur.calculer(100, \u0026#34;FR\u0026#34;)).toBe(20); }); // --- Niveau 3 : test END-TO-END avec Playwright, navigateur réel --- const { test, expect } = require(\u0026#34;@playwright/test\u0026#34;); test(\u0026#34;un client peut aller jusqu\u0026#39;au paiement (end-to-end)\u0026#34;, async ({ page }) =\u0026gt; { await page.goto(\u0026#34;/produit/123\u0026#34;); await page.click(\u0026#34;text=Ajouter au panier\u0026#34;); await page.click(\u0026#34;text=Passer commande\u0026#34;); await page.fill(\u0026#34;#carte\u0026#34;, \u0026#34;4242424242424242\u0026#34;); await page.click(\u0026#34;text=Payer\u0026#34;); await expect(page.locator(\u0026#34;h1\u0026#34;)).toHaveText(\u0026#34;Commande confirmée\u0026#34;); }); Quand utiliser chaque niveau ? # Tests unitaires : pour toute logique métier isolable — la majorité de votre effort de test devrait s\u0026rsquo;y concentrer, car ils donnent le retour le plus rapide. Tests d\u0026rsquo;intégration : pour les points de jonction sensibles — requêtes SQL, appels à un service interne, sérialisation d\u0026rsquo;une API — là où l\u0026rsquo;assemblage réel comporte un risque que le mock ne peut pas révéler. Tests end-to-end : réservés aux parcours critiques du point de vue métier (inscription, paiement, connexion) — pas pour couvrir chaque variante possible, ce qui serait beaucoup trop lent à exécuter et à maintenir. Si votre suite met plus de quelques minutes à s\u0026rsquo;exécuter en local, c\u0026rsquo;est souvent le signe d\u0026rsquo;une pyramide déséquilibrée, avec trop de poids porté par les niveaux supérieurs. Points importants # La forme pyramidale n\u0026rsquo;est pas une règle stricte à respecter au chiffre près : c\u0026rsquo;est un principe de répartition de l\u0026rsquo;effort selon le rapport coût/bénéfice de chaque niveau. Une \u0026ldquo;pyramide inversée\u0026rdquo; ou un \u0026ldquo;diamant\u0026rdquo; (beaucoup d\u0026rsquo;intégration, peu d\u0026rsquo;unitaire et d\u0026rsquo;e2e) sont des variantes parfois défendues selon le contexte — l\u0026rsquo;important est d\u0026rsquo;avoir fait ce choix consciemment, pas par accumulation hasardeuse. Un test end-to-end qui échoue est souvent le symptôme d\u0026rsquo;un problème détectable plus tôt et plus précisément par un test unitaire ou d\u0026rsquo;intégration — quand c\u0026rsquo;est le cas, ajouter le test manquant au bon niveau plutôt que d\u0026rsquo;empiler les tests e2e. La vitesse d\u0026rsquo;exécution de la suite complète conditionne directement la fréquence à laquelle l\u0026rsquo;équipe la relance : une suite lente est une suite qu\u0026rsquo;on finit par éviter de lancer. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/tests/pyramide-des-tests/","section":"Tests","summary":"La pyramide des tests décrit une répartition saine de l\u0026rsquo;effort de test : de nombreux tests unitaires rapides à la base, un nombre plus restreint de tests d\u0026rsquo;intégration au milieu, et très peu de tests end-to-end, lents mais réalistes, au sommet.","title":"Pyramide des tests"},{"content":"Le problème #Au démarrage d\u0026rsquo;un projet, tout va vite : un contrôleur, un peu de logique, une requête SQL, et ça marche. Les semaines passent, les fonctionnalités s\u0026rsquo;accumulent. Puis vient le jour où il faut exposer la même logique via une API mobile, changer de fournisseur de paiement, ou simplement écrire un test qui ne dépende pas d\u0026rsquo;une vraie base de données — et on découvre que tout est emmêlé. La moindre modification oblige à toucher dix fichiers, casse des tests qu\u0026rsquo;on croyait sans rapport, et se déploie avec appréhension.\nCe n\u0026rsquo;est pas une question de volume de code, mais de la façon dont les décisions ont été prises au départ. Certaines décisions sont faciles à revenir en arrière — renommer une variable, changer une boucle for en foreach. D\u0026rsquo;autres sont extrêmement coûteuses à défaire — le mode de communication entre deux services, le choix d\u0026rsquo;un modèle de données partagé entre plusieurs équipes. L\u0026rsquo;architecture logicielle s\u0026rsquo;occupe précisément de ces dernières.\nL\u0026rsquo;idée générale #L\u0026rsquo;architecture logicielle, c\u0026rsquo;est l\u0026rsquo;ensemble des décisions structurelles qui déterminent comment les parties d\u0026rsquo;un système collaborent, et qui sont difficiles — donc coûteuses — à changer une fois prises.\nCette définition permet de tracer une frontière utile entre deux notions souvent confondues :\nArchitecture : des décisions qui engagent le système sur la durée — synchrone ou asynchrone entre deux services, monolithe ou services séparés, modèle de données partagé ou isolé, dépendances entre couches. Design (au sens design de code) : des décisions locales et réversibles à faible coût — le nom d\u0026rsquo;une méthode, l\u0026rsquo;organisation interne d\u0026rsquo;une classe, le choix entre deux structures de données équivalentes. Une deuxième distinction, tout aussi utile, sépare les contraintes des choix. Les contraintes sont subies : un système legacy qu\u0026rsquo;on ne peut pas réécrire du jour au lendemain, une réglementation, un budget, une infrastructure imposée. Les choix, eux, sont décidés par l\u0026rsquo;équipe : comment découper les responsabilités, quels patterns adopter, comment les modules communiquent entre eux. Une bonne architecture ne prétend pas éliminer les contraintes — elle les rend visibles et isole les choix pour qu\u0026rsquo;ils puissent évoluer indépendamment d\u0026rsquo;elles.\nArchitecturer un système, ce n\u0026rsquo;est donc pas viser la perfection : c\u0026rsquo;est accepter d\u0026rsquo;être un peu plus lent à construire au départ pour rester rapide à faire évoluer ensuite.\nAnalogie du quotidien #C\u0026rsquo;est la même logique que la construction d\u0026rsquo;une maison. Les fondations, l\u0026rsquo;emplacement des murs porteurs, le tracé de la plomberie et de l\u0026rsquo;électricité dans les murs — ce sont des décisions d\u0026rsquo;architecture. Une fois les murs coulés, déplacer une salle de bain d\u0026rsquo;un bout à l\u0026rsquo;autre de la maison coûte une fortune et immobilise le chantier des semaines.\nÀ l\u0026rsquo;inverse, la couleur de peinture, le choix du mobilier, la disposition des meubles dans le salon — c\u0026rsquo;est du design d\u0026rsquo;intérieur. Ça se change en un week-end, sans toucher à la structure. Personne ne convoque un architecte pour repeindre un mur, mais personne ne déplace une colonne porteuse sans y réfléchir à deux fois. La différence n\u0026rsquo;est pas la difficulté technique du geste : c\u0026rsquo;est le coût de se tromper.\nDiagramme # flowchart TD A[Décision à prendre] --\u003e B{Coûteuse à changer plus tard ?} B --\u003e|Oui, très coûteuse| C[Architecture] B --\u003e|Non, facilement réversible| D[Design / détail d'implémentation] C --\u003e C1[\"Ex : sync vs async entre deux services\"] C --\u003e C2[\"Ex : modèle de données partagé ou isolé\"] D --\u003e D1[\"Ex : nom d'une méthode\"] D --\u003e D2[\"Ex : boucle for vs forEach\"] Exemple concret #Un exemple simple suffit à voir la différence. Voici un contrôleur qui, sans que personne l\u0026rsquo;ait vraiment décidé, vient de prendre une décision architecturale :\nfinal class SouscriptionController { public function souscrire(Requete $requete): void { $stripe = new \\Stripe\\StripeClient(getenv(\u0026#39;STRIPE_KEY\u0026#39;)); $stripe-\u0026gt;charges-\u0026gt;create([ \u0026#39;amount\u0026#39; =\u0026gt; $requete-\u0026gt;montant(), \u0026#39;currency\u0026#39; =\u0026gt; \u0026#39;eur\u0026#39;, ]); } } Rien de visiblement grave ici — le code fonctionne. Mais le contrôleur dépend directement du SDK Stripe. Six mois plus tard, quand il faut ajouter PayPal comme moyen de paiement, chaque appel à Stripe dispersé dans le code doit être retrouvé, et le contrôleur risque de finir avec un if ($fournisseur === 'stripe') qui grossira à chaque nouveau moyen de paiement ajouté.\nCe n\u0026rsquo;est pas une erreur de syntaxe ni un bug : c\u0026rsquo;est une décision architecturale prise sans le savoir, au moment où le SDK Stripe a été appelé directement plutôt que derrière une abstraction. L\u0026rsquo;article sur le couplage fort et faible détaille précisément comment corriger ce genre de décision avant qu\u0026rsquo;elle ne devienne coûteuse.\nQuand se poser la question de l\u0026rsquo;architecture ? # Dès qu\u0026rsquo;une décision impliquerait de réécrire une bonne partie du système si elle s\u0026rsquo;avérait mauvaise. Quand plusieurs canaux ou équipes doivent partager la même logique métier (API, CLI, worker asynchrone). Quand le projet est amené à vivre plusieurs années, avec des changements technologiques prévisibles. Sur un prototype jetable ou un projet à durée de vie très courte, une architecture élaborée est souvent un gaspillage de temps : mieux vaut rester simple et accepter de tout réécrire si le produit rencontre son marché. Points importants # L\u0026rsquo;architecture n\u0026rsquo;est pas un ensemble de diagrammes UML figés en début de projet — c\u0026rsquo;est un ensemble de décisions vivantes, revues au fil de l\u0026rsquo;eau. Le bon réflexe avant une décision structurante : se demander « si on se trompe, combien ça coûte de revenir en arrière ? » Les contraintes ne se négocient pas (legacy, régulation, budget) ; les choix, eux, se documentent et se remettent en question. Les articles suivants de cette catégorie — Clean Architecture, architecture hexagonale, DDD, SOLID — sont des réponses concrètes à cette même question : comment organiser un système pour qu\u0026rsquo;il reste compréhensible et évolutif dans le temps. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/architecture-logicielle/quest-ce-quune-architecture-logicielle/","section":"Architecture logicielle","summary":"L\u0026rsquo;architecture logicielle, ce n\u0026rsquo;est pas un diagramme ou un framework : c\u0026rsquo;est l\u0026rsquo;ensemble des décisions structurelles qui coûtent cher à changer une fois prises. Comprendre cette notion, c\u0026rsquo;est apprendre à distinguer ce qui est difficile à défaire de ce qui ne l\u0026rsquo;est pas.","title":"Qu'est-ce qu'une architecture logicielle ?"},{"content":"Le problème #Avant le cloud, faire tourner une application signifiait acheter des serveurs physiques : les commander, attendre leur livraison (parfois plusieurs semaines), les installer dans un datacenter, les configurer, les maintenir, prévoir leur remplacement tous les quelques années. Il fallait aussi dimensionner cette infrastructure pour le pic de trafic le plus élevé imaginable — un serveur pour Noël qui reste à 5 % d\u0026rsquo;utilisation le reste de l\u0026rsquo;année, payé et entretenu quand même.\nCe modèle pose deux problèmes symétriques. Sous-dimensionner, et le site s\u0026rsquo;effondre au premier pic de trafic. Sur-dimensionner, et l\u0026rsquo;entreprise paie pour du matériel qui reste inutilisé la majeure partie du temps. Dans les deux cas, l\u0026rsquo;investissement est fait à l\u0026rsquo;avance, avant même de savoir si le produit va rencontrer son public.\nL\u0026rsquo;idée générale #Le cloud computing consiste à louer des ressources informatiques — machines virtuelles, stockage, bases de données, réseau — chez un fournisseur (AWS, Azure, Google Cloud, OVH\u0026hellip;) plutôt que de les posséder. Ces ressources sont accessibles via une API ou une console web, provisionnées en quelques minutes, et facturées à l\u0026rsquo;usage réel : à la seconde de calcul, au gigaoctet stocké, au gigaoctet transféré.\nTrois caractéristiques distinguent vraiment le cloud d\u0026rsquo;un simple hébergement dédié :\nLe libre-service à la demande : on crée une machine virtuelle sans appeler personne, via une simple requête API. L\u0026rsquo;élasticité : les ressources peuvent grossir ou rétrécir automatiquement selon la charge, en quelques minutes plutôt qu\u0026rsquo;en quelques semaines. La facturation à l\u0026rsquo;usage : on paie ce qu\u0026rsquo;on consomme, pas une capacité réservée à l\u0026rsquo;avance \u0026ldquo;au cas où\u0026rdquo;. Le cloud ne change pas la nature du travail (il faut toujours du calcul, du stockage, du réseau) : il change le modèle économique et la vitesse de provisionnement de ces ressources.\nAnalogie du quotidien #Le cloud, c\u0026rsquo;est comme la différence entre acheter une voiture et prendre un service d\u0026rsquo;autopartage. Acheter une voiture (le serveur physique) demande un investissement important à l\u0026rsquo;avance, l\u0026rsquo;entretien est à votre charge, et la voiture reste immobilisée dans un parking la majorité du temps. Avec l\u0026rsquo;autopartage (le cloud), vous réservez une voiture uniquement quand vous en avez besoin, vous ne payez que le temps d\u0026rsquo;utilisation, et vous pouvez réserver un utilitaire un jour et une petite citadine le lendemain selon vos besoins réels.\nLe trajet est le même dans les deux cas — se déplacer d\u0026rsquo;un point A à un point B — mais la flexibilité et la structure de coût changent radicalement.\nDiagramme # flowchart LR subgraph Avant[\"Avant le cloud\"] A1[Achat de serveurs] --\u003e A2[Installation datacenter] A2 --\u003e A3[Dimensionné pour le pic] A3 --\u003e A4[Coût fixe, sous-utilisé] end subgraph Apres[\"Avec le cloud\"] B1[Requête API] --\u003e B2[Ressource provisionnée en minutes] B2 --\u003e B3[Ajustement automatique à la charge] B3 --\u003e B4[Facturation à l'usage réel] end Exemple de code #Provisionner une machine virtuelle sur AWS via l\u0026rsquo;interface en ligne de commande illustre bien le libre-service à la demande : une seule commande, pas de bon de commande ni de livraison physique.\n# Créer une instance EC2 (machine virtuelle) à la demande aws ec2 run-instances \\ --image-id ami-0c94855ba95c71c99 \\ --instance-type t3.micro \\ --count 1 \\ --key-name ma-cle-ssh \\ --tag-specifications \u0026#39;ResourceType=instance,Tags=[{Key=Name,Value=serveur-web-01}]\u0026#39; # Arrêter (et donc ne plus payer le calcul) dès qu\u0026#39;elle n\u0026#39;est plus utile aws ec2 stop-instances --instance-ids i-0abcd1234efgh5678 La ressource existe en quelques secondes, et sa facturation s\u0026rsquo;arrête dès qu\u0026rsquo;elle est stoppée — impossible avec un serveur physique acheté.\nQuand utiliser le cloud ? # Quand la charge de l\u0026rsquo;application est difficile à prévoir à l\u0026rsquo;avance (startup, lancement produit, pics saisonniers). Quand l\u0026rsquo;entreprise veut limiter l\u0026rsquo;investissement initial et transformer un coût fixe en coût variable. Quand l\u0026rsquo;équipe veut déployer rapidement dans plusieurs régions géographiques sans construire de datacenter localement. Le cloud est moins pertinent pour une charge parfaitement stable et prévisible sur le long terme, où un serveur dédié ou on-premise peut revenir moins cher à volume constant. Points importants # Le cloud n\u0026rsquo;élimine pas la responsabilité de l\u0026rsquo;ingénieur : il déplace la question de \u0026ldquo;comment acheter du matériel\u0026rdquo; vers \u0026ldquo;comment dimensionner et sécuriser des ressources à la demande\u0026rdquo;. Trois grands modèles de service existent au-dessus de cette infrastructure de base : IaaS, PaaS et SaaS, qui déterminent la part de responsabilité laissée au fournisseur (voir l\u0026rsquo;article dédié). Un piège classique est le coût \u0026ldquo;silencieux\u0026rdquo; du cloud : des ressources oubliées mais jamais éteintes continuent à être facturées, contrairement à un serveur physique qu\u0026rsquo;on voit physiquement tourner. La bascule vers le cloud est aussi culturelle : elle pousse vers l\u0026rsquo;automatisation (Infrastructure as Code, CI/CD) parce que les ressources sont éphémères et recréées à la volée plutôt que configurées à la main une fois pour toutes. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/cloud-devops/quest-ce-que-le-cloud/","section":"Cloud \u0026 DevOps","summary":"Le cloud computing, c\u0026rsquo;est louer de la puissance de calcul, du stockage et des services réseau à la demande plutôt que d\u0026rsquo;acheter et de gérer ses propres serveurs. On passe d\u0026rsquo;un investissement fixe à une dépense variable, ajustable en quelques clics.","title":"Qu'est-ce que le Cloud ?"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/redis/","section":"Tags","summary":"","title":"Redis"},{"content":"Le problème #Une base de données qui tourne sur un seul serveur a deux limites structurelles. D\u0026rsquo;abord la disponibilité : si ce serveur tombe (panne matérielle, coupure réseau, maintenance), l\u0026rsquo;application entière devient indisponible jusqu\u0026rsquo;à sa remise en route. Ensuite la scalabilité en lecture : toutes les requêtes, qu\u0026rsquo;elles viennent de mille ou d\u0026rsquo;un million d\u0026rsquo;utilisateurs, sollicitent le même serveur unique, qui finit par saturer.\nLa réplication répond à ces deux problèmes en maintenant des copies synchronisées de la base sur plusieurs serveurs.\nL\u0026rsquo;idée générale #La réplication duplique les données d\u0026rsquo;un serveur maître (ou primaire) vers un ou plusieurs serveurs répliques (ou esclaves, secondaires). Deux axes structurent les choix de conception :\nTopologie :\nMaître-esclave : un seul serveur accepte les écritures (le maître), les répliques ne servent que les lectures. Simple à raisonner, mais le maître reste un point de défaillance unique pour l\u0026rsquo;écriture. Maître-maître : plusieurs serveurs acceptent les écritures et se répliquent mutuellement. Plus résilient pour l\u0026rsquo;écriture, mais expose à des conflits si la même donnée est modifiée simultanément sur deux nœuds différents. Mode de synchronisation :\nSynchrone : le maître attend la confirmation d\u0026rsquo;au moins une réplique avant de valider l\u0026rsquo;écriture — cohérence forte, latence plus élevée. Asynchrone : le maître valide immédiatement, la réplique se met à jour \u0026ldquo;un peu après\u0026rdquo; — écriture rapide, mais risque de perdre les toutes dernières écritures si le maître tombe avant d\u0026rsquo;avoir propagé les changements. Analogie du quotidien #La réplication fonctionne comme un secrétariat central avec des antennes régionales. Le secrétariat central (le maître) est le seul habilité à enregistrer officiellement un nouveau dossier. Les antennes régionales (les répliques) reçoivent une copie de chaque dossier pour pouvoir répondre aux demandes de consultation locales, sans que chaque citoyen n\u0026rsquo;ait à se déplacer jusqu\u0026rsquo;au siège central pour une simple lecture.\nEn mode synchrone, le secrétariat central attend la confirmation qu\u0026rsquo;au moins une antenne a bien reçu le dossier avant de le considérer comme officiellement enregistré — plus lent, mais aucune antenne ne se retrouve jamais en décalage avec un dossier qui a été confirmé. En mode asynchrone, le secrétariat central enregistre puis transmet la copie \u0026ldquo;quand il peut\u0026rdquo; — plus rapide au guichet, mais si un incendie détruit le siège central juste après un enregistrement non encore transmis, ce dossier est perdu pour de bon.\nDiagramme # sequenceDiagram participant App as Application participant M as Maître participant R1 as Réplique 1 participant R2 as Réplique 2 App-\u003e\u003eM: INSERT INTO commandes (...) alt Réplication synchrone M-\u003e\u003eR1: Propager l'écriture R1--\u003e\u003eM: Confirmation reçue M--\u003e\u003eApp: Écriture validée (COMMIT) M--\u003e\u003eR2: Propager l'écriture (asynchrone en complément) else Réplication asynchrone M--\u003e\u003eApp: Écriture validée (COMMIT) immédiatement M--\u003e\u003eR1: Propager l'écriture (un peu après) M--\u003e\u003eR2: Propager l'écriture (un peu après) end App-\u003e\u003eR1: SELECT * FROM commandes (lecture répartie) R1--\u003e\u003eApp: Résultat Exemple de code #-- Sur le maître PostgreSQL : autoriser un slot de réplication pour une réplique SELECT * FROM pg_create_physical_replication_slot(\u0026#39;replica_1\u0026#39;); -- Vérifier l\u0026#39;état de la réplication depuis le maître SELECT client_addr, state, sync_state FROM pg_stat_replication; -- Côté réplique, une requête en lecture seule fonctionne normalement SELECT id, nom FROM clients WHERE actif = true; -- Une tentative d\u0026#39;écriture sur une réplique en lecture seule échoue explicitement INSERT INTO clients (nom) VALUES (\u0026#39;Nouveau client\u0026#39;); -- ERROR: cannot execute INSERT in a read-only transaction // Exemple applicatif : router les écritures vers le maître, les lectures vers une réplique DataSource masterDataSource = DataSourceFactory.forHost(\u0026#34;db-master.internal\u0026#34;); DataSource replicaDataSource = DataSourceFactory.forHost(\u0026#34;db-replica.internal\u0026#34;); public void creerCommande(Commande commande) { try (Connection conn = masterDataSource.getConnection()) { // toutes les écritures passent par le maître // ... } } public List\u0026lt;Commande\u0026gt; listerCommandes(int clientId) { try (Connection conn = replicaDataSource.getConnection()) { // les lectures peuvent être réparties sur les répliques // ... } } Quand utiliser la réplication ? # Pour la haute disponibilité : en cas de panne du maître, une réplique peut être promue rapidement pour reprendre le service (bascule ou \u0026ldquo;failover\u0026rdquo;). Pour la scalabilité en lecture : répartir les requêtes SELECT sur plusieurs répliques quand elles dominent largement le trafic par rapport aux écritures. Pour isoler des charges de travail spécifiques : exécuter des rapports lourds ou des exports sur une réplique dédiée, sans ralentir le trafic transactionnel sur le maître. Moins utile — voire risquée sans réflexion — pour scalabiliser les écritures : la réplication maître-esclave classique ne résout pas ce problème ; il faut alors envisager du sharding ou une topologie multi-maître avec gestion de conflits. Points importants # La réplication n\u0026rsquo;est pas une sauvegarde : une erreur applicative (suppression accidentelle) se propage aux répliques presque aussi vite qu\u0026rsquo;aux données originales. La réplication asynchrone introduit un délai (replication lag) : une lecture juste après une écriture sur une réplique peut encore renvoyer l\u0026rsquo;ancienne valeur — un point de vigilance classique pour des flux du type \u0026ldquo;créer puis relire immédiatement\u0026rdquo;. Le maître-maître résout la disponibilité en écriture, mais impose une stratégie de résolution de conflits (dernier écrivain gagne, résolution applicative, ou usage d\u0026rsquo;identifiants distribués) qui ajoute une réelle complexité. En entretien, savoir expliquer le compromis cohérence/latence entre synchrone et asynchrone est souvent plus valorisé que de connaître par cœur la configuration exacte d\u0026rsquo;un moteur précis. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/bases-de-donnees/replication/","section":"Bases de données","summary":"La réplication duplique les données d\u0026rsquo;une base sur plusieurs serveurs, pour survivre à une panne et répartir la charge de lecture. Le choix entre réplication synchrone et asynchrone, et entre maître-esclave et maître-maître, détermine le compromis entre cohérence et disponibilité.","title":"Réplication"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/scalabilite/","section":"Tags","summary":"","title":"Scalabilite"},{"content":"Le problème #Une application qui fonctionne parfaitement avec 100 utilisateurs simultanés peut s\u0026rsquo;effondrer à 10 000, non pas parce que le code est mal écrit, mais parce qu\u0026rsquo;aucun système n\u0026rsquo;absorbe une charge infinie avec des ressources fixes. La question n\u0026rsquo;est jamais \u0026ldquo;est-ce que ça va tenir la charge ?\u0026rdquo; mais \u0026ldquo;jusqu\u0026rsquo;à quel point, et que se passe-t-il ensuite ?\u0026rdquo;\nLe piège classique est de répondre à ce problème en achetant une machine toujours plus grosse, jusqu\u0026rsquo;à atteindre une limite physique et budgétaire — le plus gros serveur du marché a un plafond, et il coûte de plus en plus cher à mesure qu\u0026rsquo;on s\u0026rsquo;en approche. L\u0026rsquo;autre piège est de multiplier les instances d\u0026rsquo;une application qui n\u0026rsquo;a pas été conçue pour ça, découvrant après coup que deux instances se marchent dessus parce qu\u0026rsquo;elles stockent chacune un état local incompatible avec l\u0026rsquo;autre.\nL\u0026rsquo;idée générale #La scalabilité est la capacité d\u0026rsquo;un système à absorber une charge croissante en ajoutant des ressources, sans dégradation disproportionnée des performances. Elle se joue sur deux axes :\nScalabilité verticale (scale up) : augmenter la puissance d\u0026rsquo;une seule machine (plus de CPU, plus de RAM). Simple, mais plafonnée par le matériel disponible, et l\u0026rsquo;ensemble du système repose toujours sur un point de défaillance unique. Scalabilité horizontale (scale out) : ajouter davantage de machines identiques qui se partagent la charge, généralement derrière un load balancer qui répartit les requêtes entre elles. Cette approche est en théorie sans plafond, et améliore aussi la résilience (la panne d\u0026rsquo;une instance n\u0026rsquo;arrête pas le service), mais elle exige une contrainte de conception forte : l\u0026rsquo;application doit être stateless. Un service stateless ne conserve aucune donnée propre à un utilisateur en mémoire locale entre deux requêtes — cet état est externalisé dans une base de données partagée, un cache distribué (Redis), ou transporté par le client lui-même (JWT). C\u0026rsquo;est cette externalisation qui permet à n\u0026rsquo;importe quelle instance de traiter n\u0026rsquo;importe quelle requête, condition indispensable pour répartir efficacement la charge. À l\u0026rsquo;inverse, un service stateful (une base de données, par exemple) ne peut pas être dupliqué aussi simplement : il exige des mécanismes spécifiques (réplication, sharding) pour scaler horizontalement sans perdre la cohérence des données.\nAnalogie du quotidien #La scalabilité, c\u0026rsquo;est comme gérer l\u0026rsquo;affluence dans un restaurant. La scalabilité verticale, c\u0026rsquo;est agrandir la cuisine et embaucher un chef encore plus rapide — efficace jusqu\u0026rsquo;à un certain point, mais une seule cuisine reste une seule cuisine, avec un plafond physique de plats qu\u0026rsquo;elle peut produire par heure. La scalabilité horizontale, c\u0026rsquo;est ouvrir plusieurs restaurants identiques en ville, avec un système de réservation central qui répartit les clients vers le restaurant le moins occupé — ce système de réservation, c\u0026rsquo;est le load balancer.\nPour que ça fonctionne, chaque restaurant doit pouvoir servir n\u0026rsquo;importe quel client de la même façon, sans dépendre d\u0026rsquo;un souvenir propre à un restaurant précis (\u0026ldquo;le serveur du restaurant 2 se souvient de votre commande habituelle, mais pas ceux des autres restaurants\u0026rdquo;) — c\u0026rsquo;est l\u0026rsquo;équivalent du stateless : aucune information critique ne doit être coincée dans un seul endroit pour que le système fonctionne.\nDiagramme # flowchart TD Client[Client] --\u003e LB[Load Balancer] LB --\u003e|health check OK| I1[Instance API 1 — stateless] LB --\u003e|health check OK| I2[Instance API 2 — stateless] LB -.health check échoué, exclue du routage.-\u003e I3[Instance API 3 — en panne] I1 --\u003e Cache[(Cache partagé Redis)] I2 --\u003e Cache I1 --\u003e DB[(Base de données)] I2 --\u003e DB DB --\u003e Replica[(Réplique en lecture)] Exemple de code ## hpa.yaml — Horizontal Pod Autoscaler Kubernetes : # scale horizontal automatique selon la charge CPU apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: mon-api-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: mon-api minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 65 // Exemple d\u0026#39;API stateless : aucun état de session en mémoire locale. // L\u0026#39;état utilisateur vit dans le token, pas dans le processus. app.get(\u0026#34;/api/panier\u0026#34;, authenticateJWT, async (req, res) =\u0026gt; { // req.user vient du JWT décodé, pas d\u0026#39;une session stockée localement const panier = await redisClient.get(`panier:${req.user.id}`); res.json(JSON.parse(panier ?? \u0026#34;[]\u0026#34;)); }); // N\u0026#39;importe quelle instance de cette API peut traiter cette requête : // rien n\u0026#39;est stocké en mémoire locale du processus Node.js. Quand se soucier de scalabilité ? # Dès la conception d\u0026rsquo;un service destiné à grandir : rendre une API stateless dès le départ coûte peu, alors que retirer un état local accumulé après coup demande souvent une réécriture douloureuse. Quand le trafic devient imprévisible ou saisonnier : la scalabilité horizontale automatique absorbe les pics sans surdimensionner en permanence. La scalabilité verticale reste une solution valable et plus simple pour des charges modérées et prévisibles, ou comme premier palier avant d\u0026rsquo;investir dans une architecture horizontale plus complexe. Pour une base de données, la question se pose différemment : la scalabilité horizontale (sharding, réplication) est nettement plus complexe à mettre en œuvre que pour un service stateless, et n\u0026rsquo;est justifiée que lorsque la scalabilité verticale atteint clairement ses limites. Points importants # Scaler horizontalement un composant stateful sans réflexion (une base de données, par exemple) peut introduire des incohérences de données bien plus graves que le problème de performance qu\u0026rsquo;on cherchait à résoudre. Le goulot d\u0026rsquo;étranglement d\u0026rsquo;un système se déplace : scaler l\u0026rsquo;API ne sert à rien si la base de données derrière elle sature avant — la scalabilité se pense pour l\u0026rsquo;ensemble de la chaîne, pas composant par composant isolément. Un load balancer doit inclure des health checks : router du trafic vers une instance déjà en panne aggrave l\u0026rsquo;incident au lieu de le contourner. La mise en cache (Redis, CDN) est souvent le levier de scalabilité le plus rentable : elle réduit la charge réelle sur les composants les plus coûteux à scaler, avant même de devoir ajouter des instances supplémentaires. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/cloud-devops/scalabilite/","section":"Cloud \u0026 DevOps","summary":"La scalabilité est la capacité d\u0026rsquo;un système à absorber une charge croissante sans dégrader ses performances. Elle se joue sur deux axes — vertical et horizontal — et repose sur un principe clé : rendre les services sans état pour pouvoir en multiplier les instances.","title":"Scalabilité"},{"content":"Le problème #Une classe InscriptionService qui valide les données d\u0026rsquo;un formulaire, calcule un prix, enregistre l\u0026rsquo;utilisateur en base de données et envoie un e-mail de bienvenue fonctionne, au début. Puis le service marketing demande de changer le contenu de l\u0026rsquo;e-mail : il faut rouvrir cette classe. Le service comptabilité change la règle de calcul du prix : il faut rouvrir la même classe. Une nouvelle règle de validation arrive : encore la même classe.\nChaque équipe, pour des raisons complètement différentes, finit par toucher au même fichier — avec le risque, à chaque modification, de casser une fonctionnalité sans rapport avec le changement demandé. Ce n\u0026rsquo;est pas un problème de taille de fichier : c\u0026rsquo;est un problème de responsabilités mélangées dans une seule unité de code.\nL\u0026rsquo;idée générale #La séparation des responsabilités consiste à isoler chaque raison de changer dans sa propre unité de code. Une responsabilité n\u0026rsquo;est pas une méthode ni une ligne : c\u0026rsquo;est un motif de changement — une source qui, si elle évolue, force à modifier ce bout de code.\nLe principe le plus connu qui formalise cette idée à l\u0026rsquo;échelle d\u0026rsquo;une classe est le Single Responsibility Principle (le S de SOLID, détaillé dans l\u0026rsquo;article SOLID expliqué simplement) : une classe ne devrait avoir qu\u0026rsquo;une seule raison de changer.\nMais l\u0026rsquo;idée dépasse largement l\u0026rsquo;échelle d\u0026rsquo;une classe :\nÀ l\u0026rsquo;échelle d\u0026rsquo;une classe : InscriptionService devrait être scindé en un validateur, un calculateur de prix, un repository et un service de notification. À l\u0026rsquo;échelle d\u0026rsquo;un module : un module « facturation » ne devrait pas contenir de logique de gestion des stocks — ce sont deux raisons de changer différentes, portées par des équipes différentes. À l\u0026rsquo;échelle d\u0026rsquo;une couche applicative : une couche de présentation (contrôleurs, vues) ne devrait pas contenir de règles métier, et une couche métier ne devrait pas connaître les détails d\u0026rsquo;un format HTTP. Séparer les responsabilités, c\u0026rsquo;est donc organiser le code pour qu\u0026rsquo;un changement métier reste localisé — un seul endroit à modifier, un seul test à revoir, un seul risque de régression, au lieu d\u0026rsquo;une onde de choc qui traverse tout le système.\nAnalogie du quotidien #Un restaurant sépare naturellement ses responsabilités : le cuisinier prépare les plats, le serveur prend les commandes et sert la salle, le caissier encaisse le paiement. Si le restaurant change de fournisseur de légumes, seule la cuisine est concernée. Si le restaurant change son système de caisse, seul le caissier est concerné. Le serveur continue son travail sans rien changer à sa façon de faire.\nImaginez à l\u0026rsquo;inverse un restaurant où une seule personne cuisine, sert, encaisse et gère les fournisseurs. Le jour où il faut changer le système de caisse, toute l\u0026rsquo;activité s\u0026rsquo;arrête, parce qu\u0026rsquo;une seule personne porte toutes les responsabilités à la fois. Séparer les rôles, ce n\u0026rsquo;est pas juste une question d\u0026rsquo;organisation humaine — c\u0026rsquo;est ce qui permet à chaque partie du système de changer sans bloquer les autres.\nDiagramme # flowchart TD subgraph Avant[\"Avant : une seule classe, plusieurs responsabilités\"] A[InscriptionService] --\u003e|valide| A1[Règles de validation] A --\u003e|calcule| A2[Calcul du prix] A --\u003e|persiste| A3[Accès base de données] A --\u003e|notifie| A4[Envoi d'e-mail] end subgraph Apres[\"Après : une responsabilité par unité\"] V[Validateur] --\u003e C[CalculateurPrix] C --\u003e R[UtilisateurRepository] R --\u003e N[ServiceNotification] end Exemple de code #PHP #// --- AVANT : une classe, quatre responsabilités --- final class InscriptionService { public function inscrire(array $donnees): void { // Responsabilité 1 : validation if (empty($donnees[\u0026#39;email\u0026#39;]) || !str_contains($donnees[\u0026#39;email\u0026#39;], \u0026#39;@\u0026#39;)) { throw new InvalidArgumentException(\u0026#39;E-mail invalide\u0026#39;); } // Responsabilité 2 : calcul métier $prix = $donnees[\u0026#39;abonnementPremium\u0026#39;] ? 29.90 : 0.0; // Responsabilité 3 : persistance $pdo = new PDO(\u0026#39;mysql:host=localhost;dbname=app\u0026#39;, \u0026#39;root\u0026#39;, \u0026#39;\u0026#39;); $stmt = $pdo-\u0026gt;prepare(\u0026#39;INSERT INTO utilisateurs (email, prix) VALUES (?, ?)\u0026#39;); $stmt-\u0026gt;execute([$donnees[\u0026#39;email\u0026#39;], $prix]); // Responsabilité 4 : notification mail($donnees[\u0026#39;email\u0026#39;], \u0026#39;Bienvenue\u0026#39;, \u0026#39;Merci pour votre inscription !\u0026#39;); } } // --- APRÈS : une responsabilité par classe --- final class ValidateurInscription { public function valider(array $donnees): void { if (empty($donnees[\u0026#39;email\u0026#39;]) || !str_contains($donnees[\u0026#39;email\u0026#39;], \u0026#39;@\u0026#39;)) { throw new InvalidArgumentException(\u0026#39;E-mail invalide\u0026#39;); } } } final class CalculateurPrix { public function calculer(bool $premium): float { return $premium ? 29.90 : 0.0; } } interface UtilisateurRepository { public function sauvegarder(string $email, float $prix): void; } interface ServiceNotification { public function envoyerBienvenue(string $email): void; } final class InscriptionService { public function __construct( private ValidateurInscription $validateur, private CalculateurPrix $calculateur, private UtilisateurRepository $repository, private ServiceNotification $notifications, ) {} public function inscrire(array $donnees): void { $this-\u0026gt;validateur-\u0026gt;valider($donnees); $prix = $this-\u0026gt;calculateur-\u0026gt;calculer($donnees[\u0026#39;abonnementPremium\u0026#39;]); $this-\u0026gt;repository-\u0026gt;sauvegarder($donnees[\u0026#39;email\u0026#39;], $prix); $this-\u0026gt;notifications-\u0026gt;envoyerBienvenue($donnees[\u0026#39;email\u0026#39;]); } } Java #// --- APRÈS : une responsabilité par classe --- public class ValidateurInscription { public void valider(InscriptionRequest donnees) { if (donnees.email() == null || !donnees.email().contains(\u0026#34;@\u0026#34;)) { throw new IllegalArgumentException(\u0026#34;E-mail invalide\u0026#34;); } } } public class CalculateurPrix { public double calculer(boolean premium) { return premium ? 29.90 : 0.0; } } public interface UtilisateurRepository { void sauvegarder(String email, double prix); } public interface ServiceNotification { void envoyerBienvenue(String email); } public class InscriptionService { private final ValidateurInscription validateur; private final CalculateurPrix calculateur; private final UtilisateurRepository repository; private final ServiceNotification notifications; public InscriptionService(ValidateurInscription validateur, CalculateurPrix calculateur, UtilisateurRepository repository, ServiceNotification notifications) { this.validateur = validateur; this.calculateur = calculateur; this.repository = repository; this.notifications = notifications; } public void inscrire(InscriptionRequest donnees) { validateur.valider(donnees); double prix = calculateur.calculer(donnees.premium()); repository.sauvegarder(donnees.email(), prix); notifications.envoyerBienvenue(donnees.email()); } } Quand appliquer la séparation des responsabilités ? # Dès qu\u0026rsquo;une classe ou un module mélange des règles métier, de la validation, de la persistance et de la notification dans le même bloc de code. Quand des équipes différentes doivent modifier le même fichier pour des raisons complètement indépendantes. Quand vous voulez tester une règle métier isolément, sans dépendre d\u0026rsquo;une base de données ou d\u0026rsquo;un service d\u0026rsquo;envoi d\u0026rsquo;e-mails. À doser avec discernement sur un petit script ou un prototype : séparer à l\u0026rsquo;excès une logique très simple en cinq classes de trois lignes chacune complique la lecture plus qu\u0026rsquo;elle ne l\u0026rsquo;aide. Points importants # Une responsabilité se définit par une raison de changer, pas par une taille de classe : une grande classe cohérente qui ne change que pour une seule raison respecte le principe. Le principe s\u0026rsquo;applique à toutes les échelles : classe, module, couche — le symptôme est le même à chaque niveau, un changement qui devrait être local se propage ailleurs. Le Single Responsibility Principle (S de SOLID) est la version la plus connue de ce principe, appliquée à l\u0026rsquo;échelle d\u0026rsquo;une classe. Trop séparer nuit aussi à la lisibilité : l\u0026rsquo;objectif est d\u0026rsquo;isoler les raisons de changer indépendantes, pas de minimiser artificiellement la taille du code. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/architecture-logicielle/separation-des-responsabilites/","section":"Architecture logicielle","summary":"Séparer les responsabilités, ce n\u0026rsquo;est pas seulement écrire des petites classes : c\u0026rsquo;est isoler chaque raison de changer dans sa propre unité, que ce soit une classe, un module ou une couche entière du système. Bien fait, un changement métier ne touche qu\u0026rsquo;un seul endroit du code.","title":"Séparation des responsabilités"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/solid/","section":"Tags","summary":"","title":"Solid"},{"content":"Le problème #SOLID est l\u0026rsquo;un des acronymes les plus cités en entretien technique et l\u0026rsquo;un des moins bien maîtrisés en pratique. Beaucoup de développeurs savent réciter les cinq lettres sans être capables de dire, face à un bout de code réel, lequel des cinq principes est violé — ou pire, appliquent les cinq principes partout, systématiquement, produisant du code sur-architecturé pour des besoins qui n\u0026rsquo;en demandaient pas tant.\nSOLID n\u0026rsquo;est pas une checklist à cocher automatiquement. C\u0026rsquo;est un ensemble de signaux d\u0026rsquo;alerte, à reconnaître dans du code qui commence à devenir rigide, difficile à tester ou risqué à modifier.\nL\u0026rsquo;idée générale #SOLID regroupe cinq principes de conception orientée objet, formulés (et popularisés) par Robert C. Martin. Chacun d\u0026rsquo;entre eux, à sa manière, poursuit le même objectif : permettre au code d\u0026rsquo;évoluer sans se casser.\nS — Single Responsibility Principle : une classe ne devrait avoir qu\u0026rsquo;une seule raison de changer. O — Open/Closed Principle : le code doit être ouvert à l\u0026rsquo;extension, mais fermé à la modification — on ajoute un comportement, on ne réécrit pas l\u0026rsquo;existant. L — Liskov Substitution Principle : une sous-classe doit pouvoir remplacer sa classe mère partout où celle-ci est utilisée, sans changer le comportement attendu par l\u0026rsquo;appelant. I — Interface Segregation Principle : mieux vaut plusieurs interfaces petites et spécifiques qu\u0026rsquo;une seule grosse interface qui force à implémenter des méthodes inutiles. D — Dependency Inversion Principle : les modules dépendent d\u0026rsquo;abstractions (interfaces), jamais d\u0026rsquo;implémentations concrètes — le sens des dépendances technique s\u0026rsquo;inverse vers le métier. Ces cinq principes ne s\u0026rsquo;appliquent pas isolément : dans la pratique, ils se renforcent mutuellement. Une classe respectant le SRP est plus facile à étendre sans la modifier (OCP) ; une bonne inversion de dépendance (DIP) s\u0026rsquo;appuie souvent sur des interfaces fines (ISP).\nAnalogie du quotidien #Pensez à un système d\u0026rsquo;étagères modulaires, comme ceux qu\u0026rsquo;on assemble soi-même : chaque module (tiroir, porte, tablette) a un rôle précis et se fixe selon un standard commun.\nS : chaque module a une seule fonction — un tiroir range, une porte ferme. On ne conçoit pas un module « tiroir-porte-éclairage » qui fait tout à moitié bien. O : on ajoute un nouveau module (une nouvelle tablette) sans redémonter ni redessiner les modules déjà en place. L : n\u0026rsquo;importe quel tiroir standard de la gamme se fixe à la place d\u0026rsquo;un autre tiroir standard, sans que le meuble ait besoin d\u0026rsquo;être modifié. I : le kit de vis fourni pour fixer une porte ne vous oblige pas à utiliser aussi le kit prévu pour des roulettes que vous n\u0026rsquo;installerez jamais — chaque kit reste spécifique à son usage. D : le plan de montage général ne mentionne jamais une marque de vis précise, seulement un standard de fixation — n\u0026rsquo;importe quel fournisseur respectant ce standard peut être utilisé. Diagramme # flowchart TD SOLID((SOLID)) SOLID --\u003e S[\"S — Single Responsibility Une seule raison de changer\"] SOLID --\u003e O[\"O — Open/Closed Étendre sans modifier\"] SOLID --\u003e L[\"L — Liskov Substitution Une sous-classe respecte le contrat\"] SOLID --\u003e I[\"I — Interface Segregation Des interfaces petites et spécifiques\"] SOLID --\u003e D[\"D — Dependency Inversion Dépendre d'abstractions, pas d'implémentations\"] Exemple de code #Un exemple court par principe, en PHP, suffit à voir concrètement ce que chaque lettre change dans le code.\nS — Single Responsibility #// Violation : une classe calcule ET exporte en PDF — deux raisons de changer. final class Facture { public function calculerTotal(array $lignes): float { /* ... */ } public function exporterEnPdf(array $lignes): string { /* ... */ } } // Respect du principe : une responsabilité par classe. final class CalculateurFacture { public function calculerTotal(array $lignes): float { /* ... */ } } final class ExporteurFacturePdf { public function exporter(array $lignes): string { /* ... */ } } O — Open/Closed #// Violation : chaque nouveau type de client oblige à modifier cette méthode. final class CalculateurRemise { public function calculer(string $typeClient, float $montant): float { return match ($typeClient) { \u0026#39;standard\u0026#39; =\u0026gt; $montant * 0.0, \u0026#39;premium\u0026#39; =\u0026gt; $montant * 0.1, \u0026#39;vip\u0026#39; =\u0026gt; $montant * 0.2, }; } } // Respect du principe : ajouter un type de client = ajouter une classe, sans toucher à l\u0026#39;existant. interface RemiseStrategy { public function calculer(float $montant): float; } final class RemiseVip implements RemiseStrategy { public function calculer(float $montant): float { return $montant * 0.2; } } L — Liskov Substitution #// Violation : Pingouin hérite d\u0026#39;Oiseau mais trahit le contrat voler(). class Oiseau { public function voler(): void { /* ... */ } } class Pingouin extends Oiseau { public function voler(): void { throw new LogicException(\u0026#39;Un pingouin ne vole pas\u0026#39;); } } // Tout code qui manipule un Oiseau générique et appelle voler() casse avec un Pingouin. // Respect du principe : séparer ce qui n\u0026#39;est pas garanti pour tous les sous-types. interface Oiseau {} interface CapableDeVoler { public function voler(): void; } class Pingouin implements Oiseau {} class Aigle implements Oiseau, CapableDeVoler { public function voler(): void { /* ... */ } } I — Interface Segregation #// Violation : une interface trop large force des implémentations inutiles. interface Travailleur { public function travailler(): void; public function manger(): void; } final class RobotOuvrier implements Travailleur { public function travailler(): void { /* ... */ } public function manger(): void { throw new LogicException(\u0026#39;Un robot ne mange pas\u0026#39;); // méthode forcée, inutile } } // Respect du principe : deux interfaces distinctes, chacune implémentée si besoin. interface Travaillable { public function travailler(): void; } interface Mangeable { public function manger(): void; } final class RobotOuvrier implements Travaillable { public function travailler(): void { /* ... */ } } D — Dependency Inversion #// Violation : le service dépend directement d\u0026#39;une implémentation concrète. final class NotificationService { public function notifier(string $message): void { (new EmailSender())-\u0026gt;envoyer($message); } } // Respect du principe : le service dépend d\u0026#39;une abstraction, injectée de l\u0026#39;extérieur. interface Sender { public function envoyer(string $message): void; } final class NotificationService { public function __construct(private Sender $sender) {} public function notifier(string $message): void { $this-\u0026gt;sender-\u0026gt;envoyer($message); } } Quand appliquer SOLID ? # Sur du code métier amené à évoluer souvent — nouvelles règles, nouveaux types de clients, nouveaux moyens de paiement. Quand un switch ou une cascade de if/else grossit à chaque nouvelle fonctionnalité (signal typique d\u0026rsquo;une violation d\u0026rsquo;Open/Closed). Quand une classe doit être modifiée pour des raisons complètement indépendantes par des équipes différentes (signal typique d\u0026rsquo;une violation de Single Responsibility). Avec retenue sur du code simple et stable : appliquer les cinq lettres systématiquement à un script utilitaire de vingt lignes produit plus d\u0026rsquo;indirection que de valeur. Points importants # SOLID est un outil de diagnostic, pas un objectif en soi : le but n\u0026rsquo;est pas d\u0026rsquo;avoir « du code SOLID », mais du code qui reste facile à faire évoluer. S et I se ressemblent : S sépare les responsabilités d\u0026rsquo;une classe, I sépare les contrats d\u0026rsquo;une interface — deux granularités du même réflexe. D (Dependency Inversion) est le principe le plus structurant à l\u0026rsquo;échelle d\u0026rsquo;une architecture entière : c\u0026rsquo;est lui qui rend possibles la Clean Architecture, l\u0026rsquo;architecture hexagonale et le couplage faible en général. Les cinq principes se retrouvent partout ailleurs sur ce site : le couplage fort vs faible illustre le D, la séparation des responsabilités approfondit le S, et le Strategy Pattern est une application directe de l\u0026rsquo;Open/Closed. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/architecture-logicielle/solid-explique-simplement/","section":"Architecture logicielle","summary":"SOLID est un acronyme regroupant cinq principes de conception orientée objet formulés par Robert C. Martin : Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation et Dependency Inversion. Ensemble, ils visent un seul objectif : pouvoir faire évoluer le code sans le casser.","title":"SOLID expliqué simplement"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/sql/","section":"Tags","summary":"","title":"Sql"},{"content":"Le problème #Sans langage de requête, interroger une base reviendrait à écrire du code procédural pour parcourir les données une par une : ouvrir la table, boucler sur chaque ligne, vérifier une condition, accumuler un résultat. Ce code serait long, répétitif, et surtout figé sur une stratégie d\u0026rsquo;exécution précise — impossible pour le moteur d\u0026rsquo;optimiser quoi que ce soit puisque tout est déjà écrit pas à pas.\nSQL (Structured Query Language) résout ce problème en changeant de paradigme : on décrit le résultat voulu, pas la façon de l\u0026rsquo;obtenir. Le moteur analyse la requête et choisit lui-même la meilleure stratégie (quel index utiliser, dans quel ordre joindre les tables) pour y arriver.\nL\u0026rsquo;idée générale #Une poignée de mots-clés couvrent l\u0026rsquo;essentiel des besoins :\nSELECT : quelles colonnes (ou quelles expressions) on veut récupérer. FROM : de quelle(s) table(s) on part. JOIN : comment combiner plusieurs tables via leurs clés. WHERE : quelles lignes garder, avant tout regroupement. GROUP BY : comment regrouper les lignes pour calculer des agrégats (COUNT, SUM, AVG\u0026hellip;). HAVING : quels groupes garder, après le regroupement. ORDER BY / LIMIT : dans quel ordre trier et combien de résultats retourner. Un point souvent mal compris : l\u0026rsquo;ordre dans lequel on écrit ces clauses n\u0026rsquo;est pas l\u0026rsquo;ordre dans lequel le moteur les exécute. Il évalue d\u0026rsquo;abord FROM/JOIN, puis WHERE, puis GROUP BY, puis HAVING, et seulement à la fin SELECT et ORDER BY. C\u0026rsquo;est pour ça que WHERE ne peut pas utiliser un alias défini dans SELECT, mais ORDER BY le peut.\nAnalogie du quotidien #Écrire une requête SQL, c\u0026rsquo;est comme remplir une fiche de commande précise pour un traiteur, plutôt que d\u0026rsquo;aller vous-même en cuisine. Vous ne dites pas \u0026ldquo;prends la casserole, allume le feu, verse l\u0026rsquo;huile\u0026rdquo; (le comment) — vous dites \u0026ldquo;je veux une salade de fruits, sans banane, pour 4 personnes\u0026rdquo; (le quoi). C\u0026rsquo;est le chef (le moteur SQL) qui décide comment organiser sa cuisine pour vous livrer ce résultat le plus efficacement possible.\nDiagramme # flowchart LR A[\"FROM / JOIN\\n(quelles tables, comment liées)\"] --\u003e B[\"WHERE\\n(quelles lignes garder)\"] B --\u003e C[\"GROUP BY\\n(comment regrouper)\"] C --\u003e D[\"HAVING\\n(quels groupes garder)\"] D --\u003e E[\"SELECT\\n(quelles colonnes retourner)\"] E --\u003e F[\"ORDER BY / LIMIT\\n(tri et pagination)\"] Exemple de code #-- Nombre de commandes par client, uniquement les clients actifs, -- avec au moins 3 commandes, triés du plus actif au moins actif SELECT cl.nom, COUNT(c.id) AS nb_commandes FROM clients cl JOIN commandes c ON c.client_id = cl.id WHERE cl.actif = true GROUP BY cl.nom HAVING COUNT(c.id) \u0026gt;= 3 ORDER BY nb_commandes DESC LIMIT 10; -- DELETE : supprime les commandes annulées, filtrable et journalisé DELETE FROM commandes WHERE statut = \u0026#39;annulee\u0026#39;; -- TRUNCATE : vide toute la table de logs, réinitialise l\u0026#39;id, plus rapide TRUNCATE TABLE logs_acces; // Exemple avec JDBC : exécuter une requête paramétrée String sql = \u0026#34;\u0026#34;\u0026#34; SELECT nom, COUNT(*) AS nb_commandes FROM clients cl JOIN commandes c ON c.client_id = cl.id WHERE cl.actif = ? GROUP BY nom HAVING COUNT(*) \u0026gt;= ? \u0026#34;\u0026#34;\u0026#34;; try (PreparedStatement stmt = connection.prepareStatement(sql)) { stmt.setBoolean(1, true); stmt.setInt(2, 3); ResultSet rs = stmt.executeQuery(); while (rs.next()) { System.out.println(rs.getString(\u0026#34;nom\u0026#34;) + \u0026#34; : \u0026#34; + rs.getInt(\u0026#34;nb_commandes\u0026#34;)); } } Quand utiliser SQL ? # Dès qu\u0026rsquo;on interroge une base relationnelle : c\u0026rsquo;est le langage standard, portable (avec quelques variations) entre PostgreSQL, MySQL, SQL Server, Oracle\u0026hellip; Pour exprimer des agrégations et des croisements de données (statistiques, tableaux de bord) sans écrire de boucle applicative. Pour garantir l\u0026rsquo;intégrité des opérations d\u0026rsquo;écriture (INSERT, UPDATE, DELETE) directement au niveau de la base, avec les garanties transactionnelles du moteur. Moins adapté pour des traitements très itératifs ou procéduraux complexes — dans ce cas, on combine SQL avec du code applicatif ou des procédures stockées. Points importants # Toujours utiliser des requêtes préparées (paramètres liés) plutôt que de concaténer des chaînes : c\u0026rsquo;est la première ligne de défense contre l\u0026rsquo;injection SQL. DELETE peut être ciblé et annulé (transaction), TRUNCATE est une opération lourde et généralement irréversible — ne jamais les confondre en production. GROUP BY sans agrégat n\u0026rsquo;a pas de sens : chaque colonne du SELECT doit soit être dans le GROUP BY, soit être enveloppée dans une fonction d\u0026rsquo;agrégation. Un JOIN mal indexé peut transformer une requête simple en scan complet de deux tables : comprendre les jointures va de pair avec comprendre les index (voir l\u0026rsquo;article dédié). ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/bases-de-donnees/sql-explique-simplement/","section":"Bases de données","summary":"SQL est le langage universel pour interroger une base relationnelle. Avec une poignée de mots-clés — SELECT, WHERE, JOIN, GROUP BY — on peut exprimer des questions complexes sur les données sans jamais écrire de boucle.","title":"SQL expliqué simplement"},{"content":"Le problème #Une équipe démarre un projet avec une base relationnelle par habitude, puis se heurte à un schéma qui change sans arrêt (chaque type de produit a des attributs différents), ou à un volume de données qui dépasse ce qu\u0026rsquo;un seul serveur relationnel peut absorber. À l\u0026rsquo;inverse, une équipe qui choisit une base NoSQL par effet de mode se retrouve parfois à réimplémenter des jointures et des contraintes d\u0026rsquo;intégrité à la main, dans le code applicatif, alors qu\u0026rsquo;un moteur relationnel les aurait gérées nativement.\nLe vrai problème n\u0026rsquo;est pas \u0026ldquo;SQL ou NoSQL\u0026rdquo; comme un choix idéologique, mais de comprendre quel modèle de données et quelle charge votre application impose, pour choisir l\u0026rsquo;outil qui y répond le mieux.\nL\u0026rsquo;idée générale #Les bases relationnelles (SQL) structurent les données en tables avec un schéma strict, des relations explicites via des clés, et des garanties transactionnelles fortes (ACID). Elles excellent quand les données ont une structure stable et des relations complexes à interroger.\nLes bases NoSQL (Not Only SQL) regroupent plusieurs familles, chacune optimisée pour un usage différent :\nDocument (MongoDB, CouchDB) : stocke des documents JSON/BSON flexibles, adaptés à des structures hétérogènes ou imbriquées (catalogue produits avec attributs variables). Clé-valeur (Redis, DynamoDB) : associe une clé à une valeur, extrêmement rapide pour des accès simples (cache, sessions, compteurs). Colonne (Cassandra, HBase) : optimisée pour écrire et lire de très gros volumes distribués sur de nombreux serveurs (séries temporelles, logs). Graphe (Neo4j) : modélise explicitement des relations complexes entre entités (réseaux sociaux, recommandations, détection de fraude). La plupart des bases NoSQL sacrifient une partie de la cohérence stricte ou des relations natives au profit de la flexibilité de schéma et de la scalabilité horizontale (distribuer les données sur plusieurs serveurs).\nAnalogie du quotidien #Choisir entre SQL et NoSQL, c\u0026rsquo;est comme choisir entre un classeur administratif et une boîte à archives libres. Le classeur (SQL) impose une structure : chaque document a sa place précise, dans un ordre défini, avec des références croisées fiables — parfait pour des dossiers officiels où la cohérence compte. La boîte à archives (NoSQL) accepte n\u0026rsquo;importe quel format de document, s\u0026rsquo;organise plus librement, et peut être dupliquée facilement dans plusieurs entrepôts pour être accessible partout rapidement — parfait pour du stockage massif et hétérogène où la rigidité du classeur deviendrait un obstacle.\nAucun des deux n\u0026rsquo;est \u0026ldquo;meilleur\u0026rdquo; dans l\u0026rsquo;absolu : le classeur est inadapté pour stocker des objets de formes très variées, la boîte à archives est inadaptée pour retrouver instantanément un dossier précis avec des règles strictes de cohérence.\nDiagramme # flowchart TD Q{\"Structure des données\\nstable et relationnelle ?\"} Q --\u003e|Oui| Q2{\"Cohérence forte\\nrequise (ACID) ?\"} Q --\u003e|Non, schéma flexible| Doc[\"Base Document\\n(MongoDB, CouchDB)\"] Q2 --\u003e|Oui| SQL[\"Base relationnelle\\n(PostgreSQL, MySQL)\"] Q2 --\u003e|Non, accès simple\\net très rapide| KV[\"Clé-valeur\\n(Redis, DynamoDB)\"] Q --\u003e|Non, volumes massifs\\ndistribués| Col[\"Colonne\\n(Cassandra, HBase)\"] Q --\u003e|Non, relations\\ntrès complexes| Graph[\"Graphe\\n(Neo4j)\"] Exemple de code #-- Modèle relationnel : structure fixe, jointure pour relier les entités SELECT cl.nom, c.date_commande FROM clients cl JOIN commandes c ON c.client_id = cl.id WHERE cl.id = 42; // Modèle document (MongoDB) : structure flexible, données imbriquées dans un seul document db.commandes.find({ clientId: 42 }); // Chaque document peut avoir une structure légèrement différente sans migration de schéma db.produits.insertOne({ nom: \u0026#34;Clavier mécanique\u0026#34;, attributs: { switchType: \u0026#34;brown\u0026#34;, retroeclairage: true } }); db.produits.insertOne({ nom: \u0026#34;Livre\u0026#34;, attributs: { auteur: \u0026#34;Victor Hugo\u0026#34;, pages: 400 } }); // Exemple clé-valeur avec Redis : accès direct par clé, sans schéma ni jointure $redis = new Redis(); $redis-\u0026gt;connect(\u0026#39;127.0.0.1\u0026#39;, 6379); $redis-\u0026gt;set(\u0026#39;session:42\u0026#39;, json_encode([\u0026#39;userId\u0026#39; =\u0026gt; 42, \u0026#39;role\u0026#39; =\u0026gt; \u0026#39;admin\u0026#39;]), 3600); $session = json_decode($redis-\u0026gt;get(\u0026#39;session:42\u0026#39;), true); Quand choisir SQL plutôt que NoSQL (et inversement) ? # Choisir SQL quand les données ont une structure stable, des relations complexes à interroger, et que la cohérence transactionnelle est prioritaire (finance, gestion de commandes, comptabilité). Choisir NoSQL document quand le schéma varie beaucoup d\u0026rsquo;une entité à l\u0026rsquo;autre ou évolue fréquemment sans vouloir gérer de migrations lourdes. Choisir NoSQL clé-valeur pour du cache, des sessions, des compteurs : accès très rapides, structure minimale. Choisir NoSQL colonne ou graphe pour des besoins spécifiques : très gros volumes distribués, ou relations complexes au cœur du produit (recommandations, réseaux). Dans de nombreux systèmes réels, les deux coexistent : une base relationnelle pour le cœur métier, une base NoSQL en complément pour le cache ou un besoin précis. Points importants # NoSQL n\u0026rsquo;est pas \u0026ldquo;plus moderne\u0026rdquo; ou \u0026ldquo;plus rapide\u0026rdquo; par nature : c\u0026rsquo;est un compromis différent, pas une évolution universelle du relationnel. Beaucoup de bases NoSQL ont ajouté des garanties transactionnelles ou du typage au fil du temps, et beaucoup de bases relationnelles ont ajouté du support JSON flexible — la frontière est moins nette qu\u0026rsquo;il y a dix ans. Le théorème CAP (Cohérence, Disponibilité, tolérance au Partitionnement) explique pourquoi les bases distribuées doivent souvent sacrifier une garantie pour privilégier les deux autres — c\u0026rsquo;est une clé de lecture utile pour comparer les moteurs NoSQL entre eux. Le bon réflexe en entretien : ne jamais répondre \u0026ldquo;SQL\u0026rdquo; ou \u0026ldquo;NoSQL\u0026rdquo; sans expliquer le compromis qui justifie le choix pour le cas précis posé. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/bases-de-donnees/sql-vs-nosql/","section":"Bases de données","summary":"SQL et NoSQL ne s\u0026rsquo;opposent pas frontalement : ce sont deux familles d\u0026rsquo;outils avec des compromis différents entre cohérence, flexibilité de schéma et scalabilité. Le bon choix dépend de la forme des données et des contraintes de charge, pas d\u0026rsquo;une mode technique.","title":"SQL vs NoSQL"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/","section":"Tags","summary":"","title":"Tags"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/tdd/","section":"Tags","summary":"","title":"Tdd"},{"content":"Le problème #L\u0026rsquo;approche naturelle consiste à écrire d\u0026rsquo;abord le code, puis, si le temps le permet, à ajouter des tests après coup. Dans la pratique, \u0026ldquo;si le temps le permet\u0026rdquo; devient souvent \u0026ldquo;jamais\u0026rdquo; : une fois la fonctionnalité livrée, l\u0026rsquo;attention passe au ticket suivant. Et quand des tests sont écrits après coup, ils ont tendance à confirmer ce que le code fait déjà — y compris ses bugs — plutôt qu\u0026rsquo;à vérifier ce qu\u0026rsquo;il devrait faire.\nIl manque un mécanisme qui force à clarifier le comportement attendu avant d\u0026rsquo;écrire la première ligne d\u0026rsquo;implémentation, et qui donne un signal objectif du moment où le travail est terminé.\nL\u0026rsquo;idée générale #Le TDD (Test-Driven Development) inverse l\u0026rsquo;ordre habituel : on écrit un test qui décrit le comportement attendu, on le voit échouer (puisque le code n\u0026rsquo;existe pas encore), puis on écrit le code minimal nécessaire pour le faire passer. Ce cycle se répète en trois étapes courtes :\nRed : écrire un test qui décrit un comportement pas encore implémenté. Il échoue — c\u0026rsquo;est normal et attendu, ça prouve que le test teste bien quelque chose. Green : écrire le code le plus simple possible pour faire passer ce test, sans se soucier de l\u0026rsquo;élégance. L\u0026rsquo;objectif est uniquement que la suite de tests soit au vert. Refactor : une fois le test au vert, nettoyer le code (noms, duplication, structure) sans changer son comportement. Les tests déjà écrits garantissent que rien ne casse pendant cette étape. On répète ensuite ce cycle pour le comportement suivant. Chaque itération est volontairement petite — quelques minutes — ce qui garde un rythme rapide et un feedback immédiat.\nAnalogie du quotidien #Le TDD, c\u0026rsquo;est comme se fixer un itinéraire avant de démarrer un trajet plutôt que de rouler et vérifier après coup si on est arrivé au bon endroit. On définit d\u0026rsquo;abord la destination précise (le test : \u0026ldquo;je veux arriver ici\u0026rdquo;), puis on choisit le chemin le plus direct pour y parvenir (le code minimal), puis, une fois arrivé, on peut ajuster l\u0026rsquo;itinéraire pour éviter les bouchons la prochaine fois (le refactoring) — sans jamais changer la destination elle-même.\nRouler d\u0026rsquo;abord et vérifier ensuite si on est au bon endroit fonctionne parfois, mais on ne le sait qu\u0026rsquo;après coup, une fois le trajet terminé.\nDiagramme # flowchart TD A[Red: écrire un test qui échoue] --\u003e B[Green: écrire le code minimal pour le faire passer] B --\u003e C[Refactor: améliorer la structure, tests toujours au vert] C --\u003e|nouveau comportement à ajouter| A C --\u003e|fonctionnalité terminée| D[Fin du cycle] Exemple de code #PHP #// Étape 1 - RED : on écrit le test avant que la classe existe use PHPUnit\\Framework\\TestCase; final class PanierTest extends TestCase { public function testUnPanierVideATotalZero(): void { $panier = new Panier(); $this-\u0026gt;assertSame(0.0, $panier-\u0026gt;total()); } } // À ce stade, `Panier` n\u0026#39;existe pas : le test échoue (erreur de classe manquante). // Étape 2 - GREEN : le code minimal pour faire passer le test final class Panier { public function total(): float { return 0.0; } } // Le test passe. C\u0026#39;est volontairement simpliste : on n\u0026#39;anticipe rien de plus // que ce que le test actuel exige. // On ajoute un nouveau test pour pousser le comportement plus loin (nouveau cycle RED) public function testAjouterUnArticleAugmenteLeTotal(): void { $panier = new Panier(); $panier-\u0026gt;ajouter(new Article(\u0026#39;Livre\u0026#39;, 15.0)); $this-\u0026gt;assertSame(15.0, $panier-\u0026gt;total()); } // Étape GREEN suivante : le code évolue pour répondre au nouveau test, // sans casser le précédent (`testUnPanierVideATotalZero` reste vert) final class Panier { /** @var Article[] */ private array $articles = []; public function ajouter(Article $article): void { $this-\u0026gt;articles[] = $article; } public function total(): float { return array_sum(array_map(fn (Article $a) =\u0026gt; $a-\u0026gt;prix(), $this-\u0026gt;articles)); } } JavaScript #// Étape 1 - RED : le test décrit un comportement qui n\u0026#39;existe pas encore const { Panier } = require(\u0026#34;./panier\u0026#34;); describe(\u0026#34;Panier\u0026#34;, () =\u0026gt; { it(\u0026#34;a un total de 0 quand il est vide\u0026#34;, () =\u0026gt; { const panier = new Panier(); expect(panier.total()).toBe(0); }); }); // À ce stade, `./panier` n\u0026#39;existe même pas : le test échoue. // Étape 2 - GREEN : le code minimal pour faire passer le test class Panier { total() { return 0; } } module.exports = { Panier }; // Nouveau cycle RED : on pousse le comportement plus loin it(\u0026#34;additionne le prix des articles ajoutés\u0026#34;, () =\u0026gt; { const panier = new Panier(); panier.ajouter({ nom: \u0026#34;Livre\u0026#34;, prix: 15 }); expect(panier.total()).toBe(15); }); // GREEN suivant : le code évolue sans casser le test précédent class Panier { constructor() { this.articles = []; } ajouter(article) { this.articles.push(article); } total() { return this.articles.reduce((somme, a) =\u0026gt; somme + a.prix, 0); } } module.exports = { Panier }; Quand utiliser le TDD ? # Sur une logique métier dont le comportement attendu est connu à l\u0026rsquo;avance : le TDD excelle à clarifier des règles précises (calculs, validations, transitions d\u0026rsquo;état). Quand on veut garantir une bonne couverture de tests par construction, plutôt que d\u0026rsquo;espérer la rattraper après coup. Sur un code destiné à évoluer souvent : le filet de tests produit par le TDD facilite chaque refactoring futur. Moins adapté à l\u0026rsquo;exploration pure (prototypage d\u0026rsquo;UI, expérimentation d\u0026rsquo;API externe méconnue) où le comportement attendu n\u0026rsquo;est pas encore clair — dans ce cas, explorer d\u0026rsquo;abord, puis stabiliser au TDD une fois la direction connue, est souvent plus efficace. Points importants # L\u0026rsquo;étape Red n\u0026rsquo;est pas optionnelle : voir le test échouer avant d\u0026rsquo;écrire le code confirme que le test teste réellement quelque chose de nouveau. Le code écrit à l\u0026rsquo;étape Green doit rester minimal — céder à la tentation de généraliser trop tôt casse le rythme court du cycle et introduit du code non couvert par un test. Le Refactor s\u0026rsquo;appuie entièrement sur les tests déjà verts : sans eux, refactorer devient aussi risqué qu\u0026rsquo;avant le TDD. Le TDD est une discipline de conception autant qu\u0026rsquo;une pratique de test : le vrai bénéfice est la clarté qu\u0026rsquo;il impose sur le comportement attendu, avant même d\u0026rsquo;écrire l\u0026rsquo;implémentation. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/tests/tdd-explique-simplement/","section":"Tests","summary":"Le TDD (Test-Driven Development) consiste à écrire un test qui échoue avant d\u0026rsquo;écrire le code qui le fait passer, puis à améliorer la structure une fois le comportement validé. Ce cycle court — Red, Green, Refactor — guide la conception au lieu de la vérifier après coup.","title":"TDD expliqué simplement"},{"content":"Le problème #Une équipe teste une fonction calculerRemise() en isolation : elle passe toujours. Pourtant, en production, la remise ne s\u0026rsquo;applique jamais correctement — parce que la requête SQL qui récupère le statut fidélité du client renvoie la mauvaise colonne. Le test unitaire n\u0026rsquo;a rien vu, car il ne testait que la logique de calcul, pas la façon dont les composants s\u0026rsquo;assemblent réellement.\nÀ l\u0026rsquo;inverse, une suite composée uniquement de tests qui démarrent une vraie base de données et font de vrais appels HTTP devient si lente que personne ne la lance avant de pousser son code, et les retours d\u0026rsquo;échec arrivent des heures plus tard, noyés dans le bruit d\u0026rsquo;une pipeline CI. Le périmètre du test — ce qu\u0026rsquo;il isole et ce qu\u0026rsquo;il laisse réel — n\u0026rsquo;est jamais un détail anodin.\nL\u0026rsquo;idée générale #Un test unitaire vérifie une seule unité de code (une fonction, une méthode, une classe) en l\u0026rsquo;isolant complètement de ses dépendances. Toute dépendance externe — base de données, appel réseau, système de fichiers, horloge — est remplacée par un mock ou un stub. Le test unitaire répond à une question précise : cette logique, prise isolément, se comporte-t-elle comme prévu ?\nUn test d\u0026rsquo;intégration vérifie que plusieurs composants réels collaborent correctement entre eux. Il peut faire appel à une vraie base de données (souvent une base de test dédiée), un vrai client HTTP contre un serveur de test, ou plusieurs classes réelles qui interagissent sans mock. Il répond à une autre question : ces composants, assemblés ensemble, produisent-ils le résultat attendu ?\nLes deux ne s\u0026rsquo;opposent pas — ils couvrent des risques différents :\nTest unitaire Test d\u0026rsquo;intégration Périmètre Une seule unité Plusieurs composants réels Dépendances Mockées / stubbées Réelles (ou proches du réel) Vitesse Très rapide (millisecondes) Plus lent (connexions réelles) Détecte Erreurs de logique isolée Erreurs d\u0026rsquo;assemblage, de configuration, de contrat Débogage en cas d\u0026rsquo;échec Précis, cause évidente Plus large, cause à identifier parmi plusieurs composants Analogie du quotidien #Tester unitairement, c\u0026rsquo;est vérifier chaque pièce détachée d\u0026rsquo;un meuble en kit séparément : la vis tient-elle la charge prévue, la planche a-t-elle les bonnes dimensions. Tester en intégration, c\u0026rsquo;est monter réellement deux ou trois pièces ensemble et vérifier qu\u0026rsquo;elles s\u0026rsquo;assemblent sans jeu ni porte-à-faux.\nVérifier chaque vis isolément est rapide et précis, mais ne garantit pas que le meuble entier tiendra debout une fois monté — un plan de perçage mal aligné entre deux pièces, chacune parfaite individuellement, peut rendre l\u0026rsquo;assemblage impossible. C\u0026rsquo;est exactement ce qu\u0026rsquo;un test d\u0026rsquo;intégration détecte et qu\u0026rsquo;un test unitaire, par construction, ne peut pas voir.\nDiagramme # flowchart TB subgraph Unitaire[\"Test unitaire\"] direction LR U1[ServiceRemise] -.mock.-\u003e U2[[RepositoryClient - mocké]] end subgraph Integration[\"Test d'intégration\"] direction LR I1[ServiceRemise] --\u003e I2[RepositoryClient réel] I2 --\u003e I3[(Base de données de test)] end Exemple de code #PHP #// --- Code métier --- interface ClientRepository { public function estFidele(int $clientId): bool; } final class ServiceRemise { public function __construct(private ClientRepository $repository) {} public function calculer(int $clientId, float $montant): float { return $this-\u0026gt;repository-\u0026gt;estFidele($clientId) ? $montant * 0.9 : $montant; } } use PHPUnit\\Framework\\TestCase; // --- Test UNITAIRE : le repository est mocké, aucune vraie base de données --- final class ServiceRemiseUnitTest extends TestCase { public function testClientFideleObtientUneRemise(): void { $repository = $this-\u0026gt;createStub(ClientRepository::class); $repository-\u0026gt;method(\u0026#39;estFidele\u0026#39;)-\u0026gt;willReturn(true); $service = new ServiceRemise($repository); $this-\u0026gt;assertSame(90.0, $service-\u0026gt;calculer(clientId: 1, montant: 100.0)); } } // --- Test D\u0026#39;INTÉGRATION : le repository réel interroge une vraie base de test --- final class ServiceRemiseIntegrationTest extends TestCase { private PDO $pdo; protected function setUp(): void { $this-\u0026gt;pdo = new PDO(\u0026#39;sqlite::memory:\u0026#39;); $this-\u0026gt;pdo-\u0026gt;exec(\u0026#39;CREATE TABLE clients (id INTEGER, fidele INTEGER)\u0026#39;); $this-\u0026gt;pdo-\u0026gt;exec(\u0026#39;INSERT INTO clients VALUES (1, 1)\u0026#39;); } public function testCalculDeRemiseAvecUneVraieBaseDeDonnees(): void { $repository = new PdoClientRepository($this-\u0026gt;pdo); $service = new ServiceRemise($repository); $this-\u0026gt;assertSame(90.0, $service-\u0026gt;calculer(clientId: 1, montant: 100.0)); } } JavaScript #// --- Code métier --- class ServiceRemise { constructor(clientRepository) { this.clientRepository = clientRepository; } async calculer(clientId, montant) { const estFidele = await this.clientRepository.estFidele(clientId); return estFidele ? montant * 0.9 : montant; } } module.exports = { ServiceRemise }; // --- Test UNITAIRE : le repository est mocké avec Jest --- const { ServiceRemise } = require(\u0026#34;./serviceRemise\u0026#34;); test(\u0026#34;un client fidèle obtient une remise (unitaire, repository mocké)\u0026#34;, async () =\u0026gt; { const repositoryMocke = { estFidele: jest.fn().mockResolvedValue(true) }; const service = new ServiceRemise(repositoryMocke); const total = await service.calculer(1, 100); expect(total).toBe(90); }); // --- Test D\u0026#39;INTÉGRATION : appel réel à une base SQLite en mémoire --- const { ServiceRemise } = require(\u0026#34;./serviceRemise\u0026#34;); const { SqlClientRepository } = require(\u0026#34;./sqlClientRepository\u0026#34;); const { openTestDatabase } = require(\u0026#34;./testDb\u0026#34;); test(\u0026#34;un client fidèle obtient une remise (intégration, vraie base de données)\u0026#34;, async () =\u0026gt; { const db = await openTestDatabase(); await db.run(\u0026#34;INSERT INTO clients (id, fidele) VALUES (1, 1)\u0026#34;); const repository = new SqlClientRepository(db); const service = new ServiceRemise(repository); const total = await service.calculer(1, 100); expect(total).toBe(90); }); Quand utiliser l\u0026rsquo;un ou l\u0026rsquo;autre ? # Test unitaire : pour vérifier une logique métier isolée (calcul, validation, transformation) rapidement et souvent — à chaque sauvegarde ou en pré-commit. Test d\u0026rsquo;intégration : pour vérifier qu\u0026rsquo;une requête SQL est correcte, qu\u0026rsquo;un appel HTTP respecte le bon format, ou que plusieurs classes s\u0026rsquo;assemblent sans erreur de configuration. Une suite saine combine les deux : beaucoup de tests unitaires rapides pour la logique, un nombre plus restreint de tests d\u0026rsquo;intégration pour les points de jonction critiques. Évitez de mocker une dépendance dans un test qui a justement pour but de vérifier que cette dépendance fonctionne correctement en conditions réelles — ce serait un test d\u0026rsquo;intégration déguisé en test unitaire, qui ne teste plus rien d\u0026rsquo;utile. Points importants # Un test unitaire rapide (millisecondes) peut tourner par centaines à chaque sauvegarde de fichier ; un test d\u0026rsquo;intégration, plus lent, tourne plus souvent en pré-push ou en CI. Un échec de test unitaire pointe précisément vers la logique cassée ; un échec de test d\u0026rsquo;intégration demande d\u0026rsquo;investiguer parmi plusieurs composants pour trouver la cause. Le périmètre d\u0026rsquo;un test (ce qu\u0026rsquo;il mocke, ce qu\u0026rsquo;il laisse réel) doit toujours être un choix conscient, pas un hasard d\u0026rsquo;implémentation. Ni l\u0026rsquo;un ni l\u0026rsquo;autre ne remplace les tests end-to-end, qui vérifient le système complet du point de vue de l\u0026rsquo;utilisateur final — les trois niveaux forment la pyramide des tests. ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/tests/test-unitaire-vs-integration/","section":"Tests","summary":"Un test unitaire vérifie une seule unité de code en isolation, en général en mockant ses dépendances. Un test d\u0026rsquo;intégration vérifie que plusieurs composants réels — base de données, API, autres classes — collaborent correctement ensemble.","title":"Test unitaire vs intégration"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/tests/","section":"Tags","summary":"","title":"Tests"},{"content":"Écrire des tests n\u0026rsquo;est pas une corvée administrative : c\u0026rsquo;est un outil de conception. Ces fiches expliquent le raisonnement derrière le TDD, la BDD et les différents niveaux de tests.\n","date":null,"permalink":"https://www.mybearisyellow.me/tests/","section":"Tests","summary":"\u003cp\u003eÉcrire des tests n\u0026rsquo;est pas une corvée administrative : c\u0026rsquo;est un outil de conception. Ces fiches expliquent le raisonnement derrière le TDD, la BDD et les différents niveaux de tests.\u003c/p\u003e","title":"Tests"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/transactions/","section":"Tags","summary":"","title":"Transactions"},{"content":"Le problème #Imaginez un virement bancaire de 100 euros entre deux comptes : débiter le compte A, puis créditer le compte B. Ce sont deux opérations distinctes. Si le serveur plante juste après le débit et avant le crédit — panne électrique, crash applicatif, coupure réseau — le compte A a perdu 100 euros qui n\u0026rsquo;existent plus nulle part. La base est dans un état incohérent, et personne ne s\u0026rsquo;en aperçoit avant qu\u0026rsquo;un client ne s\u0026rsquo;en plaigne.\nCe problème existe dès qu\u0026rsquo;une opération métier nécessite plusieurs écritures liées. Sans garantie particulière, chaque écriture est indépendante et vulnérable à toute interruption entre les deux.\nL\u0026rsquo;idée générale #Une transaction regroupe plusieurs opérations en un seul bloc tout ou rien. Les propriétés ACID définissent les garanties que ce bloc doit respecter :\nAtomicité : soit toutes les opérations de la transaction réussissent, soit aucune n\u0026rsquo;est appliquée. Pas de virement à moitié fait. Cohérence : la transaction fait passer la base d\u0026rsquo;un état valide à un autre état valide, en respectant toutes les contraintes (clés étrangères, contraintes CHECK, unicité\u0026hellip;). Isolation : les transactions concurrentes ne voient pas les états intermédiaires les unes des autres — le niveau exact de cette garantie dépend du niveau d\u0026rsquo;isolation choisi (voir l\u0026rsquo;article dédié). Durabilité : une fois la transaction validée (COMMIT), les changements survivent à toute panne — ils sont écrits de façon persistante, généralement via un journal des transactions. Analogie du quotidien #Une transaction, c\u0026rsquo;est comme signer un contrat de vente immobilière chez le notaire. Le vendeur ne remet pas les clés avant d\u0026rsquo;avoir reçu l\u0026rsquo;argent, et l\u0026rsquo;acheteur ne paie pas avant d\u0026rsquo;avoir la garantie de recevoir les clés. Les deux actions — payer et transférer la propriété — sont liées : soit elles ont lieu ensemble, au même instant juridique (la signature), soit la vente n\u0026rsquo;a pas lieu du tout.\nIl n\u0026rsquo;existe pas d\u0026rsquo;état intermédiaire où l\u0026rsquo;acheteur a payé mais où le bien n\u0026rsquo;est pas encore transféré : le notaire (l\u0026rsquo;équivalent du moteur transactionnel) garantit que tout se valide d\u0026rsquo;un bloc, ou que rien ne se passe si un élément manque.\nDiagramme # sequenceDiagram participant App as Application participant DB as Base de données App-\u003e\u003eDB: BEGIN TRANSACTION App-\u003e\u003eDB: UPDATE comptes SET solde = solde - 100 WHERE id = 'A' DB--\u003e\u003eApp: OK App-\u003e\u003eDB: UPDATE comptes SET solde = solde + 100 WHERE id = 'B' alt Les deux opérations réussissent DB--\u003e\u003eApp: OK App-\u003e\u003eDB: COMMIT DB--\u003e\u003eApp: Transaction validée et durable else Une erreur survient (contrainte violée, panne...) DB--\u003e\u003eApp: Erreur App-\u003e\u003eDB: ROLLBACK DB--\u003e\u003eApp: Aucune modification appliquée end Exemple de code #BEGIN; UPDATE comptes SET solde = solde - 100 WHERE id = \u0026#39;A\u0026#39;; UPDATE comptes SET solde = solde + 100 WHERE id = \u0026#39;B\u0026#39;; -- Si le solde du compte A devient négatif, une contrainte CHECK -- peut faire échouer la transaction et déclencher un ROLLBACK automatique COMMIT; // Exemple avec JDBC : transaction explicite autour d\u0026#39;un virement Connection conn = dataSource.getConnection(); try { conn.setAutoCommit(false); try (PreparedStatement debit = conn.prepareStatement( \u0026#34;UPDATE comptes SET solde = solde - ? WHERE id = ?\u0026#34;)) { debit.setBigDecimal(1, montant); debit.setString(2, compteA); debit.executeUpdate(); } try (PreparedStatement credit = conn.prepareStatement( \u0026#34;UPDATE comptes SET solde = solde + ? WHERE id = ?\u0026#34;)) { credit.setBigDecimal(1, montant); credit.setString(2, compteB); credit.executeUpdate(); } conn.commit(); } catch (SQLException e) { conn.rollback(); // annule tout : le compte A n\u0026#39;a jamais réellement perdu son argent throw e; } finally { conn.setAutoCommit(true); conn.close(); } Quand utiliser une transaction ? # Dès qu\u0026rsquo;une opération métier nécessite plusieurs écritures liées qui doivent réussir ou échouer ensemble (virement, création de commande avec décrément de stock, inscription avec création de profil). Pour garantir qu\u0026rsquo;une contrainte métier n\u0026rsquo;est jamais violée temporairement, même en cas d\u0026rsquo;erreur applicative en plein milieu du traitement. Moins pertinent pour une écriture unique et isolée, où l\u0026rsquo;atomicité est déjà garantie nativement par le moteur sans bloc BEGIN/COMMIT explicite. À manier avec précaution sur des transactions longues : elles retiennent des verrous et peuvent bloquer d\u0026rsquo;autres opérations concurrentes. Points importants # Une transaction non terminée par COMMIT ou ROLLBACK explicite reste ouverte et retient des ressources — toujours s\u0026rsquo;assurer qu\u0026rsquo;elle se termine, y compris en cas d\u0026rsquo;exception (bloc finally). La Durabilité repose généralement sur un journal des transactions (write-ahead log) : les changements sont écrits dans ce journal avant d\u0026rsquo;être appliqués aux fichiers de données, ce qui permet de rejouer les transactions validées après un crash. ACID décrit des garanties, pas une implémentation unique : chaque moteur (PostgreSQL, MySQL/InnoDB\u0026hellip;) les respecte avec ses propres mécanismes internes. Isolation et Atomicité sont souvent confondues : l\u0026rsquo;Atomicité protège une transaction en elle-même, l\u0026rsquo;Isolation protège les transactions les unes des autres (détaillé dans l\u0026rsquo;article sur l\u0026rsquo;isolation transactionnelle). ","date":"10 juillet 2026","permalink":"https://www.mybearisyellow.me/bases-de-donnees/transactions-acid/","section":"Bases de données","summary":"Une transaction regroupe plusieurs opérations en un bloc tout-ou-rien. Les propriétés ACID — Atomicité, Cohérence, Isolation, Durabilité — garantissent qu\u0026rsquo;elle ne laisse jamais la base dans un état incohérent, même en cas de panne ou d\u0026rsquo;accès concurrent.","title":"Transactions ACID"},{"content":"","date":null,"permalink":"https://www.mybearisyellow.me/tags/unit-testing/","section":"Tags","summary":"","title":"Unit-Testing"},{"content":"Un entretien technique se prépare comme un examen : en connaissant les questions qui reviennent, et surtout en sachant les expliquer avec vos propres mots. Cette section rassemble les questions les plus fréquentes, classées par thème.\n","date":null,"permalink":"https://www.mybearisyellow.me/preparation-entretien/","section":"Préparation entretien","summary":"\u003cp\u003eUn entretien technique se prépare comme un examen : en connaissant les questions qui reviennent, et surtout en sachant les expliquer avec vos propres mots. Cette section rassemble les questions les plus fréquentes, classées par thème.\u003c/p\u003e","title":"Préparation entretien"},{"content":"Un entretien technique backend revient presque toujours sur les mêmes fondamentaux : principes de conception, bases de données, API, conteneurisation. Les questions ci-dessous sont classées par thème — cliquez sur une question pour voir la réponse, et suivez le lien vers l\u0026rsquo;article complet pour creuser le sujet.\n","date":"1 janvier 1","permalink":"https://www.mybearisyellow.me/preparation-entretien/questions-backend/","section":"Préparation entretien","summary":"Les questions qui reviennent le plus souvent dans un entretien technique backend — classées par thème, avec une réponse directe et un lien vers l\u0026rsquo;article complet pour aller plus loin.","title":"Questions Backend"}]