Monorepo avec Java : comment organiser le code d'un grand projet
Dans le développement moderne de projets Java, la question se pose souvent : faut-il stocker tous les composants dans un seul dépôt (monorepo) ou les diviser en plusieurs dépôts distincts (multirepo) ? Le monorepo est une approche où le code de plusieurs projets, bibliothèques et services est stocké dans un système de contrôle de version unique. Dans cet article, nous verrons comment organiser efficacement le code Java dans un monorepo en utilisant Maven et Gradle, et nous examinerons des exemples pratiques.
Avantages et inconvénients du monorepo pour Java
Avant de passer à la mise en œuvre, il est important de comprendre quand le monorepo est justifié. Pour les projets Java, il est particulièrement utile si vous avez des bibliothèques communes, des modèles de données ou des interfaces utilisés par plusieurs services.
Avantages du monorepo
- Version unique des dépendances : tous les modules utilisent les mêmes versions de bibliothèques, ce qui élimine les conflits.
- Refactoring simple : la modification d'une API commune ou d'un modèle de données se fait de manière atomique dans un seul commit.
- CI/CD simplifié : un seul pipeline pour la construction et le test de tous les modules.
- Transparence : chaque développeur voit tout le code du projet, ce qui facilite la compréhension de l'architecture.
Inconvénients et comment les atténuer
- Taille du dépôt : avec le temps, il peut devenir énorme. Solution : utiliser
.gitignoreet ne pas stocker d'artefacts binaires. - Complexité de configuration du CI : il faut savoir exécuter uniquement les modules modifiés. Des outils comme Bazel ou Gradle Build Cache aident.
- Vérification de code longue : si toutes les modifications passent par une seule pull request. Solution : utiliser des branch policies et des déclencheurs automatiques.
Structure d'un monorepo avec Java : exemple pratique
Considérons une structure typique pour un monorepo Java utilisant Maven. Supposons que nous ayons trois modules : core (logique commune), service-api (API REST) et service-worker (gestionnaire en arrière-plan).
my-java-monorepo/
├── pom.xml # POM parent
├── core/
│ ├── pom.xml
│ └── src/
│ └── main/java/com/example/core/
│ └── Utils.java
├── service-api/
│ ├── pom.xml
│ └── src/
│ └── main/java/com/example/api/
│ └── MainController.java
├── service-worker/
│ ├── pom.xml
│ └── src/
│ └── main/java/com/example/worker/
│ └── WorkerTask.java
└── .gitignore
POM parent (pom.xml)
À la racine du projet, on crée un POM parent qui gère les versions et liste les modules :
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-java-monorepo</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>core</module>
<module>service-api</module>
<module>service-worker</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.2.0</version>
<type>pom</type>
<scope>import</scope>