Монорепозиторий с Kotlin: организация кода и лучшие практики

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в монорепозиторий с Kotlin

Современная разработка сложных приложений часто сталкивается с проблемой управления кодом. Выбор между монорепозиторием и мультирепозиторием — одно из ключевых архитектурных решений. В этой статье мы разберем, как организовать монорепозиторий с Kotlin, используя лучшие практики и современные инструменты.

Монорепозиторий (monorepo) — это подход, при котором все проекты, библиотеки и сервисы хранятся в одном репозитории. Для Kotlin-разработчиков это особенно актуально благодаря мощной системе сборки Gradle и возможности использовать общий код между различными модулями.

Преимущества монорепозитория для Kotlin-проектов

Перед тем как перейти к организации, рассмотрим ключевые преимущества монорепозитория для Kotlin:

  • Единая версия зависимостей — все модули используют одну версию Kotlin, библиотек и плагинов, что устраняет конфликты версий.
  • Упрощенный рефакторинг — изменения в общей библиотеке автоматически проверяются всеми зависимыми модулями.
  • Централизованная конфигурация — общие настройки Gradle, линтеры и CI/CD конфигурируются один раз.
  • Атомарные коммиты — связанные изменения в разных модулях можно делать в одном коммите.
  • Повторное использование кода — общие утилиты, DTO и бизнес-логика легко доступны всем проектам.

Структура монорепозитория с Kotlin и Gradle

Рассмотрим оптимальную структуру директорий для Kotlin-монорепозитория с использованием Gradle Multi-Module:

my-monorepo/
├── build.gradle.kts          # Корневой build-файл
├── settings.gradle.kts       # Настройка модулей
├── gradle.properties         # Общие свойства
├── gradle/
│   └── libs.versions.toml    # Version Catalog
├── modules/
│   ├── common/               # Общие утилиты и модели
│   ├── domain/               # Бизнес-логика
│   ├── data/                 # Слой данных (API, БД)
│   ├── service-a/            # Микросервис A
│   ├── service-b/            # Микросервис B
│   └── shared-test/          # Общие тестовые утилиты
├── apps/
│   ├── backend/              # Основное приложение
│   └── cli-tool/             # CLI инструмент
└── config/
    ├── checkstyle/           # Конфигурация линтеров
    └── docker/               # Dockerfile для сервисов

Настройка settings.gradle.kts

Файл settings.gradle.kts определяет все модули проекта:

rootProject.name = "my-monorepo"

// Включаем все модули
include(
    ":modules:common",
    ":modules:domain",
    ":modules:data",
    ":modules:service-a",
    ":modules:service-b",
    ":apps:backend",
    ":apps:cli-tool"
)

// Плагин для управления версиями
pluginManagement {
    repositories {
        gradlePluginPortal()
        mavenCentral()
    }
}

Использование Version Catalog (libs.versions.toml)

Для централизованного управления зависимостями используем gradle/libs.versions.toml:

[versions]
kotlin = "1.9.22"
coroutines = "1.7.3"
ktor = "2.3.7"
exposed = "0.44.1"

[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }
exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }

[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

Пример build.gradle.kts для модуля common

plugins {
    alias(libs.plugins.kotlin.jvm)
}

dependencies {
    implementation(libs.kotlin.stdlib)
    implementation(libs.kotlinx.coroutines.core)
    
    testImplementation(kotlin("test"))
}

kotlin {
    jvmToolchain(17)
}

Организация кода: модули и зависимости

Правильная организация кода в монорепозитории критична для поддержки масштабируемости. Рассмотрим основные принципы:

Принцип разделения ответственности

Каждый модуль должен иметь четкую зону ответственности. Рекомендуется следующая иерархия:

  • common — базовые утилиты, расширения, общие типы данных (не содержит бизнес-логики).
  • domain — бизнес-логика, модели предметной области, порты для репозиториев.
  • data — реализация репозиториев, работа с API, БД, кэширование.
  • service-* — конкретные микросервисы или приложения.

Пример общего кода в модуле common

// modules/common/src/main/kotlin/com/example/common/Result.kt
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val message: String, val exception: Throwable? = null) : Result<Nothing>()
}

// modules/common/src/main/kotlin/com/example/common/extensions.kt
fun String.isValidEmail(): Boolean = 
    this.matches(Regex("^[A-Za-z0-9+_.-]+@(.+)$"))

Зависимости между модулями

Управление зависимостями должно быть направлено от конкретного к общему:

// modules/service-a/build.gradle.kts
dependencies {
    implementation(project(":modules:domain"))
    implementation(project(":modules:data"))
    implementation(libs.ktor.server.core)
}

// modules/domain/build.gradle.kts
dependencies {
    implementation(project(":modules:common"))
}

// modules/data/build.gradle.kts
dependencies {
    implementation(project(":modules:domain"))
    implementation(libs.exposed.core                

Блоги

Рекомендаций по книгам