Read in English · Leer en español · Auf Deutsch lesen
Section 5.6 sur 5.8 · Intermédiaire · 25 min de lecture
Architecture hexagonale, en oignon et Clean Architecture
Leçon vidéo
5 min · narré en anglais, avec sous-titresÉcouter seulement (audio)
Lire la transcription
Cette leçon présente une idée sous trois noms : architecture hexagonale, en oignon et Clean Architecture. Elle garde vos règles métier indépendantes des bases de données, des frameworks et des services externes, pour que vous puissiez les tester rapidement et remplacer la technologie qui les entoure.
Pensez à un adaptateur de voyage. Le chargeur de votre ordinateur portable a une seule forme de fiche, et chaque pays a sa propre prise. Vous ne reconstruisez pas l’ordinateur pour chaque pays. Vous emportez de petits adaptateurs qui s’adaptent à la prise d’un côté et à votre fiche de l’autre. Ces styles construisent le logiciel de la même façon. Les règles métier ont leurs propres prises standard, appelées ports. Les bases de données, les sites web, les sociétés de paiement et les outils de test s’y connectent par des adaptateurs, si bien que n’importe lequel peut être remplacé sans toucher aux règles.
Ces styles sont nés d’une frustration envers l’organisation en couches, où la couche métier dépend de la persistance, si bien que les règles finissent façonnées par les tables, et que tester une règle implique de démarrer une base de données. La réponse d’Alistair Cockburn, en 2005, a été l’architecture hexagonale, aussi appelée ports et adaptateurs. L’architecture en oignon de Jeffrey Palermo a suivi en 2008. Et Robert Martin a réuni ces idées sous le nom de Clean Architecture, dans un billet de blog en 2012 et un livre en 2017. Toutes trois inversent la dépendance. Le code métier déclare les interfaces dont il a besoin, et l’infrastructure les implémente.
Voici l’hexagone. À l’intérieur se trouve l’application : le domaine et ses cas d’utilisation. La forme elle-même n’a aucune signification ; ce qui compte, c’est l’intérieur par opposition à l’extérieur. Un port est une interface qui appartient à l’application. À gauche se trouvent les ports pilotes, comme Place Order et Track Order : l’API propre de l’application. Des adaptateurs pilotes les appellent : un contrôleur REST, l’API d’une app mobile, un banc de test ou un script batch. Chacun transforme ses entrées en un appel avec des valeurs du domaine. À droite se trouvent les ports pilotés, comme Orders et Payments. L’application les appelle quand elle a besoin de quelque chose du monde extérieur. Des adaptateurs pilotés les implémentent : un repository PostgreSQL ou un repository en mémoire, un vrai prestataire de paiement par carte ou une imitation. À l’exécution, l’application appelle l’adaptateur, mais dans le code, c’est l’adaptateur qui dépend de l’application.
La Clean Architecture dessine la même idée sous forme d’anneaux. Au centre se trouvent les entités : les règles valables pour toute l’entreprise, comme Order et Money. Autour d’elles, les cas d’utilisation : les règles propres à l’application, comme Place Order. Puis les adaptateurs d’interface : contrôleurs, présentateurs et implémentations de repositories. Et en bordure, les frameworks et pilotes : le framework web, la base de données, l’interface utilisateur. La règle de dépendance fait tenir l’ensemble : les dépendances du code source ne pointent que vers l’intérieur. Rien dans un anneau intérieur ne peut nommer quoi que ce soit de déclaré dans un anneau extérieur.
Voici pourquoi c’est important. La plateforme de livraison de repas doit passer à un nouveau prestataire de paiement par carte en six semaines, à cause d’un changement de tarifs. Dans une version du système, l’étape de paiement appelle directement le kit de développement logiciel de l’ancien prestataire, à vingt-trois endroits. Ses types d’erreur sont capturés à onze endroits, et ses objets sont stockés dans la base de données. Chaque endroit d’appel doit changer, les données stockées doivent être migrées, et les tests de paiement réécrits. Cela fait cinq à six semaines, avec une bascule risquée d’un seul coup. Dans l’autre version, une seule classe d’adaptateur d’environ deux cents lignes implémente un port de passerelle de paiement. Le noyau ne connaît que le débit, le remboursement et son propre type de résultat. L’équipe écrit donc un second adaptateur, exécute contre lui les tests existants du port, puis bascule un pour cent des paiements, puis dix, puis tous. Deux semaines, réversible à chaque étape. Le port a coûté environ trois jours de plus au départ. Sa valeur dépend de la probabilité et du coût du changement. Et il doit parler la langue du domaine, et non calquer l’API d’un fournisseur.
Utilisez cette structure quand la logique métier est substantielle et survivra aux frameworks actuels, et quand des tests rapides et isolés des règles métier comptent. Elle est aussi rentable quand vous vous intégrez à des services externes susceptibles de changer. Soyez prudent avec les prototypes, les outils à courte durée de vie et les applications qui se contentent pour l’essentiel de déplacer des champs de formulaire vers des tables. Là, des interfaces supplémentaires et du code de mapping ajoutent des fichiers sans ajouter de protection. Et comme pour les couches, faites respecter la règle automatiquement, avec import-linter, avec la règle d’architecture en oignon toute prête d’ArchUnit, ou avec un module de domaine qui refusera même de compiler s’il dépend d’un framework.
La section complète construit le cas d’utilisation Place Order en Python, avec un véritable adaptateur et une doublure de test, et compare les trois styles côte à côte. Ensuite, à la section cinq point sept, nous trouverons de bonnes frontières grâce à la conception pilotée par le domaine. À très vite.
Ce qu’il faut savoir
- Les architectures hexagonale (ports et adaptateurs, Cockburn, 2005), en oignon (Palermo, 2008) et Clean Architecture (Martin, billet de blog en 2012, livre en 2017) partagent une même idée : la logique métier au centre, indépendante des frameworks et de l’infrastructure, avec des dépendances orientées vers l’intérieur.
- Un port est une interface qui appartient à l’application ; un adaptateur fait la traduction entre un port et une technologie. Les adaptateurs pilotes appellent l’application ; les adaptateurs pilotés implémentent des interfaces que l’application appelle.
- La règle de dépendance dit que le code intérieur ne doit jamais nommer quoi que ce soit d’un anneau extérieur ; l’inversion des dépendances permet au flux de contrôle d’aller vers l’extérieur tandis que les dépendances du code source pointent vers l’intérieur.
- Les principaux bénéfices sont des tests rapides et isolés des règles métier, et la possibilité de remplacer une infrastructure, comme un prestataire de paiement, en écrivant un nouvel adaptateur.
- Les coûts sont des interfaces supplémentaires, du code de mapping et de l’indirection, qui ne valent pas la peine pour de simples applications CRUD ou des outils à courte durée de vie.
- Faites respecter la règle automatiquement, par exemple avec import-linter, la règle onionArchitecture d’ArchUnit ou des modules de build séparés.
Une idée, trois noms
L’architecture hexagonale, l’architecture en oignon et la Clean Architecture sont trois formulations d’une même idée : placer la logique métier au centre, la rendre indépendante des interfaces utilisateur, des bases de données, des frameworks et des services externes, et faire pointer vers elle toutes les dépendances du code source. Elles décrivent la structure interne d’une application ; elles se combinent donc avec n’importe quel style de déploiement : un monolithe unique, un monolithe modulaire dont chaque module est construit de cette façon, ou un microservice.
Elles sont nées d’une frustration envers l’architecture en couches classique. Dans le style en couches de la section 5.2, la couche métier dépend de la couche de persistance, qui dépend de la base de données : les règles métier finissent façonnées par les tables et le mapping objet-relationnel, et tester une règle implique de démarrer une base de données. La famille de styles de cette section inverse cette dépendance grâce au principe d’inversion des dépendances : le code métier déclare les interfaces dont il a besoin, et le code d’infrastructure les implémente. Le principe lui-même est traité à la section 3.5 ; cette section présente les architectures qui reposent sur lui.
L’architecture hexagonale : ports et adaptateurs
Alistair Cockburn a d’abord décrit l’idée sur le wiki du Portland Pattern Repository, puis l’a rédigée sous forme d’article en 2005 sous le nom plus descriptif de ports et adaptateurs. Son intention déclarée était de permettre à une application d’être pilotée aussi bien par des utilisateurs que par des programmes, des tests automatisés ou des scripts batch
, et de la développer et la tester indépendamment de ses futures bases de données et de ses futurs périphériques. En 2024, Juan Manuel Garrido de Paz et lui ont publié un court livre, Hexagonal Architecture Explained, qui reformule le patron.
Un port est une conversation intentionnelle entre l’application et le monde extérieur, exprimée sous forme d’interface : « passer une commande », « stocker les commandes », « débiter une carte ». Un adaptateur est le code qui fait la traduction entre un port et une technologie particulière : un contrôleur HTTP, un repository SQL, le SDK d’un prestataire de paiement, un script en ligne de commande, une imitation en mémoire. Un même port peut avoir plusieurs adaptateurs. L’application, à l’intérieur de l’hexagone, ne connaît que ses ports.
L’hexagone n’a aucune signification en tant que forme ; Cockburn l’a choisi pour échapper à la représentation des couches de haut en bas et pour laisser de la place afin de dessiner plusieurs ports de chaque côté. Ce qui compte, c’est la frontière entre l’intérieur et l’extérieur, et l’asymétrie entre deux sortes de ports.
Ports pilotes et ports pilotés
Côté pilote et côté piloté
Les ports et adaptateurs pilotes (primaires) se trouvent à gauche dans la représentation habituelle. Un adaptateur pilote reçoit une requête d’un acteur principal (le navigateur d’un utilisateur, une app mobile, un autre système, un test, une tâche planifiée) et appelle un port pilote, qui est l’API propre de l’application : en pratique, un cas d’utilisation comme PlaceOrder. L’adaptateur transforme du HTTP, un message ou un argument de ligne de commande en un appel avec des valeurs du domaine, et retransforme le résultat en réponse.
Les ports et adaptateurs pilotés (secondaires) se trouvent à droite. L’application les appelle quand elle a besoin de quelque chose du monde extérieur : stocker une commande, débiter une carte, envoyer une notification, lire l’heure. Le port est une interface qui appartient à l’application et qui est écrite dans ses termes (OrderRepository.save(order)), et un adaptateur piloté l’implémente pour une technologie (PostgreSQL, l’API d’un prestataire de paiement par carte, une passerelle SMS). C’est là que se produit l’inversion des dépendances : à l’exécution, l’application appelle l’adaptateur, mais dans le code source, c’est l’adaptateur qui dépend de l’interface de l’application, et non l’inverse.
Le texte original de Cockburn parle d’acteurs principaux et secondaires ; les termes pilote et piloté sont aujourd’hui plus courants, et il les emploie lui-même. La distinction compte pour les tests : les ports pilotes sont exercés par des bancs de test qui jouent le rôle d’utilisateurs, et les ports pilotés sont remplacés par des doublures de test. Elle compte aussi pour le changement : les nouveaux canaux (un assistant vocal, une API partenaire) arrivent sous la forme de nouveaux adaptateurs pilotes, et les nouvelles infrastructures sous la forme de nouveaux adaptateurs pilotés.
Quelqu’un doit créer les adaptateurs et les brancher sur l’application. Cela se fait dans une seule racine de composition, généralement le code de démarrage du programme, qui est la seule partie du système à connaître chaque adaptateur. Un framework d’injection de dépendances peut faire ce travail, mais de simples appels de constructeurs dans main sont souvent plus clairs.
L’architecture en oignon
Jeffrey Palermo a décrit l’architecture en oignon dans une série de billets de blog à partir de juillet 2008, à l’intention des applications métier à longue durée de vie au comportement substantiel (il la déconseillait pour les petits sites web). Il a dessiné des anneaux concentriques : le modèle de domaine au centre, les services de domaine autour, les services applicatifs autour de ceux-ci, et un anneau extérieur contenant l’interface utilisateur, l’infrastructure (y compris la base de données) et les tests. Ses principes clés sont que l’application est construite autour d’un modèle objet indépendant, que les couches intérieures définissent des interfaces que les couches extérieures implémentent, que tout couplage est orienté vers le centre et que le cœur de l’application peut être compilé et exécuté séparément de l’infrastructure.
Par rapport à l’architecture hexagonale, l’architecture en oignon ajoute des anneaux nommés à l’intérieur de l’application et rend explicite un point que la représentation de Cockburn laisse implicite : la base de données n’est pas le fondement du système, mais un service externe situé en bordure. Les interfaces de repository vivent dans le cœur ; leurs implémentations vivent dans l’anneau d’infrastructure.
La Clean Architecture et la règle de dépendance
Robert C. Martin a publié « The Clean Architecture » sur son blog en août 2012, comme synthèse de l’architecture hexagonale, de l’architecture en oignon et d’autres propositions similaires, puis l’a développée dans le livre Clean Architecture en 2017. Sa représentation compte quatre anneaux. Les entités portent les règles métier valables pour toute l’entreprise. Les cas d’utilisation portent les règles propres à l’application, qui orchestrent les entités. Les adaptateurs d’interface convertissent les données entre la forme dont ont besoin les cas d’utilisation et celle dont ont besoin les agents externes : contrôleurs, présentateurs et passerelles. Les frameworks et pilotes forment l’anneau extérieur : le framework web, la base de données, l’interface utilisateur.
La règle de dépendance fait tenir l’ensemble : les dépendances du code source ne pointent que vers l’intérieur, et rien dans un anneau intérieur ne peut nommer quoi que ce soit de déclaré dans un anneau extérieur, qu’il s’agisse d’une classe, d’une fonction ou d’un format de données. Quand le contrôle doit aller vers l’extérieur (un cas d’utilisation qui enregistre une commande), l’anneau intérieur déclare une interface et l’anneau extérieur l’implémente. Les données qui franchissent une frontière doivent être des structures simples, exprimées dans les termes de l’anneau intérieur, jamais une ligne de base de données ni un objet de requête HTTP. Martin soutient aussi que la structure de premier niveau doit révéler la finalité du système (commandes, paiements, livraisons) plutôt que son framework, une idée qu’il appelle screaming architecture (l’architecture qui crie).
Les anneaux de la Clean Architecture
Les architectures hexagonale, en oignon et Clean Architecture comparées
| Aspect | Hexagonale (ports et adaptateurs) | En oignon | Clean Architecture |
|---|---|---|---|
| Auteur et date | Alistair Cockburn ; rédigée sous forme d’article en 2005 | Jeffrey Palermo ; série de billets de blog à partir de juillet 2008 | Robert C. Martin ; billet de blog en août 2012, livre en 2017 |
| Représentation | Un hexagone avec un intérieur (l’application) et un extérieur (les adaptateurs), le côté gauche pilote, le côté droit piloté | Anneaux concentriques : modèle de domaine, services de domaine, services applicatifs, puis interface utilisateur, infrastructure et tests | Anneaux concentriques : entités, cas d’utilisation, adaptateurs d’interface, frameworks et pilotes |
| Règle centrale | L’application ne communique avec le monde que par des ports ; les adaptateurs font la traduction | Tout couplage est orienté vers le centre ; le cœur définit des interfaces que l’anneau extérieur implémente | La règle de dépendance : les dépendances du code source ne pointent que vers l’intérieur |
| Structure interne prescrite | Aucune : seulement l’intérieur et l’extérieur | Oui : des anneaux intérieurs nommés pour le domaine | Oui : entités et cas d’utilisation, plus des recommandations sur les présentateurs et les données qui franchissent les frontières |
| Accent principal | Symétrie : utilisateurs, tests, scripts et autres programmes peuvent tous piloter l’application | Placer le modèle de domaine, et non la base de données, au centre d’une application métier | Indépendance vis-à-vis des frameworks, de l’interface utilisateur, de la base de données et des agents externes ; des règles métier testables |
| Vocabulaire typique | Port, adaptateur, pilote (primaire), piloté (secondaire), acteur | Modèle de domaine, services de domaine, services applicatifs, infrastructure | Entité, cas d’utilisation (interactor), passerelle (gateway), présentateur, contrôleur |
Un cas d’utilisation avec un port et deux adaptateurs
L’exemple Python suivant implémente le cas d’utilisation PlaceOrder de la plateforme de livraison de repas à la manière hexagonale. Le premier extrait est l’intérieur : une petite classe du domaine, deux ports pilotés écrits sous forme de classes typing.Protocol, et le cas d’utilisation, qui joue le rôle de port pilote. Le deuxième extrait est l’extérieur : un adaptateur PostgreSQL et une doublure de test en mémoire qui implémentent le même port. Le troisième montre pourquoi c’est important : un test unitaire rapide, et la racine de composition où les adaptateurs de production sont branchés.
from dataclasses import dataclass
from decimal import Decimal
from typing import Protocol
@dataclass
class Order:
id: str
total: Decimal
status: str = "new"
class OrderRepository(Protocol): # driven port
def save(self, order: Order) -> None: ...
class PaymentGateway(Protocol): # driven port
def charge(self, order_id: str, amount: Decimal) -> bool: ...
class PlaceOrder: # driving port + use case
def __init__(self, orders: OrderRepository, payments: PaymentGateway):
self.orders, self.payments = orders, payments
def __call__(self, order_id: str, total: Decimal) -> Order:
if total <= 0:
raise ValueError("empty basket")
order = Order(order_id, total)
order.status = "paid" if self.payments.charge(order_id, total) else "payment-failed"
self.orders.save(order)
return orderclass PostgresOrderRepository: # real driven adapter
def __init__(self, conn):
self.conn = conn
def save(self, order):
with self.conn.cursor() as cur:
cur.execute(
"INSERT INTO orders (id, total, status) VALUES (%s, %s, %s) "
"ON CONFLICT (id) DO UPDATE SET status = EXCLUDED.status",
(order.id, order.total, order.status))
self.conn.commit()
class InMemoryOrderRepository: # test double, same port
def __init__(self):
self.saved = {}
def save(self, order):
self.saved[order.id] = orderclass DecliningPayments:
def charge(self, order_id, amount): return False
def test_failed_payment_is_recorded():
orders = InMemoryOrderRepository()
place = PlaceOrder(orders, DecliningPayments())
place("A-17", Decimal("23.50"))
assert orders.saved["A-17"].status == "payment-failed"
# composition root (main.py): the only place that knows every adapter
# place_order = PlaceOrder(PostgresOrderRepository(conn), CardProviderGateway(key))
# app.add_route("/orders", make_rest_controller(place_order)) # driving adapterRemarquez ce que le cas d’utilisation ne contient pas : pas de SQL, pas de codes de statut HTTP, pas de types d’exception propres à un prestataire, pas de décorateurs de framework. Le test s’exécute en quelques millisecondes et couvre un cas (un paiement refusé) qu’il serait malcommode de mettre en place avec la sandbox d’un vrai prestataire. L’adaptateur PostgreSQL a besoin de ses propres tests, mais ce sont des tests d’intégration ciblés d’une seule classe face à une vraie base de données, pas des tests de règles métier. C’est le bénéfice de testabilité décrit à la section 2.6 : la contrôlabilité et l’observabilité grâce aux dépendances injectées.
Exemple commenté
Exemple commenté : changer de prestataire de paiement
La plateforme de livraison de repas doit passer d’un prestataire de paiement par carte à un autre en six semaines, à cause d’un changement de tarifs. Comparez le travail dans deux versions du système : l’une où le code de l’étape de paiement appelle directement le SDK de l’ancien prestataire à 23 endroits, et l’autre construite avec un port piloté PaymentGateway et un adaptateur.
| Appels directs : mesurer l’impact | Une recherche trouve le SDK dans l’étape de paiement, les remboursements, les abonnements et trois écrans d’administration. Les types d’exception propres au SDK sont capturés à 11 endroits, et son objet Charge est stocké dans une colonne de base de données. |
|---|---|
| Appels directs : la modification | Chaque endroit d’appel change, plus une migration de données pour les objets stockés. Chaque test qui touchait aux paiements a été écrit pour la sandbox de l’ancien SDK et doit être réécrit. Estimation : 5 à 6 semaines, avec une bascule risquée d’un seul coup. |
| Ports et adaptateurs : mesurer l’impact | Une seule classe d’adaptateur (environ 200 lignes) implémente PaymentGateway. Le noyau ne connaît que charge, refund et son propre type PaymentResult. |
| Ports et adaptateurs : la modification | Écrire un second adaptateur pour le nouveau prestataire ; exécuter contre lui les tests de contrat existants du port ; choisir l’adaptateur commande par commande avec un feature flag, de sorte que 1 %, puis 10 %, puis 100 % des paiements passent par le nouveau. Estimation : 2 semaines, réversible à chaque étape. |
| Ce que le port a coûté | Concevoir le port et l’adaptateur au départ a pris environ 3 jours de plus, et chaque nouvelle fonctionnalité de paiement exige une petite modification du port en plus de l’adaptateur. |
La valeur d’un port est proportionnelle à la probabilité et au coût du changement qu’il isole. Les prestataires de paiement, les courtiers de messages et les API tierces changent souvent ; votre propre moteur de base de données principal ne changera peut-être jamais, et l’envelopper dans des ports élaborés est plus difficile à justifier.
Le port doit parler la langue du domaine (charge, PaymentResult), et non celle de l’ancien prestataire. Un port qui calque l’API d’un fournisseur ne protège de rien.
Caractéristiques de la famille ports et adaptateurs
| Caractéristique | Évaluation | Justification |
|---|---|---|
| Testabilité | Élevé | Les règles métier peuvent être testées en mémoire, rapidement et de façon déterministe, en branchant des doublures de test sur les ports pilotés. |
| Modifiabilité de l’infrastructure | Élevé | Remplacer une base de données, un prestataire de paiement ou un framework web revient à écrire un nouvel adaptateur ; le noyau ne change pas. |
| Modifiabilité des règles métier | Moyen à élevé | Les règles vivent à un seul endroit, libres de tout code de framework, mais une fonctionnalité traverse tout de même le port, le cas d’utilisation et l’adaptateur. |
| Simplicité | Bas à moyen | Plus de types, d’interfaces et de code de mapping que dans une application en couches classique ; plus difficile au début pour les nouveaux venus. |
| Vitesse de développement (au début) | Moyen | La structure supplémentaire ralentit les premières fonctionnalités d’une simple application CRUD ; elle est rentabilisée à mesure que le domaine grandit. |
| Coût global | Faible à moyen | Aucune infrastructure supplémentaire ; le coût est le temps que les développeurs passent à écrire et à maintenir les ports, les adaptateurs et le code de mapping. |
| Performances | Moyen à élevé | Les appels d’interface ne coûtent presque rien ; les vrais surcoûts sont le mapping d’objets entre les couches et la perte de certaines commodités de l’ORM. |
| Scalabilité, déployabilité, tolérance aux pannes | Non déterminées par ce style | Elles dépendent du style de déploiement (monolithe ou services) dans lequel s’inscrit l’hexagone. |
Coûts, critiques et quand l’utiliser
Les bénéfices sont réels, et les coûts aussi. Chaque port est une interface supplémentaire ; chaque frontière peut exiger du code de mapping pour convertir, par exemple, entre une entité ORM, un objet du domaine et une réponse d’API. Pour une application qui se contente pour l’essentiel de déplacer des champs de formulaire vers des tables avec peu de logique entre les deux, cette structure ajoute des fichiers sans ajouter de protection, et les conventions d’un framework (un modèle active record, une interface d’administration générée) peuvent donner un meilleur résultat plus vite. Certains praticiens expérimentés soutiennent que les versions strictes de la Clean Architecture produisent trop d’indirection et que la plupart des équipes ne changent jamais de base de données ; d’autres répondent que le principal bénéfice n’est pas de changer de technologie, mais d’avoir des tests rapides et fiables et un code métier lisible sans connaître le framework. Les deux ont raison, dans des contextes différents.
Utilisez-la quand la logique métier est substantielle et survivra aux frameworks actuels ; quand le système s’intègre à des services externes susceptibles de changer ; quand des tests rapides et isolés des règles métier sont importants ; et comme structure interne des modules d’un monolithe modulaire ou des services construits avec la conception pilotée par le domaine (section 5.7). Soyez prudent avec les simples applications CRUD, les prototypes et les outils à courte durée de vie, pour lesquels la structure par défaut du framework suffit généralement. Une voie médiane pragmatique est courante : des ports pour les services externes et pour tout ce qui est susceptible de changer, et les commodités propres au framework ailleurs.
Faire respecter la règle de dépendance
Comme pour les couches, la règle s’érode si une machine ne la vérifie pas. En Python, import-linter peut déclarer les anneaux sous la forme d’un contrat layers (adaptateurs au-dessus de l’application, elle-même au-dessus du domaine) et ajouter des contrats forbidden qui empêchent les paquets du domaine et de l’application d’importer des frameworks web, des pilotes de base de données ou des SDK. En Java, ArchUnit propose une règle toute prête, onionArchitecture(), dans laquelle vous nommez les paquets du modèle de domaine, des services de domaine, des services applicatifs et des adaptateurs, et qui fait échouer un test si une dépendance pointe vers l’extérieur. Découper le code en modules de build séparés (un module de domaine sans aucune dépendance à un framework) rend les dépendances vers l’extérieur impossibles à compiler, ce qui est la vérification la plus forte de toutes. Ces vérifications sont des exemples des fonctions d’aptitude traitées au chapitre 11.
Une structure de paquets qui rend la règle visible aide les relecteurs : par exemple orders/domain, orders/application (cas d’utilisation et ports) et orders/adapters/{rest,postgres,payments}. S’organiser d’abord par domaine métier, puis par anneau, garde la structure alignée sur les frontières du domaine des sections 5.3 et 5.7.
Vérifiez votre compréhension
Un contrôleur REST est-il un adaptateur pilote ou piloté ? Et un repository SQL ?
Un contrôleur REST est un adaptateur pilote : il reçoit des requêtes et appelle l’application. Un repository SQL est un adaptateur piloté : il implémente une interface que l’application appelle.
Dans la Clean Architecture, un cas d’utilisation peut-il renvoyer une entité ORM à un contrôleur ?
Non. L’ORM appartient à l’anneau extérieur, donc un cas d’utilisation qui le nomme a une dépendance vers l’extérieur. Il doit renvoyer une structure simple ou un objet du domaine défini dans un anneau intérieur.
Où les adaptateurs sont-ils créés et connectés à l’application ?
Dans une racine de composition, généralement le code de démarrage, qui est le seul endroit à connaître chaque adaptateur.
Qu’est-ce que l’architecture en oignon ajoute à la représentation hexagonale ?
Des anneaux nommés à l’intérieur de l’application (modèle de domaine, services de domaine, services applicatifs) et l’affirmation explicite que la base de données est une préoccupation externe située en bordure, et non le fondement.
Sources et lectures complémentaires
Une question sur cette section ? ArchitectBot, l’assistant d’étude IA du cours, répond d’abord à partir du cours.
Demander : « Notre application Django a des règles métier dispersées entre les modèles, les vues et les tâches Celery. Comment pourrions-nous introduire progressivement les ports et adaptateurs sans tout réécrire ? »