Vol. 1 · № 01 · Un atelier d'architectureAn architecture workshop

Dependency Inversion, ou la flèche qu'on retourneDependency Inversion, or the arrow you flip

Le code naïf fait pointer la règle métier vers le détail technique : la commande « connaît » SMTP. Le jour où SMTP change, le métier change. L'inversion ne déplace pas le code, elle retourne une flèche — et soudain le métier ne connaît plus personne.Naive code makes the business rule point to the technical detail: the order 'knows' SMTP. The day SMTP changes, the business changes. The inversion doesn't move code, it flips an arrow — and suddenly the business knows no one.

AudienceAudience
Dev à l'aise en code, qui veut raisonner architectureDev fluent in code, ready to reason about architecture
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026June 2026
≈ 18 min●○○○CouplageSOLIDTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

Une règle métier qui connaît un détail technique. Trace la flèche.A business rule that knows a technical detail. Trace the arrow.

Un OrderService doit notifier le client après une commande. La version qu'on écrit tous le premier jour marche très bien — et plante un clou qui coûtera cher. La règle métier, stable, qui décrit ce que fait l'entreprise, dépend maintenant d'un détail technique volatil : un client SMTP concret.An OrderService must notify the customer after an order. The version we all write on day one works fine — and drives a nail that'll cost dearly. The stable business rule, describing what the company does, now depends on a volatile technical detail: a concrete SMTP client.

Le code naïf — la flèche pointe dans le mauvais sensThe naive code — the arrow points the wrong way
class OrderService:
    def place(order):
        validate(order)
        save(order)
        EmailSender().send(order.customer_email, "Confirmée")  # ← ici
Qui dépend de qui ?Who depends on whom?
OrderService
le quoi · stablethe what · stable
→
dépend dedepends on
EmailSender
le comment · volatilthe how · volatile

Elle part du stable et pointe vers le volatil. Le jour où SMTP change, le métier change.It runs from stable to volatile. The day SMTP changes, the business changes.

Le réflexe du volumeThe volume's reflex

« Qui dépend de qui, et la flèche pointe-t-elle vers le stable ou vers le volatil ? » Le métier change rarement ; le canal de notification (SMTP → API tierce → file de messages) change souvent. On veut que le volatil dépende du stable — jamais l'inverse.'Who depends on whom, and does the arrow point toward the stable or the volatile?' The business changes rarely; the notification channel (SMTP → third-party API → message queue) changes often. We want the volatile to depend on the stable — never the reverse.

Pourquoi ce numéro ouvre la série.Why this issue opens the series.

Presque tous les patterns d'architecture sont des manières de gérer une dépendance : la casser, l'inverser, la médiatiser, ou l'assumer. Avant de parler de couches, de microservices ou d'event sourcing, il faut savoir lire le sens d'une flèche. C'est le réflexe-socle.Almost every architecture pattern is a way to manage a dependency: break it, invert it, mediate it, or accept it. Before talking layers, microservices, or event sourcing, you must read the direction of an arrow. That's the foundational reflex.

№ 02La douleurThe pain4 min

Le couplage direct ne coûte rien à l'écriture — tout au changement.Direct coupling costs nothing to write — everything to change.

Reprenons la flèche OrderService → EmailSender et regardons ce qu'elle déclenche. Le problème n'est pas la qualité d'EmailSender — il peut être impeccable. C'est la direction : le module qui porte la valeur est l'esclave du module qui porte un détail.Take the OrderService → EmailSender arrow again and watch what it triggers. The problem isn't EmailSender's quality — it can be flawless. It's the direction: the module that carries the value is enslaved to the module that carries a detail.

№ 1 · Le changement se propageChange propagates

Passer de SMTP à un provider HTTP oblige à ouvrir OrderService — un fichier métier — pour changer une ligne d'infra. Une raison de modifier le métier qui n'a rien à voir avec le métier.Switching from SMTP to an HTTP provider forces you to open OrderService — a business file — to change one line of infra. A reason to edit the business that has nothing to do with the business.

№ 2 · Le test devient lourdThe test gets heavy

