Einleitung: Warum ein Monorepository in Kotlin?
\nIn der modernen Kotlin-Entwicklung stellt sich oft die Frage: Soll man mehrere zusammenhängende Projekte (Backend, Microservices, Bibliotheken, Android-Apps) in separaten Repositories (Polyrepository) oder in einem einzigen Monorepository zusammenfassen? Ein Monorepository ist eine Strategie zur Codeverwaltung, bei der sich alle Komponenten in einem einzigen Versionskontrollsystem befinden. Für Kotlin-Projekte ist dies aufgrund des leistungsstarken Build-Systems Gradle und der integrierten Unterstützung für Multi-Modularität besonders relevant.
\nIn diesem Artikel behandeln wir die praktischen Aspekte der Organisation eines Monorepositorys mit Kotlin: von der Verzeichnisstruktur bis zur Konfiguration von CI/CD und der Verwaltung von Abhängigkeiten. Sie erfahren, wie Sie typische Fehler vermeiden und den größtmöglichen Nutzen aus einem einheitlichen Code-Repository ziehen.
\n\n1. Struktur des Monorepositorys: Von der Theorie zur Praxis
\nDie richtige Struktur ist die Grundlage eines erfolgreichen Monorepositorys. Betrachten wir ein typisches Projekt, das eine REST-API mit Ktor, eine Bibliothek für die Datenbankarbeit und eine Android-App umfasst. Hier ist die empfohlene Hierarchie:
\nmy-monorepo/\n├── build.gradle.kts # Root-Build-Datei\n├── settings.gradle.kts # Modulkonfiguration\n├── gradle.properties # Gemeinsame Eigenschaften\n├── gradle/\n│ └── libs.versions.toml # Versionskatalog (Version Catalog)\n├── libs/ # Gemeinsame Bibliotheken\n│ ├── common-utils/ # Hilfsprogramme, Erweiterungen\n│ ├── common-db/ # Datenbankarbeit (Exposed)\n│ └── common-dto/ # Data Transfer Objects\n├── services/ # Serveranwendungen\n│ ├── user-service/ # Benutzer-Microservice\n│ └── order-service/ # Bestell-Microservice\n└── apps/ # Client-Anwendungen\n └── android-app/ # Android-App\n\nDas Schlüsselelement ist settings.gradle.kts. Es bindet alle Module ein:
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\nEine solche Struktur ermöglicht die einfache Wiederverwendung von Code zwischen Diensten und Client sowie die zentrale Verwaltung von Abhängigkeitsversionen.
\n\n1.1. Version Catalog: Abhängigkeitsverwaltung
\nIn einem Monorepository ist es entscheidend, die Duplizierung von Bibliotheksversionen zu vermeiden. Verwenden Sie den Version Catalog (Datei 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\nJetzt können Sie in jedem Modul schreiben:
\ndependencies {\n implementation(libs.ktor.server.core)\n implementation(libs.exposed.core)\n}\n\nDies stellt sicher, dass alle Module dieselben Versionen verwenden, und vereinfacht Massenaktualisierungen.
\n\n2. Praktische Modulorganisation: Gradle Multi-Project
\nJedes Modul des Monorepositorys ist ein separates Gradle-Unterprojekt mit seiner eigenen build.gradle.kts. Betrachten wir die Konfiguration der Bibliothek 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\nUnd hier ein typischer Microservice mit 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