Retour aux articles

Appliquer la Clean Architecture à une application Spring Boot 3.x : Étude de cas et implémentation

Appliquer la Clean Architecture à une application Spring Boot 3.x : Étude de cas et implémentation | Laty Gueye Samba - Développeur Full Stack Dakar Sénégal, Expert Java Spring Boot Angular

Appliquer la Clean Architecture à une application Spring Boot 3.x : Étude de cas et implémentation

Dans le monde du développement logiciel, la construction d'applications robustes, maintenables et évolutives est une quête constante. La Clean Architecture, popularisée par Robert C. Martin (Uncle Bob), offre un cadre puissant pour atteindre cet objectif. Elle propose une structure logicielle qui sépare les préoccupations, rendant le code indépendant des frameworks, des bases de données et même de l'interface utilisateur.

Pour un Développeur Full Stack Java Spring Boot + Angular comme Laty Gueye Samba, basé à Dakar, Sénégal, l'adoption de la Clean Architecture dans des projets Spring Boot est cruciale. Cette approche permet de créer des systèmes résilients, capables de s'adapter aux changements rapides des exigences métier, un atout majeur dans des contextes de développement exigeants tels que les applications de gestion hospitalière ou les systèmes de gestion des risques.

Cet article explore comment la Clean Architecture peut être appliquée efficacement à une application Spring Boot 3.x. Il détaille les principes fondamentaux, propose une structure de projet et fournit des exemples d'implémentation pour guider les développeurs vers des architectures plus propres et plus flexibles.

Les principes fondamentaux de la Clean Architecture et leur mapping Spring Boot

La Clean Architecture repose sur l'idée de couches concentriques, chacune ayant des responsabilités spécifiques et obéissant à la "Règle des Dépendances". Cette règle stipule que les dépendances doivent toujours pointer vers l'intérieur : les couches externes peuvent dépendre des couches internes, mais l'inverse est strictement interdit. Cela garantit l'indépendance du cœur métier.