Pour vérifier « une commande valide est sauvegardée », il faut un serveur mail joignable, sinon le test part en timeout. On teste la logique métier à travers la plomberie réseau.To check 'a valid order is saved', you need a reachable mail server, or the test times out. You test business logic through network plumbing.

№ 3 · La réutilisation est verrouilléeReuse is locked

Impossible d'utiliser OrderService dans un contexte qui notifie par SMS, ou pas du tout (import batch), sans le rouvrir.You can't use OrderService in a context that notifies by SMS, or not at all (batch import), without reopening it.

Le test révèle le couplageThe test reveals the coupling
def test_place_saves_order():
    OrderService().place(valid_order)   # ← part en timeout SMTP en CI
Le diagnostic.The diagnosis.

Le test est le détecteur de couplage le plus honnête qu'on ait : si tester place() exige un vrai serveur SMTP, c'est que la flèche est dans le mauvais sens. On a sous-traité la stabilité du cœur à la volatilité du bord. Le réflexe : puis-je faire dépendre le détail de l'abstraction, plutôt que l'inverse ?The test is the most honest coupling detector we have: if testing place() needs a real SMTP server, the arrow points the wrong way. You've subcontracted the core's stability to the edge's volatility. The reflex: can I make the detail depend on the abstraction, rather than the reverse?

№ 03L'inversionThe inversion5 min

On introduit une abstraction, et la flèche se retourne.Introduce an abstraction, and the arrow flips.

L'inversion ne déplace pas le code et ne change pas l'exécution : à l'exécution, OrderService appellera toujours du code d'envoi. Elle change qui connaît le type de qui à la compilation. Le port «Notifier» est exprimé dans le vocabulaire du métier, pas de l'infra — et soudain le métier ne connaît plus personne. L'animation fait pivoter la flèche.The inversion doesn't move code or change execution: at runtime, OrderService still calls sending code. It changes who knows whose type at compile time. The «Notifier» port speaks the business vocabulary, not infra — and suddenly the business knows no one. The animation pivots the arrow.

Animation — le graphe de dépendancesAnimation — the dependency graph
Départ — couplage directStart — direct coupling · 1/4
OrderService
le quoi · stablethe what · stable
▼
EmailSender
le comment · volatilthe how · volatile
Le métier pointe vers le détail. La flèche descend du stable vers le volatil : mauvais sens.The business points to the detail. The arrow runs from stable down to volatile: wrong way.

Le code de l'inversionThe inversion's code
# port défini DANS le monde métier, à côté d'OrderService
interface Notifier:
    notify(customer, message)

class OrderService(notifier: Notifier):       # ne dépend que de l'idée
    self.notifier.notify(order.customer, "Confirmée")

class EmailNotifier implements Notifier:       # l'infra se conforme
    def notify(customer, message): smtp.send(customer.email, message)

# composition root : le seul endroit qui connaît tout le monde
service = OrderService(notifier = EmailNotifier())
Le motif transférable.The transferable pattern.

« Le module de haut niveau ne doit pas dépendre du module de bas niveau ; les deux doivent dépendre d'une abstraction. » (Le « D » de SOLID.) C'est le squelette des Ports & Adapters, de la Clean Architecture, du Repository, du mock en test. Partout où tu veux changer le comment sans toucher au quoi, tu retournes cette flèche.'High-level modules shouldn't depend on low-level modules; both should depend on an abstraction.' (The 'D' in SOLID.) It's the skeleton of Ports & Adapters, Clean Architecture, the Repository, the test mock. Wherever you want to change the how without touching the what, you flip this arrow.

Modèle mentalMental model
L'imageThe image

La prise de courant normalisée. L'appareil ne dépend pas de la centrale électrique : il dépend de la forme de la prise. Et c'est le bâtiment — le haut niveau — qui impose cette forme ; la centrale s'y conforme.The standardized wall socket. The appliance doesn't depend on the power plant: it depends on the socket's shape. And it's the building — the high level — that imposes that shape; the plant conforms to it.

La règleThe rule

