Architecture Hexagonale en Java : logique pure sans douleur

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : pourquoi votre application n'est pas sa base de données

\n\n

Imaginez que vous construisez une maison. Fondations, murs, toit — tout est pensé. Mais au bout d'un an, vous décidez de changer le câblage. Et là, vous découvrez que les fils sont noyés dans le béton et les prises soudées aux armatures. Pour changer un seul interrupteur, vous devez démolir la moitié de la maison.

\n\n

C'est à peu près à quoi ressemble une application Java classique, où la logique métier est étroitement imbriquée avec le framework, la base de données ou le contrôleur REST. Vous voulez passer de PostgreSQL à MongoDB ? Réécrire les contrôleurs de Spring MVC à JAX-RS ? Bienvenue dans l'enfer du refactoring.

\n\n

La solution — l'architecture hexagonale (également appelée « Ports et Adaptateurs », ou « Clean Architecture » dans l'interprétation d'Alistair Cockburn). Ce n'est pas une solution miracle, mais un moyen de rendre votre application flexible, testable et indépendante du monde extérieur.

\n\n

Aujourd'hui, nous allons décortiquer l'architecture hexagonale en Java avec des exemples de code réels, sans blabla ni réflexions abstraites. C'est parti.

\n\n

Qu'est-ce que l'architecture hexagonale (Ports et Adaptateurs) ?

\n\n

Le principe est simple : votre application est un noyau (core) qui contient la logique métier. Autour du noyau se trouve un « hexagone » (en réalité un nombre arbitraire de faces), à travers lequel le monde extérieur communique avec le noyau.

\n\n

Chaque face a son propre port (interface). Et un adaptateur est l'implémentation du port pour une technologie spécifique. Par exemple :

\n
    \n
  • Port « UserRepository » — adaptateur « JpaUserRepository » ou « MongoUserRepository »
  • \n
  • Port « SendNotification » — adaptateur « EmailSender » ou « SmsSender »
  • \n
\n\n

La règle principale : le noyau ne sait rien des bibliothèques externes, bases de données, frameworks web. Il ne connaît que ses ports (interfaces). Cela offre plusieurs avantages :

\n
    \n
  • Indépendance de l'infrastructure — vous changez de base de données ou de framework sans toucher à la logique.
  • \n
  • Testabilité — la logique métier peut être testée sans démarrer le contexte Spring ou la base de données.
  • \n
  • Lisibilité — le code du noyau raconte l'histoire du domaine, et non les détails techniques.
  • \n
\n\n

Structure du projet : comment organiser le code

\n\n

Il n'existe pas de structure de paquets unique et correcte, mais je propose une version éprouvée basée sur le DDD (Domain-Driven Design) et les principes de l'hexagonale :

\n\n
com.example.app\n├── domain\n│   ├── model          // Entités, Value Objects\n│   ├── port           // Interfaces des ports (inbound/outbound)\n│   └── service        // Services métier (use cases)\n├── adapter\n│   ├── inbound\n│   │   ├── rest       // Contrôleurs REST (adaptateurs entrants)\n│   │   └── messaging  // Écouteurs Kafka/RabbitMQ\n│   └── outbound\n│       ├── persistence // Référentiels JPA, JDBC, MongoDB\n│       └── client      // Clients HTTP, API externes\n└── config             // Configuration Spring (si utilisé)\n
\n\n

Notez que domain n'importe rien de adapter. C'est critique. Si vous voyez @Entity ou @RestController dans domain, cela signifie que l'architecture est cassée.

\n\n

Exemple : système de gestion de commandes

\n\n

Concevons un système simple : un utilisateur peut créer une commande, et le système vérifie la disponibilité des produits en stock et envoie une notification.

\n\n

Étape 1. Modèle du domaine (domain/model)

\n\n
// Order.java\npublic class Order {\n    private final OrderId id;\n    private final List<OrderItem> items;\n    private OrderStatus status;\n\n    public Order(List<OrderItem> items) {\n        this.id = new OrderId(UUID.randomUUID().toString());\n        this.items = Collections.unmodifiableList(items);\n        this.status = OrderStatus.PENDING;\n    }\n\n    public void confirm() {\n        if (status != OrderStatus.PENDING) {\n            throw new IllegalStateException("Only PENDING orders can be confirmed");\n        }\n        this.status = Ord                

Blogs

Book Recommendations