Retour aux articles

Architecture Hexagonale et Clean Code avec Spring Boot : Guide pour des projets maintenables

Architecture Hexagonale et Clean Code avec Spring Boot : Guide pour des projets maintenables | Laty Gueye Samba - Développeur Full Stack Dakar Sénégal, Expert Java Spring Boot Angular

Architecture Hexagonale et Clean Code avec Spring Boot : Guide pour des projets maintenables

Introduction

Ce guide présente une approche pragmatique pour appliquer l'architecture hexagonale (ports et adaptateurs) associée aux principes de Clean Code dans des projets Spring Boot. L'objectif est de favoriser la maintenabilité, la testabilité et l'évolution du logiciel en séparant clairement la logique métier des mécanismes d'infrastructure.

Principes fondamentaux

Domain first et séparation des responsabilités

La logique métier (domaine) doit rester indépendante des frameworks et technologies. Les cas d'utilisation (application) orchestrent les règles métiers sans connaître les détails d'infrastructure. Les adaptateurs inbound/outbound implémentent des ports définis par le domaine ou l'application.

Dépendance inversion et testabilité

Les couches externes dépendent des ports (interfaces) définis au cœur. Cette inversion de dépendance permet d'isoler le domaine et d'écrire des tests unitaires rapides en simulant les ports. Les adaptateurs peuvent être testés séparément avec des tests d'intégration.

Organisation des couches dans un projet Spring Boot

Structure recommandée

Une organisation simple et cohérente facilite la navigation et les responsabilités :

src/main/java/com/example/ domain/ // entités, value objects, règles métier application/ // cas d'utilisation, DTOs, interfaces (ports) adapters/ // inbound (rest), outbound (persistence, messaging) config/ // configuration Spring, beans partagés

Règle d'or

Le domaine ne doit contenir aucune dépendance Spring. Les classes du domaine restent de simples POJOs, ce qui garantit indépendance et portabilité.

Exemple minimal de code

Définition d'un port (interface)

public interface OrderPort { Order save(Order order); Optional<Order> findById(Long id); }

Use case dans la couche application

public class CreateOrderUseCase { private final OrderPort orderPort; public CreateOrderUseCase(OrderPort orderPort) { this.orderPort = orderPort; } public Order execute(CreateOrderCommand cmd) { // validation et logique métier return orderPort.save(cmd.toOrder()); } }

Adaptateur de persistance (outbound)

public interface SpringOrderRepository extends JpaRepository<OrderEntity, Long> { } @Repository public class OrderRepositoryAdapter implements OrderPort { private final SpringOrderRepository repository; public OrderRepositoryAdapter(SpringOrderRepository repository) { this.repository = repository; } public Order save(Order order) { return repository.save(mapToEntity(order)).toDomain(); } }

Adaptateur inbound (REST controller)

@RestController @RequestMapping("/orders") public class OrderController { private final CreateOrderUseCase createOrderUseCase; public OrderController(CreateOrderUseCase createOrderUseCase) { this.createOrderUseCase = createOrderUseCase; } @PostMapping public ResponseEntity<OrderDto> create(@RequestBody CreateOrderDto dto) { Order order = createOrderUseCase.execute(dto.toCommand()); return ResponseEntity.status(HttpStatus.CREATED).body(OrderDto.from(order)); } }

Tests et stratégies

Tests unitaires

Les use cases sont testés isolément en simulant les ports via des doubles (mocks ou stubs). Ces tests garantissent la logique métier sans dépendre d'une base de données ou d'un broker.

Tests d'intégration

Les adaptateurs sont testés avec des slices Spring (@DataJpaTest, @WebMvcTest) ou des tests d'intégration complets (@SpringBootTest). Les scénarios de bout en bout valident l'assemblage des couches.

Bonnes pratiques Clean Code

Noms explicites et petites fonctions

Préférer des noms clairs et concis pour les classes et méthodes. Les fonctions doivent rester petites et exprimer une seule intention. Le code lisible réduit le coût de la maintenance.

Éviter la duplication

Extraire les comportements communs dans des services réutilisables ou des utilitaires bien testés. Les DTOs et mappers doivent être simples et centralisés pour éviter la duplication des conversions.

Gestion des erreurs

Utiliser des exceptions métiers explicites et des classes de domaine pour représenter les invariants. Dans les contrôleurs, transformer ces exceptions en réponses HTTP appropriées via des handlers globaux.

Transactions et frontières

Définir clairement la frontière transactionnelle. Il est fréquent d'appliquer @Transactional sur les classes de l'application (use cases) exposées comme beans Spring afin de garantir l'atomicité des opérations métier, tout en gardant le domaine détaché des mécanismes transactionnels.

Évolution et maintenance

La modularité offerte par l'architecture hexagonale facilite l'évolution : remplacement d'une base de données, ajout d'une file de messages ou refonte d'une interface utilisateur sans impact sur le domaine. Les tests automatisés et la documentation des ports accélèrent la compréhension des changements.

Conclusion

En combinant l'architecture hexagonale et les principes de Clean Code, les projets Spring Boot gagnent en robustesse, testabilité et extensibilité. La séparation claire des responsabilités, l'utilisation d'interfaces pour inverser les dépendances et des conventions de code strictes contribuent à un code plus maintenable et à une livraison plus prévisible.

À propos de l'auteur

Laty Gueye Samba est développeur Full Stack basé à Dakar, Sénégal. Spécialiste des écosystèmes Java / Spring Boot et Angular.

Contact : latygueyesamba@gmail.com  |  Dakar, Sénégal