C’est le « D » de SOLID, et celui qui fait le plus peur. Pourtant l’idée est simple : les modules importants ne doivent pas dépendre des détails ; les détails doivent dépendre d’eux. Ton code métier ne devrait jamais « connaître » PostgreSQL — c’est PostgreSQL qui doit se plier à ce que ton métier attend.
Le sens naturel de la flèche
Sans précaution, la dépendance suit le sens de l’appel : le service appelle la base, donc il en dépend.
// ❌ le métier dépend d'un détail technique
class CreateOrder {
constructor(private db: PostgresClient) {}
run(order: Order) {
this.db.query("INSERT INTO orders ...");
}
}Change de base, change de bibliothèque, et CreateOrder — du code métier — doit
être réécrit. Le détail technique commande le cœur. C’est la flèche dans le mauvais
sens.
On inverse avec une abstraction
On introduit une interface possédée par le métier. Le service en dépend ; la base l’implémente.
// le métier définit ce dont il a besoin
interface OrderRepository {
save(order: Order): Promise<void>;
}
class CreateOrder {
constructor(private orders: OrderRepository) {}
run(order: Order) {
return this.orders.save(order);
}
}
// le détail s'adapte au contrat
class PostgresOrders implements OrderRepository {
save(order: Order) { /* ... SQL ... */ }
}Maintenant CreateOrder ne connaît que OrderRepository, une abstraction. La
flèche de dépendance pointe vers le métier, plus vers la technique. C’est
ça, l’inversion.
Ce que ça t’achète vraiment
Trois choses concrètes :
- Tester sans infrastructure : passe un faux repository en mémoire, aucun besoin de vraie base.
- Remplacer un détail : Postgres → un autre moteur ne touche pas une ligne de métier.
- Protéger le cœur : les décisions importantes ne sont plus otages d’un choix technique pris un mardi.
Dépends de ce qui est stable (une abstraction métier), jamais de ce qui change (un détail technique).
L’inversion de dépendances n’est pas une fin : c’est le mécanisme qui rend possible une frontière nette entre ton domaine et le monde extérieur — celle que les ports et adaptateurs dessinent explicitement.
It’s the “D” in SOLID, and the scariest one. Yet the idea is simple: important modules must not depend on details; details must depend on them. Your business code should never “know” PostgreSQL — it’s PostgreSQL that should bend to what your business expects.
The natural direction of the arrow
Left alone, the dependency follows the call: the service calls the database, so it depends on it.
// ❌ business logic depends on a technical detail
class CreateOrder {
constructor(private db: PostgresClient) {}
run(order: Order) {
this.db.query("INSERT INTO orders ...");
}
}Switch databases, switch libraries, and CreateOrder — business code — has to
be rewritten. The technical detail rules the core. That’s the arrow pointing the
wrong way.
We invert with an abstraction
We introduce an interface owned by the business. The service depends on it; the database implements it.
// the business defines what it needs
interface OrderRepository {
save(order: Order): Promise<void>;
}
class CreateOrder {
constructor(private orders: OrderRepository) {}
run(order: Order) {
return this.orders.save(order);
}
}
// the detail conforms to the contract
class PostgresOrders implements OrderRepository {
save(order: Order) { /* ... SQL ... */ }
}Now CreateOrder only knows OrderRepository, an abstraction. The dependency
arrow points toward the business, no longer toward the tech. That’s the
inversion.
What it actually buys you
Three concrete things:
- Test without infrastructure: pass a fake in-memory repository, no real database needed.
- Replace a detail: Postgres → another engine touches not one line of business logic.
- Protect the core: important decisions are no longer hostage to a technical choice made on some random Tuesday.
Depend on what’s stable (a business abstraction), never on what changes (a technical detail).
Dependency Inversion isn’t an end in itself: it’s the mechanism that makes a clean boundary possible between your domain and the outside world — the one that Ports & Adapters draws explicitly.