Le module stable définit le contrat dans son propre vocabulaire ; le module volatil s'y conforme. Les deux pointent vers l'abstraction, aucun ne pointe vers l'autre.The stable module defines the contract in its own vocabulary; the volatile module conforms to it. Both point to the abstraction, neither points to the other.

Où l'analogie casseWhere the analogy breaks

La prise normalisée appartient à un organisme neutre ; le port, lui, appartient à celui qui l'utilise — le métier. C'est toute la différence avec la simple injection : une interface écrite dans le vocabulaire de l'infra (send_smtp, host) est une prise dessinée par le fabricant de la centrale. Elle a la forme d'une abstraction sans en avoir la propriété — et la flèche n'est pas retournée.The standardized socket belongs to a neutral body; the port, though, belongs to the one who uses it — the business. That's the whole difference from plain injection: an interface written in infra's vocabulary (send_smtp, host) is a socket drawn by the power plant's maker. It has the shape of an abstraction without owning it — and the arrow isn't flipped.

№ 04Le piègeThe trap4 min

Injecter n'est pas inverser. La fausse abstraction qui fuit le détail.Injecting isn't inverting. The fake abstraction that leaks the detail.

Le bug classique n'est pas d'oublier l'injection — c'est de croire qu'injecter une dépendance suffit à l'inverser. Les deux extraits injectent un Notifier de l'extérieur (c'est de la Dependency Injection). Mais l'un retourne la flèche, l'autre non. Bascule le toggle : regarde le vocabulaire du contrat.The classic bug isn't forgetting injection — it's believing injecting a dependency suffices to invert it. Both snippets inject a Notifier from outside (that's Dependency Injection). But one flips the arrow, the other doesn't. Flip the toggle: watch the contract's vocabulary.

# port métier : neutre, ignore tout de l'infra
interface Notifier:
    notify(customer, message)

class OrderService(notifier: Notifier):
    self.notifier.notify(order.customer, "Confirmée")
# le jour où l'on passe à une API HTTP : on écrit un nouvel adapter.
# OrderService ne bouge pas.
Pourquoi c'est justeWhy it's right

L'abstraction est écrite pour décrire le besoin d'OrderService — « notifier un client » — pas pour décrire EmailNotifier. Le contrat est neutre. Passer à une API HTTP, c'est écrire un nouvel adapter et recâbler une ligne à la composition root. Le métier ne bouge pas : la flèche est vraiment retournée.The abstraction is written to describe OrderService's need — 'notify a customer' — not to describe EmailNotifier. The contract is neutral. Moving to an HTTP API means writing a new adapter and rewiring one line at the composition root. The business doesn't move: the arrow is truly flipped.

La règle qui tranche.The rule that settles it.

L'abstraction doit appartenir à celui qui l'utilise, pas à celui qui l'implémente. Si l'interface est écrite pour décrire EmailNotifier, c'est EmailNotifier déguisé. Si elle décrit le besoin d'OrderService, c'est une vraie inversion. DI déplace l'objet ; DIP retourne la connaissance.The abstraction must belong to the one who uses it, not the one who implements it. If the interface is written to describe EmailNotifier, it's EmailNotifier in disguise. If it describes OrderService's need, it's a real inversion. DI moves the object; DIP flips the knowledge.

La version fonctionnelle.The functional version.

Dans un langage où les fonctions sont des valeurs, le port d'une seule méthode est un type de fonction : OrderService reçoit notify: (Customer, string) -> void, et EmailNotifier n'est qu'une closure qui le satisfait. Pas d'interface, pas de classe — la même flèche retournée. Le pattern « port » est souvent juste un manque du langage qu'on nomme. (On y revient au Vol 3.)In a language where functions are values, a single-method port is a function type: OrderService receives notify: (Customer, string) -> void, and EmailNotifier is just a closure that satisfies it. No interface, no class — the same flipped arrow. The 'port' pattern is often just a language gap we name. (More in Vol 3.)

№ 05Bilan & éditoWrap-up & editorial2 min

Le quoi appelle le comment, ou les deux pointent vers l'idée.The what calls the how, or both point to the idea.

Les deux versions, côte à côteThe two versions, side by side
Couplage directDirect couplingDépendance inverséeInverted dependency
Qui connaît le détailWho knows the detaille métierthe businesspersonne (sauf la composition root)no one (except the composition root)
Changer de canalChange channelrouvrir OrderServicereopen OrderServiceajouter un adapter, 1 ligne recâbléeadd an adapter, rewire 1 line
Tester la logiqueTest the logicexige l'infra réelleneeds the real infradoublure triviale, zéro réseautrivial double, zero network
Coût payéCost paidnul à l'écriture, lourd au changementnone to write, heavy to changeune indirection (interface + câblage)one indirection (interface + wiring)
Idée cléKey ideale quoi appelle le commentthe what calls the howles deux pointent vers l'idéeboth point to the idea

Quand ne pas inverserWhen not to invert

Le pattern a un loyer : une interface de plus, une couche à lire, un saut au débogueur, une composition root à tenir. On l'évite quand :The pattern has rent: one more interface, a layer to read, a debugger hop, a composition root to maintain. Avoid it when:

  • il n'y aura jamais qu'une implémentation et qu'elle ne franchit aucune frontière d'I/O (un calcul pur, un formateur de date). Inverser ici, c'est de la cérémonie : YAGNI.there will only ever be one implementation and it crosses no I/O boundary (a pure calculation, a date formatter). Inverting here is ceremony: YAGNI.
  • le détail EST le métier. Un service de paiement dont le cœur est l'intégration Stripe n'a pas à cacher Stripe derrière une abstraction fantôme « au cas où ».the detail IS the business. A payment service whose core is the Stripe integration needn't hide Stripe behind a phantom 'just in case' abstraction.
  • l'abstraction n'aurait qu'un seul appelant ET un seul implémenteur pour toujours : une interface 1-pour-1 perpétuelle ajoute un nom sans ajouter de liberté.the abstraction would have one caller AND one implementer forever: a perpetual 1-to-1 interface adds a name without adding freedom.

Le réflexe n'est donc pas « inverse tout ». C'est : « cette dépendance traverse-t-elle une frontière susceptible de changer (I/O, tiers, canal) ? Si oui, je retourne la flèche ; sinon, je laisse le couplage direct et j'économise l'indirection. »So the reflex isn't 'invert everything'. It's: 'does this dependency cross a boundary likely to change (I/O, third party, channel)? If yes, flip the arrow; if not, keep direct coupling and save the indirection.'

Mot de l'éditeurFrom the editor

Dependency Inversion n'est pas un problème de notification. C'est le numéro où l'on cesse de lire le code comme une liste d'instructions et où l'on commence à le lire comme un graphe de flèches. Le débutant demande « est-ce que ça marche ? ». L'architecte demande « dans quel sens pointent les dépendances, et est-ce que le stable dépend du volatil ? ».Dependency Inversion isn't a notification problem. It's the issue where you stop reading code as a list of instructions and start reading it as a graph of arrows. The beginner asks 'does it work?'. The architect asks 'which way do the dependencies point, and does the stable depend on the volatile?'.

La fonctionnalité ne change pas quand on inverse — l'exécution est identique. Ce qui change, c'est qui doit rouvrir quel fichier le jour où le monde bouge. Toute la série découle de là : une architecture, c'est une discipline sur le sens des flèches.Functionality doesn't change when you invert — execution is identical. What changes is who must reopen which file the day the world moves. The whole series flows from this: an architecture is a discipline over the direction of arrows.

Les voisins que ce numéro débloqueThe neighbors this issue unlocks

  • Dependency Injection — le mécanisme qui livre l'implémentation à la composition root (constructeur, conteneur). Souvent confondu avec DIP ; c'en est seulement la moitié visible.the mechanism that delivers the implementation to the composition root (constructor, container). Often confused with DIP; it's only the visible half.
  • Ports & Adapters (Hexagonal) — le même retournement de flèche, généralisé à toutes les frontières d'I/O : le métier au centre, les adapters au bord. (Vol 1 #2.)the same arrow flip, generalized to all I/O boundaries: the business at the center, adapters at the edge. (Vol 1 #2.)
  • Service Locator — l'anti-pattern voisin : au lieu d'injecter, le code va chercher sa dépendance dans un registre global. Il restaure le couplage qu'on venait de casser, en le cachant.the neighboring anti-pattern: instead of injecting, code fetches its dependency from a global registry. It restores the coupling you just broke, by hiding it.

Le kata — écris-le, ne le relis pasThe kata — write it, don't reread it

Tu as vu la flèche pivoter au chapitre 3. Reste le geste qui ancre : l'écrire. Copie ce fichier de tests dans ton langage habituel — la traduction est triviale, la spec tient dans les noms des tests — et fais-le passer au vert sans revenir au numéro. Le dernier test est le sournois : c'est lui qui vérifie que ton port parle le métier (notify) et non l'infra (send_smtp) — la seule chose qui sépare une vraie inversion d'une injection déguisée.You watched the arrow pivot in chapter 3. What's left is the gesture that anchors it: writing it. Copy this test file into your usual language — the translation is trivial, the spec fits in the test names — and turn it green without coming back to the issue. The last test is the sneaky one: it's what checks that your port speaks the business (notify) and not infra (send_smtp) — the only thing that separates a real inversion from a disguised injection.

test_inversion.py
# test_inversion.py — la spec EST le harnais : fais-le passer au vert.
# un OrderService qui ne connaît QUE le port Notifier. sans mock lib, sans réseau.

def test_confirme_notifie_via_le_port():
    notifier = FakeNotifier()
    svc = OrderService(notifier)                  # on injecte une doublure
    svc.confirm(Order(customer=alice, total=42))
    assert notifier.calls == [(alice, "Confirmée")]   # le métier a notifié via le port

def test_le_metier_ne_connait_pas_le_canal():
    svc = OrderService(FakeNotifier())
    assert "smtp" not in modules_touched(svc.confirm)  # aucune trace d'infra dans le cœur
    # OrderService n'importe ni SMTP ni HTTP : il ne parle que « Notifier »

def test_changer_de_canal_ne_touche_pas_le_metier():
    mails, sms = [], []
    OrderService(EmailNotifier(send=lambda *m: mails.append(m))).confirm(Order(customer=alice, total=42))  # même métier…
    OrderService(SmsNotifier(send=lambda *m: sms.append(m))).confirm(Order(customer=alice, total=42))      # …autre adapter
    assert mails and sms           # une seule classe OrderService, inchangée, deux canaux

def test_l_adapter_traduit_seulement():
    sent = []
    EmailNotifier(send=lambda to, msg: sent.append((to, msg))).notify(alice, "Confirmée")
    assert sent == [("alice@x.io", "Confirmée")]   # l'adapter mappe client→email, rien d'autre

def test_le_port_parle_metier_pas_smtp():
    # le contrat n'expose que notify(customer, message) — jamais send_smtp/host.
    assert port_methods(Notifier) == {"notify"}    # une fuite d'infra dans le port = un test rouge
Chez toi, cette semaine.In your codebase, this week.

Ouvre ton dossier de domaine (le cœur métier) et cherche-y un import d'infra : grep -rn "import smtplib\|import requests\|import stripe\|from sqlalchemy" src/domain/ src/core/. Chaque résultat est une classe métier qui connaît un détail technique — la flèche pointe du stable vers le volatil. Pose la question du chapitre 5 : « ce détail traverse-t-il une frontière susceptible de changer ? » Si oui, retourne la flèche ; sinon, laisse le couplage direct et note pourquoi.Open your domain folder (the business core) and hunt for an infra import: grep -rn "import smtplib\|import requests\|import stripe\|from sqlalchemy" src/domain/ src/core/. Every hit is a business class that knows a technical detail — the arrow runs from stable to volatile. Ask chapter 5's question: 'does this detail cross a boundary likely to change?' If yes, flip the arrow; if not, keep the direct coupling and note why.