L'Architecture hexagonale : développer en partant du domaine

L'Architecture hexagonale, formalisée par Alistair Cockburn, vise à isoler le domaine. Elle repose sur l'inversion et l'injection des dépendances.
Publié le 23 avril 2025Dernière mise à jour le 17 mars 2026
Loin de moi la volonté de minimiser le mérite d'Alistair Cockburn, mais il faut le dire : l'Architecture hexagonale est ce qu'il y a de plus simple. Et de plus utile. L'Architecture hexagonale est le paradis du développeur, puisqu'elle permet d'apporter très rapidement de la valeur au cœur du domaine tout en faisant abstraction de l'infrastructure.
D'ailleurs, j'en ai un usage très concret : habitué des longs trajets en TGV, je peux témoigner que le wifi, c'est pas toujours ça. Hors de question d'envisager un
npm install
quand une simple recherche sur le Web est déjà pénible, donc pouvoir travailler sans dépendance, avec le seul langage, en créant les abstractions qui ont du sens pour le métier, cela n'a pas de prix.
Place à la théorie, en commençant par l'énoncé du problème.

Les limites de l'architecture N-tiers

Les architectures N-tiers nous ont apporté une parfaite séparation des responsabilités, réparties (dans l'exemple d'une architecture 3-tiers) entre la couche de présentation, la couche applicative/domaine et l'infrastructure.
Mais selon cette architecture, le domaine dépend de l'infrastructure. Or, cette dépendance conduit à un double problème :
  • Le test du domaine : en l'état, vous n'avez d'autre choix que de monter une base de données, y injecter des données connues à l'avance et effectuer le test. Même avec une base en mémoire type SQLite, c'est long, trop long pour des tests unitaires censés s'exécuter en quelques millisecondes. Problématique, puisque c'est justement le type de tests que l'on s'attend à trouver en grande quantité (par centaines) dans le domaine, qui concentre la logique métier.
  • La dépendance au changement : le domaine décrit le cœur de métier de votre entreprise. Banque, assurance, télécommunication, e-commerce, ce que vous voulez, ce métier ne change pas tous les quatre matins. Son cycle d'évolution est lent. En revanche, les montées de version majeures (cassantes) de votre base de données préférée sont fréquentes. C'est dommage que le domaine doive être mis à jour à chaque fois qu'une dépendance change… On sent bien que quelque chose ne va pas ici.

L'inversion de dépendance à la rescousse

L'inversion de dépendance résout exactement ce problème. Voyez comme nous passons de ceci (en TypeScript) :
const random = (min: number, max: number): number =>
  min + (max - min) * Math.random();

const n = random(1, 10);
à cela :
type RandomGenerator = () => number;

type Dependencies = {
  generator: RandomGenerator;
};

const random =
  ({ generator }: Dependencies) =>
  (min: number, max: number): number =>
    min + (max - min) * generator();

const n = random({ generator: Math.random })(1, 10);
Nous sommes ici au cœur du domaine, et
Math.random
est la dépendance qui nous pose problème. Pour y remédier, le domaine définit ce dont il a besoin, selon ses termes, au travers d'une interface :
type RandomGenerator = () => number
. Le domaine pose donc les termes du contrat que l'implémentation réelle de la dépendance (
Math.random
) devra respecter au moment où elle est injectée. C'est un petit tour de passe-passe qui change tout :
  • inversion de dépendance : le domaine définit l'interface
  • injection de dépendance : l'implémentation est fournie à l'appel
