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

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

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

Начать курс

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

С ростом проектов и увеличением числа сервисов перед разработчиками встаёт вопрос: как организовать код так, чтобы он оставался управляемым, легко тестируемым и быстро разворачиваемым? Один из популярных подходов — монорепозиторий (monorepo). В этой статье мы разберём, как эффективно организовать монорепозиторий на Kotlin с использованием Gradle, какие инструменты и паттерны применять, и как избежать типичных ошибок.

Что такое монорепозиторий и зачем он нужен?

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

Преимущества монорепозитория:

  • Единая версионизация — все модули используют одну версию Kotlin и общих зависимостей.
  • Упрощённый рефакторинг — изменения в общей библиотеке сразу видны всем потребителям.
  • Общая настройка CI/CD — один пайплайн для всех модулей.
  • Улучшенная коллаборация — разработчики видят весь код проекта.

Однако монорепозиторий требует дисциплины: без правильной организации он может превратиться в «монолит» с запутанными зависимостями. Kotlin и Gradle предоставляют отличные инструменты для борьбы с этим.

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

Рассмотрим типовую структуру проекта, где есть backend-сервис на Ktor, общая библиотека для моделей данных и Android-приложение:

my-monorepo/
├── build.gradle.kts          # корневой файл сборки
├── settings.gradle.kts       # подключение модулей
├── gradle.properties         # общие версии
├── common/
│   ├── build.gradle.kts
│   └── src/
│       └── main/kotlin/
│           └── com/example/common/
│               └── Models.kt
├── backend/
│   ├── build.gradle.kts
│   └── src/
│       └── main/kotlin/
│           └── com/example/backend/
│               └── Application.kt
├── android-app/
│   ├── build.gradle.kts
│   └── src/
│       └── main/kotlin/
│           └── com/example/android/
│               └── MainActivity.kt
└── ci/
    └── build.sh

Файл settings.gradle.kts подключает модули:

rootProject.name = "my-monorepo"

include(":common")
include(":backend")
include(":android-app")

Корневой build.gradle.kts обычно содержит только общие плагины и настройки:

plugins {
    kotlin("jvm") version "1.9.22" apply false
    kotlin("android") version "1.9.22" apply false
    id("org.jetbrains.kotlin.plugin.serialization") version "1.9.22" apply false
}

allprojects {
    group = "com.example"
    version = "1.0.0"
}

Важно: плагины указываются с apply false, чтобы они не применялись к корневому проекту, а были доступны дочерним модулям.

Управление зависимостями в монорепозитории

Одна из главных задач — избежать дублирования версий зависимостей. Для этого используют Gradle Version Catalog (файл libs.versions.toml в папке gradle). Пример:

# gradle/libs.versions.toml
[versions]
kotlin = "1.9.22"
ktor = "2.3.7"
coroutines = "1.7.3"

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

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

Теперь в модулях можно ссылаться на каталог:

// backend/build.gradle.kts
plugins {
    alias(libs.plugins.kotlin.jvm)
    alias(libs.plugins.kotlin.serialization)
}

dependencies {
    implementation(project(":common"))
    implementation(libs.ktor.server.core)
    implementation(libs.ktor.server.netty)
    implementation(libs.kotlinx.coroutines)
}

Такой подход гарантирует, что все модули используют одинаковые версии библиотек, а при обновлении достаточно изменить один файл.

Организация модулей: общие библиотеки и изоляция

В монорепозитории важно чётко разделять ответственность модулей. Рекомендуется выделить:

  • Common / Shared — модуль с моделями данных, DTO, общими утилитами. Он не должен зависеть от фреймворков.
  • Domain — бизнес-логика, которая не зависит от инфраструктуры.
  • Infrastructure — работа с БД, внешними API, кэшированием.
  • Application / Service — точки входа (Ktor, Spring Boot, Android Activity).

Пример модуля common:

// common/build.gradle.kts
plugins {
    alias(libs.plugins.kotlin.jvm)
    alias(libs.plugins.kotlin.serialization)
}

dependencies {
    implementation(libs.kotlinx.serialization.json)
    // никаких зависимостей от Ktor или Android!
}

Код внутри common:

// common/src/main/kotlin/com/example/common/Models.kt
package com.example.common

import kotlinx.serialization.Serializable

@Serializable
data class User(
    val id: Long,
    val name: String,
    val email: String
)

Backend-сервис использует этот модуль через implementation(

Блоги

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