Les couches principales sont généralement les suivantes :

  • Entities (Entités) : Le cœur de l'application, contenant les règles métier les plus générales et les plus stables. Elles sont indépendantes de tout.
  • Use Cases (Cas d'utilisation) : Contiennent les règles métier spécifiques à l'application. Ils orchestrent le flux de données vers et depuis les entités et demandent aux passerelles (ports) de persister les données.
  • Interface Adapters (Adaptateurs d'interface) : Convertissent les données des formats les plus commodes pour les cas d'utilisation et les entités vers les formats les plus commodes pour les frameworks et les bases de données externes. Cela inclut les contrôleurs, les présentateurs, et les passerelles de base de données.
  • Frameworks & Drivers (Frameworks et Pilotes) : La couche la plus externe, regroupant les détails d'implémentation comme le framework web (Spring Boot), la base de données (JPA, Hibernate), l'interface utilisateur, etc.

Pour une application Spring Boot, cette structure peut se traduire par les modules ou packages suivants :

  • domain : Contient les entités (POJO purs) et les interfaces de port (ex: ProductRepositoryPort). C'est le cœur métier agnostique.
  • application : Inclut les cas d'utilisation (services applicatifs) qui implémentent la logique métier et utilisent les ports définis dans domain.
  • infrastructure : Fournit les implémentations concrètes des ports définis dans domain, en utilisant des frameworks comme Spring Data JPA, ou des clients HTTP pour des services externes.
  • adapter : Contient les points d'entrée (Contrôleurs RESTful avec @RestController) qui appellent les cas d'utilisation, et les présentateurs/convertisseurs.

Voici un exemple d'interface d'entité et de port :


// domain/model/Product.java
package com.latysamba.cleanarch.domain.model;

public class Product {
    private String id;
    private String name;
    private double price;

    // Getters, Setters, Constructors...
    public Product(String id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public double getPrice() { return price; }
    public void setPrice(double price) { this.price = price; }
}

// domain/port/out/ProductRepositoryPort.java
package com.latysamba.cleanarch.domain.port.out;

import com.latysamba.cleanarch.domain.model.Product;
import java.util.List;
import java.util.Optional;

public interface ProductRepositoryPort {
    Product save(Product product);
    Optional<Product> findById(String id);
    List<Product> findAll();
    void deleteById(String id);
}

Implémentation pratique : Stratégies de découpage et gestion des dépendances

La mise en œuvre de la Clean Architecture dans un projet Spring Boot 3.x nécessite une structuration soignée des packages et une gestion rigoureuse des dépendances.

Structure des packages

Une structure de packages typique pourrait ressembler à ceci :


src/main/java/com/latysamba/cleanarch
├── application
│   └── usecase
│       ├── CreateProductUseCase.java
│       └── GetProductUseCase.java
│   └── service
│       └── ProductService.java // Implémente les Use Cases
├── domain
│   ├── model
│   │   └── Product.java // Entités métier pures
│   └── port
│       ├── in
│       │   ├── CreateProductCommand.java // DTOs d'entrée pour les Use Cases
│       │   └── CreateProductUseCase.java // Interface de Use Case
│       └── out
│           └── ProductRepositoryPort.java // Interface de port de persistance
├── infrastructure
│   ├── adapter
│   │   └── persistence
│   │       ├── ProductJpaAdapter.java // Implémente ProductRepositoryPort
│   │       └── entity
│   │           └── ProductJpaEntity.java // Entité JPA pour la base de données
│   ├── configuration
│   │   └── ApplicationConfig.java // Configuration des beans Spring
│   └── web
│       └── ProductController.java // Point d'entrée REST

Gestion des dépendances

La règle d'or est que les couches intérieures ne doivent jamais dépendre des couches extérieures. Pour ce faire, les couches internes définissent des interfaces (ports) que les couches externes implémentent (adaptateurs).

  • domain : Ne dépend de rien d'extérieur à lui-même.
  • application : Dépend de domain (utilise les entités et les ports).
  • infrastructure : Dépend de application et domain (implémente les ports définis dans domain et utilise les services définis dans application). Cette couche gère les détails techniques comme la base de données, les services externes, etc.
  • adapter (spécifiquement la partie web) : Dépend de application (pour appeler les cas d'utilisation).

Exemple d'implémentation d'un cas d'utilisation et de son adaptateur de persistance :


// application/service/ProductService.java (implémentation du Use Case)
package com.latysamba.cleanarch.application.service;

import com.latysamba.cleanarch.domain.model.Product;
import com.latysamba.cleanarch.domain.port.in.CreateProductCommand;
import com.latysamba.cleanarch.domain.port.in.CreateProductUseCase;
import com.latysamba.cleanarch.domain.port.out.ProductRepositoryPort;
import org.springframework.stereotype.Service;

@Service // Annotation Spring pour gérer ce service
public class ProductService implements CreateProductUseCase {

    private final ProductRepositoryPort productRepositoryPort;

    public ProductService(ProductRepositoryPort productRepositoryPort) {
        this.productRepositoryPort = productRepositoryPort;
    }

    @Override
    public Product createProduct(CreateProductCommand command) {
        // Logique métier spécifique au cas d'utilisation
        Product product = new Product(null, command.getName(), command.getPrice());
        // Validation ou autres règles métier peuvent être ici
        return productRepositoryPort.save(product);
    }
}

// infrastructure/adapter/persistence/ProductJpaAdapter.java (implémentation du port de persistance)
package com.latysamba.cleanarch.infrastructure.adapter.persistence;

import com.latysamba.cleanarch.domain.model.Product;
import com.latysamba.cleanarch.domain.port.out.ProductRepositoryPort;
import com.latysamba.cleanarch.infrastructure.adapter.persistence.entity.ProductJpaEntity;
import com.latysamba.cleanarch.infrastructure.adapter.persistence.repository.ProductJpaRepository;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Component // Annotation Spring pour gérer cet adaptateur
public class ProductJpaAdapter implements ProductRepositoryPort {

    private final ProductJpaRepository productJpaRepository;

    public ProductJpaAdapter(ProductJpaRepository productJpaRepository) {
        this.productJpaRepository = productJpaRepository;
    }

    @Override
    public Product save(Product product) {
        ProductJpaEntity entity = new ProductJpaEntity(product.getId(), product.getName(), product.getPrice());
        ProductJpaEntity savedEntity = productJpaRepository.save(entity);
        return new Product(savedEntity.getId(), savedEntity.getName(), savedEntity.getPrice());
    }

    @Override
    public Optional<Product> findById(String id) {
        return productJpaRepository.findById(id)
                .map(entity -> new Product(entity.getId(), entity.getName(), entity.getPrice()));
    }

    @Override
    public List<Product> findAll() {
        return productJpaRepository.findAll().stream()
                .map(entity -> new Product(entity.getId(), entity.getName(), entity.getPrice()))
                .collect(Collectors.toList());
    }

    @Override
    public void deleteById(String id) {
        productJpaRepository.deleteById(id);
    }
}

Enfin, le contrôleur web, qui fait partie de la couche la plus externe, injecte et appelle le cas d'utilisation :


// infrastructure/web/ProductController.java
package com.latysamba.cleanarch.infrastructure.web;

import com.latysamba.cleanarch.domain.model.Product;
import com.latysamba.cleanarch.domain.port.in.CreateProductCommand;
import com.latysamba.cleanarch.domain.port.in.CreateProductUseCase;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/products")
public class ProductController {

    private final CreateProductUseCase createProductUseCase;

    public ProductController(CreateProductUseCase createProductUseCase) {
        this.createProductUseCase = createProductUseCase;
    }

    @PostMapping
    public ResponseEntity<Product> createProduct(@RequestBody CreateProductCommand command) {
        Product newProduct = createProductUseCase.createProduct(command);
        return new ResponseEntity<>(newProduct, HttpStatus.CREATED);
    }
}

Point de vue : développeur full stack à Dakar

Pour un développeur travaillant sur des systèmes comme des applications de gestion des risques ou des plateformes ERP complexes, la maîtrise de la Clean Architecture représente un avantage concurrentiel réel sur le marché technologique africain, en pleine expansion. L'expertise d'un Développeur Full Stack à Dakar, notamment en Java Spring Boot, est grandement valorisée lorsqu'elle permet de construire des solutions modulaires et résilientes face aux contraintes du monde réel.

Conclusion

L'application de la Clean Architecture à une application Spring Boot 3.x offre des avantages considérables en termes de maintenabilité, de testabilité et de flexibilité. En séparant clairement les préoccupations et en adhérant à la Règle des Dépendances, les développeurs peuvent créer des systèmes dont le cœur métier reste indépendant des détails d'implémentation techniques.

Cette approche est particulièrement précieuse pour les professionnels comme Laty Gueye Samba, Développeur Full Stack Java Spring Boot + Angular à Dakar, Sénégal. Elle lui permet de bâtir des applications métier robustes et évolutives, répondant aux exigences des projets les plus complexes, qu'il s'agisse de solutions ERP ou de systèmes de gestion hospitalière. Adopter la Clean Architecture Spring Boot, c'est investir dans la longévité et la qualité du logiciel.

Pour approfondir ce sujet, il est recommandé de consulter les ressources officielles et les travaux de Robert C. Martin :

À 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