Mon exemple n'est pas le meilleur, car
Math.random
est une dépendance au langage et que de surcroît la dépendance implémente l'interface sans qu'il y ait besoin d'aucune adaptation. En pratique, vous aurez souvent besoin d'écrire quelques adapters, qui constituent ce que le Domain-driven design  appelle une Anti-Corruption Layer (ACL). Le terme est fort mais plein de sens : vous ne voulez pas que votre domaine soit "corrompu" par le monde extérieur.
Mais au fait, quelles sont ces dépendances ? Eh bien ce sont les usual suspects : persistence (bases de données, système de fichiers), gestion des transactions (pattern Unit of Work), appels réseau, communication événementielle, écriture de logs, mais aussi génération d'identifiants, de valeurs au hasard et un grand classique, le temps.
Effet notable : l'Architecture hexagonale rend caduques les bibliothèques d'ORM (Object-Relational Mapping), puisque ces dernières proposent de baser votre domaine sur les entités qu'elles exposent et qu'elles lient aux tables de la base de données. Le contraire de l'Architecture hexagonale, qui nous permet de construire le domaine sans aucune dépendance.

L'autre moitié du chemin

Cela n'est en fait que la moitié de l'Architecture hexagonale, mais la plus singulière. L'autre moitié nous est beaucoup plus familière, puisqu'elle consiste à travailler le domaine pour qu'il expose une API bien structurée, qui lui permettra d'être appelé dans différents contextes : le contexte d'un test unitaire, pour commencer, mais ensuite et indifféremment une API REST, une application Web, une CLI ou bien encore un batch. Une fois que votre domaine est bien travaillé, vous pouvez le bootstrapper de mille manières différentes.
L'écriture d'une API répond à des principes généraux que nous ne détaillerons pas ici mais qui, tous, vont dans le sens d'un très grand systématisme.

Une structure qui parle d'elle-même

Voici à présent quelques recommandations pour structurer votre application de manière à rendre cette architecture tout à fait évidente (screaming architecture):
├── domain/
│   ├── entities/           # including aggregates
│   ├── value-objects/
│   ├── services/
│   ├── repositories/
│   └── errors/

├── application/
│   └── use-cases/

└── adapters/
    ├── driven/
    │   ├── database/
    │   ├── filesystem/
    │   └── messaging/

    └── driving/
        ├── http/           # and stub
        ├── cli/
        └── messaging/
L'idée est de bien différencier l'intérieur de l'hexagone (domaine + application) de l'extérieur (adapteurs) :
  • Le domaine : il s'agit du modèle métier et du comportement associé (implémenté au travers de fonctions pures, idéalement), ainsi que des contrats nécessaires pour parler avec le monde extérieur.
  • L'application : chaque use-case traduit une intention utilisateur et agit en tant que chef d'orchestre. Il coordonne les appels au domaine et au monde extérieur. Mais attention : les uses-cases ne remplacent pas le domaine, ils se contentent d'y faire appel (dans le cas contraire, on parle de domaine anémique, et c'est un anti-pattern).
  • Les adapteurs : ici, il est particulièrement intéressant de différencier les adapteurs qui conduisent (driving) l'application (un contrôleur HTTP, une CLI, l'écoute d'un événement) de ceux qui sont conduits (driven) par votre application (base de données, appels réseaux, émission d'événement). Cette rigueur est particulièrement nécessaire pour les événements, qui sont écoutés et émis par l'application.
Au final, le domaine et l'application ne dépendent de rien. L'Architecture hexagonale, c'est ça et rien que ça.
Vous trouverez cette architecture sous l'autre nom de "Ports & Adapters", notamment dans l'article séminal  d'Alistair Cockburn. Ce nom met en avant la symétrie qui existe entre la gestion des dépendances d'un côté et l'API qu'expose le domaine de l'autre. La multiplicité des ports de l'application évoque les côtés d'un hexagone, mais à ce rythme-là, vous pourriez tout aussi bien avoir un dodécagone 😊
Signalons enfin que cette architecture est fondamentale en termes de création de produit, puisqu'elle vous permet de travailler dans un premier temps sur le domaine seul, de prouver très rapidement sa valeur (ex : faire un POC) et de repousser à une date ultérieure le choix des dépendances, en particulier de la base de données. Le moment venu, vous savez exactement quels sont vos besoins, votre choix n'en est que meilleur.