Quand une IA écrit du code, le risque n'est pas qu'elle se trompe de syntaxe : les tests et le compilateur l'attrapent tout de suite. Le vrai risque, c'est qu'elle modifie une règle métier sans s'en rendre compte, parce qu'elle a été branchée au mauvais endroit. L'architecture hexagonale trace une frontière nette entre ce qui fait la valeur d'une application et la plomberie technique. Pour un agent IA, cette frontière vaut toutes les consignes du monde.
L'architecture hexagonale en deux phrases
Au centre, le domaine : les règles de votre métier et les cas d'usage (« enregistrer une opposition RGPD », « créer un radar de prospection »). Il ne sait rien de la base de données, du framework ou des API externes.
Autour, les adaptateurs : Laravel, PostgreSQL, l'API du BODACC, le modèle d'IA. Ils se branchent sur le domaine par des ports, de simples interfaces que le domaine définit et que l'infrastructure implémente.
Ce que ça change quand c'est une IA qui code
Un périmètre que l'on peut nommer
« Ajoute un adaptateur pour la nouvelle API » et « modifie une règle de qualification » sont deux tâches très différentes, et l'architecture les rend visibles dans l'arborescence. Les agents qui travaillent sur Le Limier ont des consignes explicites, écrites dans leur fichier de rôle :
❌ JAMAIS de app() ou Service Location dans Domain
❌ JAMAIS d'Eloquent dans Domain Layer
❌ JAMAIS de Laravel helpers dans Domain
❌ JAMAIS de code sans tests (Domain/Application)Quand une modification d'un agent touche app/__Domain__/ alors que la tâche concernait un adaptateur, je le vois dans le diff avant même de lire le code.
Des tests qui tournent sans base de données
Comme le cas d'usage ne dépend que d'une interface, on peut lui donner un faux, en mémoire, dans les tests. Pas de base à démarrer, pas de données à préparer : la suite du domaine tourne en quelques secondes. C'est ce qui permet de faire tourner les tests à chaque modification de l'agent, et pas seulement à la fin.
Changer de fournisseur sans toucher au métier
L'API Sirene, le BODACC, le modèle d'IA qui propose des angles de prospection : chacun est un adaptateur. Le jour où un fournisseur change ses conditions ou ses tarifs, on remplace l'adaptateur, et les règles métier, testées, ne bougent pas.
229
fichiers dans le domaine de Le Limier
36
cas d'usage, un dossier chacun
13
ports vers la persistance
7
contextes métier (RGPD, radars, open data…)
Un exemple réel : le registre des oppositions RGPD
Le Limier exploite des données publiques d'entreprises. Une entreprise peut s'opposer au traitement de ses données (article 21 du RGPD), et cette opposition doit survivre à chaque réimport des registres. Le domaine définit le port :
interface DataSubjectOptoutRepositoryInterface
{
public function register(string $siren, OptoutScope $scope, ?string $reason = null, ?string $requestedVia = null): void;
public function revoke(string $siren, OptoutScope $scope): void;
public function isFullyOptedOut(string $siren): bool;
// …
}Le cas d'usage ne connaît que cette interface. Il valide le SIREN, la portée de l'opposition, puis enregistre :
class Handler
{
public function __construct(
private DataSubjectOptoutRepositoryInterface $optouts,
) {}
public function handle(Input $input): Output
{
$siren = preg_replace('/\s+/', '', $input->siren) ?? '';
if (! Siren::isValid($siren)) {
return Output::failure("SIREN invalide : {$input->siren}");
}
// … portée de l'opposition, puis :
$this->optouts->register($siren, $scope, $input->reason, $input->requestedVia);
return Output::success($siren, $scope->value);
}
}En production, Laravel branche un adaptateur Eloquent. Dans les tests, un double en mémoire, avec ce commentaire qui dit tout : « le handler ne doit dépendre que du port ». Six tests couvrent les cas qui comptent : SIREN avec espaces, SIREN invalide, portée inconnue, opposition limitée aux dirigeants, et le fait qu'enregistrer deux fois la même opposition ne crée pas de doublon.
public function test_is_idempotent(): void
{
$this->handler->handle(new Input(siren: '732829320', reason: 'premier motif'));
$output = $this->handler->handle(new Input(siren: '732829320', reason: 'motif mis à jour'));
$this->assertTrue($output->success);
$this->assertCount(1, $this->optouts->all());
}Ce que l'IA fait mal, même avec des règles
Je préfère être franc : des consignes dans un prompt restent des intentions. En relisant le code de Le Limier pour cet article, j'ai compté à la main 9 fichiers du domaine qui importaient Laravel ou l'infrastructure : un client d'API branché directement dans un cas d'usage, une façade DB pour vérifier la fraîcheur des données. Mon propre agent de planification l'écrivait d'ailleurs dans son fichier : « 5 couplages Domain→Infrastructure », score 83/100.
Le problème venait aussi du contexte de l'agent, qui vieillit. Ce même fichier décrit un domaine de 59 fichiers ; il en compte 229 aujourd'hui. Un agent qui travaille avec une carte périmée prend de mauvaises décisions, en toute confiance.
La frontière vérifiée par la machine
J'ai donc ajouté un test d'architecture avec PHPat, qui s'exécute dans l'analyse statique PHPStan déjà en place. Une seule règle, lisible par n'importe qui :
PHPat::rule()
->classes(Selector::inNamespace('App\__Domain__'))
->shouldNotDependOn()
->classes(
Selector::inNamespace('App\__Infrastructure__'),
Selector::inNamespace('App\__Application__'),
Selector::inNamespace('Illuminate'),
// … contrôleurs, jobs, commandes, providers
)
->because("le Domain définit ses propres interfaces, implémentées dans l'Infrastructure.");Premier passage : la règle a trouvé plus de violations que mon comptage à la main, dont des dépendances vers la couche Application que mon grep n'avait pas vues. La correction a surtout consisté à remettre chaque chose à sa place :
- des objets métier qui vivaient dans l'infrastructure (un résumé d'import, un résultat de géocodage, les exceptions des API publiques) ont rejoint le domaine ;
- une interface de service définie côté Application est devenue un port du domaine ;
- les cas d'usage qui appelaient directement la base ou le cache passent désormais par des interfaces.
Résultat : zéro violation, et surtout, la prochaine dépendance interdite, qu'elle vienne de moi ou d'un agent, fait échouer l'analyse avant d'arriver dans le code.
Et si vous êtes client, pas développeur ?
Ce que vous y gagnez est concret :
- Une application qui évolue sans tout casser. Les règles de votre métier sont testées à part, elles ne bougent pas quand on change d'outil.
- Pas de dépendance à un fournisseur. Changer de prestataire d'emails, de paiement ou de modèle d'IA est un chantier circonscrit.
- Un code qu'un autre développeur peut reprendre. La structure dit où se trouve quoi.
C'est la base de notre méthode, avec des specs écrites avant le code et des tests à chaque étage. Pour voir l'architecture appliquée, la page Laravel et le projet Le Limier en donnent un aperçu.
