Introduction : pourquoi un monorepo en Kotlin ?
\nDans le développement moderne en Kotlin, un dilemme se pose souvent : stocker plusieurs projets liés (backend, microservices, bibliothèques, applications Android) dans des dépôts séparés (polyrepo) ou les regrouper dans un seul monorepo. Le monorepo est une stratégie de stockage de code où tous les composants se trouvent dans un système de contrôle de version unique. Pour les projets Kotlin, cela est particulièrement pertinent grâce au puissant système de build Gradle et au support intégré du multi-module.
\nDans cet article, nous aborderons les aspects pratiques de l'organisation d'un monorepo avec Kotlin : de la structure des répertoires à la configuration du CI/CD et à la gestion des dépendances. Vous apprendrez à éviter les erreurs courantes et à tirer le meilleur parti d'un dépôt de code unique.
\n\n1. Structure du monorepo : de la théorie à la pratique
\nUne structure correcte est la base d'un monorepo réussi. Considérons un projet type comprenant une API REST avec Ktor, une bibliothèque pour interagir avec la base de données et une application Android. Voici la hiérarchie recommandée :
\nmy-monorepo/\n├── build.gradle.kts # Fichier de build racine\n├── settings.gradle.kts # Configuration des modules\n├── gradle.properties # Propriétés communes\n├── gradle/\n│ └── libs.versions.toml # Catalogue de versions (Version Catalog)\n├── libs/ # Bibliothèques communes\n│ ├── common-utils/ # Utilitaires, extensions\n│ ├── common-db/ # Travail avec la BD (Exposed)\n│ └── common-dto/ # Data Transfer Objects\n├── services/ # Applications serveur\n│ ├── user-service/ # Microservice utilisateurs\n│ └── order-service/ # Microservice commandes\n└── apps/ # Applications clientes\n └── android-app/ # Application Android\n\nL'élément clé est settings.gradle.kts. Il inclut tous les modules :
rootProject.name = "my-monorepo"\n\ninclude("libs:common-utils")\ninclude("libs:common-db")\ninclude("libs:common-dto")\ninclude("services:user-service")\ninclude("services:order-service")\ninclude("apps:android-app")\n\nCette structure permet de réutiliser facilement le code entre les services et le client, ainsi que de gérer centralement les versions des dépendances.
\n\n1.1. Version Catalog : gestion des dépendances
\nDans un monorepo, il est essentiel d'éviter la duplication des versions de bibliothèques. Utilisez le Version Catalog (fichier gradle/libs.versions.toml) :
[versions]\nkotlin = "2.0.21"\nktor = "3.0.3"\nexposed = "0.57.0"\n\n[libraries]\nktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }\nexposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }\n\n[plugins]\nkotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }\n\nDésormais, dans n'importe quel module, vous pouvez écrire :
\ndependencies {\n implementation(libs.ktor.server.core)\n implementation(libs.exposed.core)\n}\n\nCela garantit que tous les modules utilisent les mêmes versions et simplifie les mises à jour massives.
\n\n2. Organisation pratique des modules : Gradle Multi-Project
\nChaque module du monorepo est un sous-projet Gradle distinct avec son propre build.gradle.kts. Considérons la configuration de la bibliothèque common-utils :
// libs/common-utils/build.gradle.kts\nplugins {\n alias(libs.plugins.kotlin.jvm)\n}\n\ngroup = "com.mycompany.common"\nversion = "1.0.0"\n\ndependencies {\n implementation(kotlin("stdlib"))\n implementation(libs.kotlinx.coroutines)\n}\n\nEt voici un microservice type avec Ktor :
\n// services/user-service/build.gradle.kts\nplugins {\n alias(libs.plugins.kotlin.jvm)\n alias(libs.plugins.ktor)\n}\n\ndependencies {\n implementation(project(":libs:common-utils"))\n implementation(project(":libs:common-dto"))\n impl