Chapitre 812 minIntermédiaire
Architecture hexagonale et clean architecture : aperçu
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- expliquer le principe des ports et des adaptateurs ;
- distinguer adaptateurs entrants et sortants ;
- comparer architecture hexagonale, clean architecture et architecture en couches ;
- juger si un projet a besoin de ce découpage.
3 exercices corrigés1 schéma
L'architecture hexagonale, ou architecture ports et adaptateurs, place les règles de gestion au centre de l'application et tout le reste autour : l'interface web, l'application de bureau, la base de données, l'envoi d'e-mails. Le centre définit des ports (des interfaces), et chaque technologie s'y branche par un adaptateur. Toutes les dépendances pointent vers le centre, jamais vers l'extérieur. Ce chapitre est un aperçu : il te donne de quoi comprendre un article ou une offre de stage qui en parle, et reconnaître que ResaSalles en applique déjà l'essentiel.
Le principe
Alistair Cockburn a décrit ce style en 2005 sous le nom Ports and Adapters. Le nom « hexagonale » vient du dessin qu'il en faisait : l'hexagone n'a rien de particulier, il laisse simplement de la place pour dessiner plusieurs adaptateurs sur chaque côté. L'idée tient en trois points :
- Le cœur (le domaine et les cas d'utilisation) contient les règles de gestion. Il ne connaît aucune technologie : ni HTTP, ni SQL, ni WinForms.
- Les ports sont les interfaces du cœur. Les ports entrants décrivent ce que l'application sait faire (« réserver une salle ») ; les ports sortants décrivent ce dont elle a besoin (« savoir si un créneau est libre », « enregistrer une réservation », « connaître l'heure », « envoyer une confirmation »).
- Les adaptateurs relient une technologie à un port. Les adaptateurs entrants (ou primaires) appellent le cœur : un contrôleur Express, un formulaire WinForms, un script en ligne de commande, un test. Les adaptateurs sortants (ou secondaires) sont appelés par le cœur : un Repository MySQL, un Repository en mémoire, un client SMTP.
ResaSalles est déjà presque hexagonale
Reprends le code des chapitres 6 et 7 et range chaque pièce :
| Élément de l'architecture hexagonale | Dans ResaSalles |
|---|---|
| Cœur (cas d'utilisation et règles) | ReservationService, les entités Salle et Reservation |
| Port entrant | La méthode reserver(demande) du service |
| Ports sortants | IReservationRepository, ISalleRepository, TimeProvider (l'horloge) |
| Adaptateurs entrants | Le contrôleur Express, le formulaire WinForms et son contrôleur, les tests |
| Adaptateurs sortants | ReservationRepository (MySQL), FausseReservations (mémoire), TimeProvider.System |
| Configuration (qui branche quoi) | La racine de composition : app.js, Program.cs |
L'injection de dépendances du chapitre 7 est le mécanisme qui rend l'hexagone possible : le service dépend d'interfaces, et la racine de composition choisit les adaptateurs.
Ajouter un adaptateur entrant
Brévent Industrie veut importer chaque nuit les réservations des salles de formation, à partir d'un script lancé par une tâche planifiée. Avec une architecture hexagonale, c'est un nouvel adaptateur entrant : un script qui lit ses paramètres et appelle le même service.
// scripts/reserver.js : un deuxième adaptateur entrant, la ligne de commande.
import { parseArgs } from 'node:util';
import { pool } from '../src/db.js';
import { ErreurMetier } from '../src/erreurs.js';
import { creerSalleRepository } from '../src/repositories/salle.repository.js';
import { creerReservationRepository } from '../src/repositories/reservation.repository.js';
import { creerReservationService } from '../src/services/reservation.service.js';
const { values } = parseArgs({
options: {
salle: { type: 'string' },
employe: { type: 'string' },
debut: { type: 'string' },
fin: { type: 'string' },
objet: { type: 'string' },
participants: { type: 'string', default: '1' },
},
});
const service = creerReservationService({
salleRepository: creerSalleRepository(pool),
reservationRepository: creerReservationRepository(pool),
});
try {
const r = await service.reserver({
salleId: Number(values.salle),
employeId: Number(values.employe),
debut: new Date(values.debut),
fin: new Date(values.fin),
objet: values.objet,
participants: Number(values.participants),
});
console.log(`Réservation n° ${r.id} enregistrée`);
} catch (err) {
if (!(err instanceof ErreurMetier)) throw err;
console.error(`Refusée : ${err.message}`);
process.exitCode = 1;
} finally {
await pool.end();
}
$ node --env-file=.env scripts/reserver.js --salle 1 --employe 7 --debut 2026-12-03T14:00 --fin 2026-12-03T15:00 --objet "Formation sécurité"
Réservation n° 2 enregistrée
$ node --env-file=.env scripts/reserver.js --salle 1 --employe 7 --debut 2026-12-03T10:00 --fin 2026-12-03T11:00 --objet "Formation"
Refusée : La salle est déjà réservée sur ce créneau
parseArgs (module node:util) lit les options --salle 1. Le script traduit le résultat dans le langage de la ligne de commande : un message sur la sortie standard en cas de succès, un message sur la sortie d'erreur et un code de sortie non nul en cas de refus, pour que la tâche planifiée sache que quelque chose a échoué. C'est exactement le travail d'un contrôleur, avec un autre protocole que HTTP. Aucune règle n'a été recopiée.
Ajouter un adaptateur sortant
Nouvelle demande : envoyer un e-mail de confirmation après chaque réservation. Le cœur déclare un port sortant, notification.confirmer(reservation), et ignore tout du SMTP. Un adaptateur SMTP le réalise en production ; dans les tests, un faux qui garde les messages dans une liste ; plus tard, peut-être, un adaptateur qui poste un message dans la messagerie d'équipe. Le service n'a pas à changer quand on change d'adaptateur.
Clean architecture et architecture en oignon
Deux styles très proches portent d'autres noms :
- L'architecture en oignon (onion architecture, Jeffrey Palermo, 2008) dessine des cercles concentriques : le modèle du domaine au centre, puis les services du domaine, puis les services applicatifs, puis l'infrastructure et l'interface à l'extérieur.
- La clean architecture (Robert C. Martin, 2012) reprend les mêmes cercles : entités, cas d'utilisation, adaptateurs d'interface, frameworks et pilotes. Sa règle de dépendance dit que le code source ne peut dépendre que de cercles plus intérieurs.
Les trois styles partagent la même idée : isoler les règles de gestion de la technique, et inverser les dépendances pour que la technique dépende du métier. Ils diffèrent surtout par le vocabulaire et le nombre de cercles.
Couches ou hexagone ?
| Architecture en couches | Architecture hexagonale | |
|---|---|---|
| Image | Une pile : présentation, métier, données | Un centre et des adaptateurs autour |
| Sens des dépendances | Vers le bas : le métier dépend de l'accès aux données | Vers le centre : l'accès aux données dépend du métier (il implémente ses interfaces) |
| La base de données | Tout en bas, fondation de l'application | Un adaptateur parmi d'autres, remplaçable |
| Plusieurs interfaces | Possible, la présentation se multiplie | Prévu dès le départ : autant d'adaptateurs entrants que nécessaire |
| Coût | Faible, facile à expliquer | Plus d'interfaces et de fichiers, plus de vocabulaire |
Dans la version en couches « naïve », le service importe directement le Repository MySQL : il dépend de la couche du dessous. Dès qu'on fait dépendre le service d'une interface (chapitre 7), on a inversé cette dépendance, et l'architecture en couches devient, dans les faits, hexagonale. C'est pour cela que la frontière entre les deux est floue dans les projets réels.
Quand est-ce utile ?
L'architecture hexagonale paie quand les règles de gestion sont riches, que l'application doit vivre longtemps et qu'elle a plusieurs portes d'entrée ou de sortie (une API, une application de bureau, des imports, plusieurs bases). Pour un formulaire qui enregistre des contacts dans une table, c'est un excès : trois couches, ou même un MVC simple, suffisent.
Pour un projet de BTS, retiens la démarche plutôt que le nom : des règles isolées dans un service, des interfaces pour ce qui doit pouvoir être remplacé (la base, l'horloge, les e-mails), une racine de composition qui branche le tout. Si la commission te demande si ton application est hexagonale, tu peux répondre honnêtement : « elle en applique le principe, les règles de gestion ne dépendent d'aucune technologie, et je l'ai prouvé avec des tests qui tournent sans base ».
Exercices
Exercice 1 sur 3 Entrant ou sortant ? Réussi À revoir
Classe chaque élément : port entrant, port sortant, adaptateur entrant ou adaptateur sortant.
- Un contrôleur ASP.NET Core qui reçoit
POST /api/reservations. - L'interface
INotificationavec la méthodeConfirmer(Reservation r). - La classe
SmtpNotification, qui implémenteINotificationavec un serveur de messagerie. - La méthode
ReservationService.Annuler(int id, int employeId). - Un test unitaire qui appelle le service.
- Une classe
ReservationRepositoryPostgres.
Voir la réponseMasquer la réponse de l'exercice 1
- Adaptateur entrant : il traduit HTTP en appel au cœur.
- Port sortant : un besoin du cœur, exprimé sans technologie.
- Adaptateur sortant : il réalise le port avec une technologie précise.
- Port entrant : un cas d'utilisation que le cœur offre.
- Adaptateur entrant : un test pilote le cœur comme le ferait une interface.
- Adaptateur sortant : une autre réalisation du port d'accès aux réservations.
Tu avais trouvé ?
Exercice 2 sur 3 Trouver la violation Réussi À revoir
Dans un projet qui se dit hexagonal, ReservationService.cs commence par ces lignes. Lesquelles violent la règle de dépendance, et comment corriger ?
using System.Net.Mail;
using ResaSalles.Models;
using MySqlConnector;
using Microsoft.AspNetCore.Mvc;
Voir la réponseMasquer la réponse de l'exercice 2
using System.Net.Mail;: le cœur envoie lui-même des e-mails. Il faut un portINotificationet un adaptateur SMTP.using MySqlConnector;: le cœur parle à MySQL. Il faut passer parIReservationRepository.using Microsoft.AspNetCore.Mvc;: le cœur dépend du framework web (il renvoie sans doute desIActionResult). Il doit renvoyer des objets métier ou lever des exceptions métier ; le contrôleur traduit en HTTP.
using ResaSalles.Models; est correct : les entités font partie du cœur.
Tu avais trouvé ?
Exercice 3 sur 3 Un nouvel adaptateur Réussi À revoir
La direction veut que l'accueil puisse consulter le planning des salles sur un écran d'affichage qui interroge l'application toutes les minutes, et réserver depuis une borne tactile. Quels adaptateurs faut-il ajouter, et que faut-il modifier dans le cœur ?
Voir la réponseMasquer la réponse de l'exercice 3
Il faut ajouter des adaptateurs entrants : une route de lecture du planning dans l'API (ou une page dédiée) pour l'écran d'affichage, et l'interface de la borne, qui appelle la même API ou le même service. Le cœur ne change pas si le cas d'utilisation « consulter le planning » existe déjà ; sinon, on ajoute ce cas d'utilisation (une méthode de lecture du service ou du Repository) une fois, et tous les adaptateurs en profitent. La règle « pas de réservation dans le passé » s'applique à la borne sans une ligne de plus.
Tu avais trouvé ?