Введение: что такое монорепозиторий и зачем он нужен Kotlin-разработчику
Монорепозиторий (monorepo) — это подход, при котором код нескольких проектов или модулей хранится в одном репозитории. В контексте Kotlin это особенно удобно: вы можете объединить серверную часть (Ktor), библиотеки, CLI-инструменты и даже мобильные приложения (Kotlin Multiplatform) в единую структуру. Для начинающих разработчиков монорепозиторий упрощает навигацию, тестирование и совместную работу.
В этой статье мы разберем, как организовать монорепозиторий с Kotlin, используя Gradle Multi-Project Build. Вы узнаете, как правильно структурировать папки, настраивать зависимости и избегать типичных ошибок.
Основная часть: создание монорепозитория с Kotlin
1. Структура монорепозитория: базовые принципы
Правильная структура — залог успеха. В монорепозитории Kotlin каждый модуль — это отдельный подпроект Gradle. Вот типичная схема:
my-monorepo/
├── build.gradle.kts # Корневой файл сборки
├── settings.gradle.kts # Настройка модулей
├── gradle.properties # Общие свойства
├── shared/ # Общая библиотека (модуль)
│ ├── build.gradle.kts
│ └── src/
│ └── main/
│ └── kotlin/
│ └── com/example/shared/
│ └── Utils.kt
├── backend/ # Серверный модуль (Ktor)
│ ├── build.gradle.kts
│ └── src/
│ └── main/
│ └── kotlin/
│ └── com/example/backend/
│ └── Application.kt
└── cli-tool/ # CLI-инструмент
├── build.gradle.kts
└── src/
└── main/
└── kotlin/
└── com/example/cli/
└── Main.kt
Ключевое правило: один модуль — одна ответственность. Не смешивайте бизнес-логику и инфраструктуру в одном модуле. Это упрощает тестирование и переиспользование кода.
2. Настройка Gradle для монорепозитория
Начнем с корневого файла settings.gradle.kts. Он определяет, какие модули входят в проект:
// settings.gradle.kts
rootProject.name = "my-monorepo"
include(":shared")
include(":backend")
include(":cli-tool")
// Опционально: можно задать имена модулей через include()
// include(":libs:utils") — если модуль вложен в папку libs
Теперь корневой build.gradle.kts. Обычно он содержит только общие настройки (плагины, репозитории) и не имеет собственного кода:
// build.gradle.kts (корневой)
plugins {
kotlin("jvm") version "1.9.22" apply false
id("application") apply false
}
allprojects {
repositories {
mavenCentral()
}
}
subprojects {
apply(plugin = "kotlin")
// Общие настройки для всех подпроектов
dependencies {
implementation(kotlin("stdlib"))
}
}
Обратите внимание на apply false — это предотвращает применение плагина к корневому проекту. Каждый модуль сам решает, какие плагины ему нужны.
3. Пример модуля: общая библиотека (shared)
Модуль shared будет содержать переиспользуемые функции. Его build.gradle.kts:
// shared/build.gradle.kts
plugins {
kotlin("jvm")
}
dependencies {
// Внутренних зависимостей нет — это базовый модуль
testImplementation("org.jetbrains.kotlin:kotlin-test")
}
Сам код в Utils.kt:
// shared/src/main/kotlin/com/example/shared/Utils.kt
package com.example.shared
fun greet(name: String): String = "Hello, $name!"
fun add(a: Int, b: Int): Int = a + b
Теперь этот модуль можно подключить к другим модулям:
// backend/build.gradle.kts
plugins {
kotlin("jvm")
id("application")
}
dependencies {
implementation(project(":shared")) // Зависимость от модуля shared
implementation("io.ktor:ktor-server-netty:2.3.7")
}
application {
mainClass.set("com.example.backend.ApplicationKt")
}
В коде сервера вы сможете использовать com.example.shared.greet() — это и есть сила монорепозитория.
4. Зависимости между модулями: версионирование и common-конфигурация
Часто возникает необходимость вынести общие версии библиотек (например, Ktor, Coroutines) в одно место. Для этого используйте gradle.properties или файл libs.versions.toml (Version Catalog). Рассмотрим простой вариант через gradle.properties:
# gradle.properties
kotlin.version=1.9.22
ktor.version=2.3.7
coroutines.version=1.7.3
Теперь в build.gradle.kts любого модуля можно обращаться к этим версиям:
// backend/build.gradle.kts
dependencies {
implementation("io.ktor:ktor-server-netty:${property("ktor.version")}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${property("coroutines.version")}")
}
Более продвинутый способ — использовать Gradle Version Catalog. Создайте файл gradle/libs.versions.toml:
[versions]
kotlin = "1.9.22"
ktor = "2.3.7"
[libraries]
ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" }
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
Тогда в build.gradle.kts можно писать:
dependencies {
implementation(libs.ktor.server.netty)
}
Это упрощает обновление версий и делает код чище.
5. Советы по организации кода для начинающих
- Используйте единый стиль именования: папки модулей в нижнем регистре (kebab-case), пакеты — в нижнем (com