Monorepo avec Java : organisation du code en pratique
\nDans le développement Java moderne, la question du choix entre un monorepo (monorepository) et plusieurs dépôts distincts se pose de plus en plus fréquemment. Un monorepo est une approche où tout le code du projet (plusieurs services, bibliothèques, configurations) est stocké dans un seul dépôt Git. Dans cet article, nous allons voir comment organiser un monorepo avec Java en pratique : de la structure des dossiers à la configuration de la construction et du CI/CD. Vous obtiendrez des exemples de code prêts à l'emploi et des recommandations pour éviter les erreurs typiques.
\n\nPourquoi un monorepo pour Java ?
\nLe monorepo est particulièrement utile pour les projets Java où de nombreux modules dépendent les uns des autres. Les principaux avantages :
\n- \n
- Version unique des dépendances — tous les modules utilisent une seule version des bibliothèques, ce qui élimine les conflits. \n
- Refactorisation simplifiée — la modification d'une API dans un module est immédiatement visible dans tous les modules dépendants. \n
- Configuration de construction commune — Gradle ou Maven sont configurés une fois pour tous les modules. \n
- Intégrité du CI/CD — un seul pipeline pour tous les services, plus facile à suivre les modifications. \n
Cependant, le monorepo exige de la discipline : sans une structure claire, il se transforme en chaos. Examinons l'organisation correcte.
\n\nStructure d'un monorepo pour un projet Java
\nStructure recommandée pour un monorepo avec Java (exemple avec Gradle) :
\nmy-monorepo/\n├── build.gradle.kts # fichier de construction racine\n├── settings.gradle.kts # inclut tous les modules\n├── gradle.properties # propriétés communes\n├── libs/ # bibliothèques communes\n│ ├── common-utils/\n│ │ ├── build.gradle.kts\n│ │ └── src/\n│ └── logging/\n│ ├── build.gradle.kts\n│ └── src/\n├── services/ # microservices\n│ ├── user-service/\n│ │ ├── build.gradle.kts\n│ │ └── src/\n│ └── payment-service/\n│ ├── build.gradle.kts\n│ └── src/\n├── config/ # configurations (ex : checkstyle, spotbugs)\n│ └── checkstyle.xml\n└── docker/ # Dockerfile pour chaque service\n ├── user-service.Dockerfile\n └── payment-service.Dockerfile\n\nPrincipes clés :
\n- \n
- Les modules sont divisés en
libs(bibliothèques) etservices(applications exécutables). \n - Le fichier racine
build.gradle.ktsdéfinit les plugins et versions communs. \n - Le fichier
settings.gradle.ktsinclut tous les sous-projets. \n
Exemple de settings.gradle.kts :
rootProject.name = "my-monorepo"\n\ninclude(\n "libs:common-utils",\n "libs:logging",\n "services:user-service",\n "services:payment-service"\n)\n\n\nConfiguration des dépendances entre modules
\nLa tâche principale d'un monorepo est de gérer les dépendances internes. Prenons l'exemple où user-service dépend de common-utils.
Étape 1. Dans le fichier racine gradle.properties, déclarez les versions :
# gradle.properties\nkotlinVersion=1.9.0\nspringBootVersion=3.2.0\ncommonUtilsVersion=1.0.0\n\nÉtape 2. Dans le fichier racine build.gradle.kts, ajoutez les plugins et définissez les paramètres communs :
plugins {\n id("java-library") apply false\n id("org.springframework.boot") version "3.2.0" apply false\n id("io.spring.dependency-management") version "1.1.4" apply false\n}\n\nallprojects {\n group = "com.example"\n version = "1.0.0"\n\n repositories {\n mavenCentral()\n }\n}\n\nsubprojects {\n apply(plugin = "java-library")\n\n java {\n sourceCompatibility = JavaVersion.VERSION_17\n targetCompatibility = JavaVersion.VERSION_17\n }\n\n dependencies {\n implementation("org.slf4j:slf4j-api:2.0.9")\n testImplementation("org.j