« Architecture hexagonale » sonne savant, mais le dessin tient en une phrase : ton métier est au centre, et tout ce qui vient de l’extérieur (HTTP, base de données, file de messages) se branche autour par des prises standardisées. Ces prises ont un nom : les ports. Ce qui s’y branche : les adaptateurs.
Le port : un trou défini par le métier
Un port est une interface que le domaine possède. Il dit ce dont il a besoin, sans rien savoir de qui le fournira :
// port (côté domaine) : « j'ai besoin de ranger des commandes »
interface OrderRepository {
save(order: Order): Promise<void>;
byId(id: string): Promise<Order | null>;
}Le métier programme contre ce port. Il ignore totalement s’il y a une base SQL, un fichier ou une API derrière.
L’adaptateur : ce qui remplit le trou
Un adaptateur implémente un port avec une technologie concrète. On peut en avoir plusieurs pour le même port :
// adaptateur de production
class PostgresOrders implements OrderRepository {
save(order: Order) { /* SQL */ }
byId(id: string) { /* SQL */ }
}
// adaptateur de test, sans infrastructure
class InMemoryOrders implements OrderRepository {
private store = new Map<string, Order>();
async save(order: Order) { this.store.set(order.id, order); }
async byId(id: string) { return this.store.get(id) ?? null; }
}Le même domaine tourne avec l’un ou l’autre, sans changer une ligne. C’est la définition d’un métier découplé de ses détails.
Deux côtés : driving et driven
Les ports se rangent en deux familles :
- Côté pilote (driving) : ce qui appelle le métier — un contrôleur HTTP, une commande CLI, un consommateur de messages. L’adaptateur traduit le monde extérieur en appels au domaine.
- Côté piloté (driven) : ce que le métier appelle — base, mail, paiement. Le domaine définit le port, l’adaptateur l’implémente.
Dans les deux cas, la dépendance pointe vers le centre. Le monde extérieur connaît le métier ; le métier ignore le monde extérieur.
Le métier au centre ne doit jamais importer un détail technique. Si tu vois un
importde ton ORM dans ton domaine, l’hexagone fuit.
C’est l’inversion de dépendances appliquée à l’échelle d’une application : une frontière nette, testable, où chaque technologie reste un adaptateur remplaçable — jamais le cœur.
“Hexagonal architecture” sounds fancy, but the drawing fits in one sentence: your business is at the center, and everything from the outside (HTTP, database, message queue) plugs in around it through standardized sockets. Those sockets have a name: ports. What plugs into them: adapters.
The port: a hole defined by the business
A port is an interface the domain owns. It states what it needs, without knowing who will provide it:
// port (domain side): "I need to store orders"
interface OrderRepository {
save(order: Order): Promise<void>;
byId(id: string): Promise<Order | null>;
}The business programs against this port. It has no idea whether there’s a SQL database, a file, or an API behind it.
The adapter: what fills the hole
An adapter implements a port with a concrete technology. You can have several for the same port:
// production adapter
class PostgresOrders implements OrderRepository {
save(order: Order) { /* SQL */ }
byId(id: string) { /* SQL */ }
}
// test adapter, no infrastructure
class InMemoryOrders implements OrderRepository {
private store = new Map<string, Order>();
async save(order: Order) { this.store.set(order.id, order); }
async byId(id: string) { return this.store.get(id) ?? null; }
}The same domain runs with either one, without changing a line. That’s the definition of a business decoupled from its details.
Two sides: driving and driven
Ports come in two families:
- Driving side: what calls the business — an HTTP controller, a CLI command, a message consumer. The adapter translates the outside world into calls to the domain.
- Driven side: what the business calls — database, email, payment. The domain defines the port, the adapter implements it.
In both cases, the dependency points toward the center. The outside world knows the business; the business ignores the outside world.
The business at the center must never import a technical detail. If you see an
importof your ORM inside your domain, the hexagon is leaking.
It’s Dependency Inversion applied at the scale of an application: a clean, testable boundary where each technology stays a replaceable adapter — never the core.