Maîtrisez votre architecture Spring Boot avec ArchUnit

Publié le 06/05/2025 Source : sfeir.dev

Dans le développement d’applications modernes, maintenir une architecture logicielle claire et cohérente est un défi constant.
Les applications Spring Boot, avec leur flexibilité et leur richesse fonctionnelle, peuvent rapidement devenir complexes si les règles architecturales ne sont pas respectées.

C’est là qu’intervient ArchUnit, une bibliothèque Java qui permet de tester et de valider l’architecture de votre code de manière automatisée.

Présentation d’ArchUnit

ArchUnit est une bibliothèque open-source conçue pour vérifier l’architecture d’une application Java à travers des tests unitaires. Contrairement aux outils traditionnels qui se concentrent sur la logique métier, ArchUnit analyse le bytecode pour s’assurer que la structure du code respecte des règles prédéfinies.

Par exemple, il peut vérifier que les contrôleurs ne dépendent que des services ou que les couches de persistance n’accèdent pas aux couches supérieures. ArchUnit est particulièrement adapté aux projets Spring Boot, car il permet de valider des conventions architecturales courantes, comme la séparation des responsabilités entre contrôleurs, services et repositories.

Avec une API fluide et intuitive, il s’intègre facilement dans des environnements de test comme JUnit.

L’intérêt de tester son architecture

Pourquoi investir du temps dans des tests d’architecture ? Voici quelques raisons clés :

En résumé, tester son architecture avec ArchUnit garantit que votre codebase reste propre, maintenable et conforme à vos principes de conception.

⚖️ Avantages et inconvénients

➕ Avantages

➖ Inconvénients

Cas pratique : Utilisation d’ArchUnit dans une application Spring Boot

Mise en place

Ajouter la dépendance suivante dans votre fichier pom.xml

<dependency>
    <groupId>com.tngtech.archunit</groupId>
    <artifactId>archunit-junit5</artifactId>
    <version>1.4.0</version>
    <scope>test</scope>
</dependency>

Et le tour est joué.

Exemple

Prenons une application Spring Boot avec son architecture classique :

Nous allons utiliser ArchUnit pour valider son architecture à l’aide de la classe de test suivante :

public class ArchitectureTest {

    private static JavaClasses importedClasses;

    @BeforeAll
    public static void setup() {
        // Importer toutes les classes du package de l'application
        importedClasses = new ClassFileImporter()
                .importPackages("fr.eletutour.archunit");
    }

    @Test
    public void controllersShouldOnlyDependOnServices() {
        ArchRule rule = classes()
            .that().areAnnotatedWith(RestController.class)
            .or().resideInAPackage("..controller..")
            .should().onlyDependOnClassesThat()
            .resideInAnyPackage("..service..", "..model..", "..exception..", "java..", "org.springframework..");

        rule.check(importedClasses);
    }

    @Test
    public void servicesShouldOnlyDependOnRepositories() {
        ArchRule rule = classes()
            .that().areAnnotatedWith(Service.class)
            .or().resideInAPackage("..service..")
            .should().onlyDependOnClassesThat()
            .resideInAnyPackage("..repository..", "..model..", "..exception..", "java..", "org.springframework..");

        rule.check(importedClasses);
    }

    @Test
    public void repositoriesShouldNotDependOnServicesOrControllers() {
        ArchRule rule = classes()
            .that().areAnnotatedWith(Repository.class)
            .or().resideInAPackage("..repository..")
            .should().onlyDependOnClassesThat()
            .resideInAnyPackage("java..",  "..model..", "org.springframework..");

        rule.check(importedClasses);
    }

    @Test
    public void noClassesShouldAccessControllers() {
        ArchRule rule = noClasses()
                .that()
                .resideInAnyPackage("..service..", "..repository..")
                .should()
                .dependOnClassesThat().resideInAPackage("..controller..");

        rule.check(importedClasses);
    }
}

Configurer les tests

Explications des règles

Si une règle est violée, ArchUnit fournira un message d’erreur détaillé indiquant la classe et la dépendance incriminée.

Exemple de violation

Supposons que je modifie mon controller et lui ajoute une dépendance directement vers le repository :

@RestController
@RequestMapping("/authors")
public class AuthorController {

    private final AuthorService authorService;
    private final AuthorRepository authorRepository;

    public AuthorController(AuthorService authorService, AuthorRepository authorRepository) {
        this.authorService = authorService;
        this.authorRepository = authorRepository;
    }

    @GetMapping
    public List<Author> getAuthors() throws AuthorNotFoundException {
        return authorService.getAuthors();
    }

    @GetMapping("/{id}")
    public Author getAuthorById(@PathVariable Long id) throws AuthorNotFoundException {
        return authorService.getAuthorById(id);
    }
}

Le test controllersShouldOnlyDependOnServices échouera et me fournira le message d’erreur suivant :

java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'classes that are annotated with @RestController or reside in a package '..controller..' should only depend on classes that reside in any package ['..service..', '..model..', '..exception..', 'java..', 'org.springframework..']' was violated (2 times):
Constructor <fr.eletutour.archunit.controller.AuthorController.<init>(fr.eletutour.archunit.service.AuthorService, fr.eletutour.archunit.repository.AuthorRepository)> has parameter of type <fr.eletutour.archunit.repository.AuthorRepository> in (AuthorController.java:0)
Field <fr.eletutour.archunit.controller.AuthorController.authorRepository> has type <fr.eletutour.archunit.repository.AuthorRepository> in (AuthorController.java:0)

Conclusion

Intégrer ArchUnit dans une application Spring Boot est une stratégie efficace pour maintenir une architecture propre et cohérente.
En automatisant la vérification des règles architecturales, ArchUnit réduit les dettes techniques et facilite la collaboration dans les équipes.
Bien qu’il nécessite un investissement initial pour apprendre son API et définir des règles pertinentes, les bénéfices à long terme sont indéniables.

Le cas pratique présenté montre comment appliquer ArchUnit à une application simple, mais ses principes peuvent être étendus à des projets plus complexes.

Alors, pourquoi ne pas essayer ArchUnit dans votre prochain projet Spring Boot ? Votre architecture vous remerciera !