Architecture hexagonale (ports et adaptateurs)

English version

Guide complet de l'architecture hexagonale (Ports & Adapters) : isoler la logique métier de l'infrastructure, améliorer la testabilité et construire des systèmes flexibles qui évoluent avec les contraintes techniques.

Table des matières

1. Qu'est-ce que l'architecture hexagonale ?

L'architecture hexagonale, aussi appelée Ports and Adapters (ports et adaptateurs), a été introduite par Alistair Cockburn. L'objectif est de placer le cœur applicatif (domaine et cas d'usage) au centre du système et de traiter tout le reste — bases de données, interfaces utilisateur, brokers de messages, API tierces — comme des composants branchés via des interfaces bien définies.

Le nom évoque l'idée que l'application peut être abordée sous plusieurs angles (comme les côtés d'un hexagone). Chaque côté représente une façon différente d'interagir avec le monde extérieur : API HTTP, CLI, jobs batch, messagerie, persistance, etc.

Contrairement aux architectures en couches classiques où les dépendances pointent souvent vers l'infrastructure, l'architecture hexagonale applique l'inversion de dépendances : le domaine définit ce dont il a besoin (ports), et l'infrastructure fournit les implémentations (adaptateurs).

flowchart TB subgraph Inbound["Adaptateurs entrants"] REST[Contrôleur REST] CLI[CLI / Planificateur] MSG_IN[Consommateur de messages] end subgraph Core["Cœur applicatif"] UC[Cas d'usage / Services] DOM[Modèle de domaine] end subgraph Outbound["Adaptateurs sortants"] REPO[Repository base de données] API[Client API externe] MSG_OUT[Éditeur d'événements] end REST --> UC CLI --> UC MSG_IN --> UC UC --> DOM UC --> REPO UC --> API UC --> MSG_OUT style Core fill:#e1f5ff,stroke:#0273bd,stroke-width:2px style Inbound fill:#fff4e1,stroke:#f57c00,stroke-width:2px style Outbound fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px

2. Pourquoi utiliser les ports et adaptateurs ?

  • Testabilité : remplacer bases de données ou API réelles par des adaptateurs en mémoire ou des mocks lors des tests.
  • Indépendance technologique : passer de PostgreSQL à MongoDB, ou de REST à gRPC, sans réécrire les règles métier.
  • Frontières claires : les ports documentent le contrat entre la logique métier et l'extérieur.
  • Évolutivité : ajouter de nouveaux canaux (API mobile, GraphQL, événements) via de nouveaux adaptateurs.
  • Alignement avec le DDD : s'accorde naturellement avec le domain-driven design et la clean architecture.

3. Concepts fondamentaux

3.1. Cœur applicatif

Le cœur contient les entités, objets valeur, services de domaine et services applicatifs (cas d'usage). Il ne doit pas dépendre des frameworks, annotations ORM ou détails HTTP.

3.2. Ports

Un port est une interface qui définit comment l'application communique avec l'extérieur.

  • Ports entrants (driving) : API exposées à l'extérieur (ex. PlaceOrderUseCase).
  • Ports sortants (driven) : abstractions dont l'application a besoin (ex. OrderRepository, PaymentGateway).

3.3. Adaptateurs

Un adaptateur implémente un port pour une technologie donnée. Les adaptateurs entrants appellent les ports entrants ; les adaptateurs sortants implémentent les ports sortants.

4. Ports et adaptateurs en pratique

4.1. Exemple d'adaptateur entrant

Un contrôleur REST Spring traduit les requêtes HTTP en appels de cas d'usage :

@RestController
@RequestMapping("/orders")
public class OrderController {
    private final PlaceOrderUseCase placeOrder;

    public OrderController(PlaceOrderUseCase placeOrder) {
        this.placeOrder = placeOrder;
    }

    @PostMapping
    public ResponseEntity<OrderResponse> create(@RequestBody CreateOrderRequest request) {
        OrderId id = placeOrder.execute(request.toCommand());
        return ResponseEntity.status(HttpStatus.CREATED).body(OrderResponse.from(id));
    }
}

4.2. Port sortant et adaptateur

// Port sortant (module application)
public interface OrderRepository {
    void save(Order order);
    Optional<Order> findById(OrderId id);
}

// Adaptateur sortant (module infrastructure)
@Repository
public class JpaOrderRepository implements OrderRepository {
  private final OrderJpaRepository jpa;

  @Override
  public void save(Order order) {
    jpa.save(OrderEntity.from(order));
  }

  @Override
  public Optional<Order> findById(OrderId id) {
    return jpa.findById(id.value()).map(OrderEntity::toDomain);
  }
}

5. Exemple de mise en œuvre

Organisation modulaire typique en Java :

  • domain — entités, objets valeur, événements de domaine
  • application — cas d'usage, interfaces de ports entrants/sortants
  • infrastructure — adaptateurs JPA, REST, messagerie
  • bootstrap — configuration Spring, câblage des dépendances

Règle de dépendance : infrastructure → application → domain. Le module domaine n'a aucune dépendance vers les frameworks.

6. Architecture hexagonale vs architecture en couches

Dans un modèle 3 tiers classique (présentation → métier → données), la couche métier dépend souvent des frameworks de persistance. L'architecture hexagonale rend les dépendances explicites via les ports et isole le cœur.

  • En couches : organisé par rôle technique (UI, services, DAO).
  • Hexagonale : organisé par domaine et direction d'interaction (entrant vs sortant).

Les deux approches peuvent coexister : structurer les packages par couche dans chaque module adaptateur tout en préservant les frontières de ports au niveau applicatif.

7. Bonnes pratiques

  • Adaptateurs fins : mapping, validation et traduction de protocole uniquement — pas de règles métier.
  • Définir les ports dans la couche application : pas dans l'infrastructure.
  • DTO à la frontière : ne pas faire fuiter les entités JPA ou modèles HTTP dans le domaine.
  • Tester le cœur sans Spring : tests unitaires purs avec adaptateurs factices.
  • Éviter un cœur anémique : placer la logique métier dans le domaine, pas seulement dans les services.

8. Quand utiliser l'architecture hexagonale

Adapté lorsque :

  • le domaine est non trivial et durable ;
  • vous avez besoin d'une forte couverture de tests sans infrastructure lourde ;
  • plusieurs interfaces (API, événements, CLI) partagent les mêmes cas d'usage ;
  • l'infrastructure est susceptible de changer (migration cloud, changement de base).

Peut être excessif lorsque :

  • l'application est un prototype CRUD simple ;
  • l'équipe est petite et le domaine stable et peu complexe ;
  • la vitesse de livraison prime sur la modularité à long terme.

9. Conclusion

L'architecture hexagonale aide les équipes à construire des systèmes où la logique métier reste indépendante des frameworks et de l'infrastructure. En définissant des ports et adaptateurs explicites, vous gagnez en flexibilité, en structure et en qualité de tests — surtout dans les domaines complexes qui doivent évoluer sur plusieurs années.

Combinée au DDD et à des règles de dépendance claires, cette approche permet de maîtriser la complexité sans sacrifier la vélocité une fois le patron maîtrisé par l'équipe.

Résumé : Architecture hexagonale (Ports & Adapters)

Problème / besoin : Les applications deviennent souvent fortement couplées aux frameworks, bases de données et interfaces utilisateur, ce qui complique les tests, l'évolution et le remplacement des composants techniques.

Solution proposée : L'architecture hexagonale place le domaine métier au centre et connecte l'extérieur via des ports et adaptateurs explicites, isolant la logique applicative des détails techniques.

Principe d'implémentation : Elle est généralement mise en œuvre avec un cœur domaine entouré d'adaptateurs entrants (contrôleurs REST, listeners) et sortants (repositories, clients API), souvent avec l'inversion de dépendances et des frameworks comme Spring Boot.

Post a Comment

0 Comments