Монорепозиторий с Java: организация кода на практике
В современной Java-разработке всё чаще встаёт вопрос выбора между монорепозиторием (monorepo) и множеством отдельных репозиториев. Монорепозиторий — это подход, при котором весь код проекта (несколько сервисов, библиотек, конфигураций) хранится в одном Git-репозитории. В этой статье мы разберём, как организовать монорепозиторий с Java на практике: от структуры папок до настройки сборки и CI/CD. Вы получите готовые примеры кода и рекомендации, которые помогут избежать типичных ошибок.
Почему монорепозиторий для Java?
Монорепозиторий особенно полезен для Java-проектов, где много модулей зависят друг от друга. Основные преимущества:
- Единая версия зависимостей — все модули используют одну версию библиотек, что исключает конфликты.
- Упрощённый рефакторинг — изменение API в одном модуле сразу видно во всех зависимых.
- Общая конфигурация сборки — Gradle или Maven настраиваются один раз для всех модулей.
- Целостность CI/CD — один пайплайн для всех сервисов, легче отслеживать изменения.
Однако монорепозиторий требует дисциплины: без чёткой структуры он превращается в хаос. Рассмотрим правильную организацию.
Структура монорепозитория для Java-проекта
Рекомендованная структура для монорепозитория с Java (на примере Gradle):
my-monorepo/
├── build.gradle.kts # корневой файл сборки
├── settings.gradle.kts # включает все модули
├── gradle.properties # общие свойства
├── libs/ # общие библиотеки
│ ├── common-utils/
│ │ ├── build.gradle.kts
│ │ └── src/
│ └── logging/
│ ├── build.gradle.kts
│ └── src/
├── services/ # микросервисы
│ ├── user-service/
│ │ ├── build.gradle.kts
│ │ └── src/
│ └── payment-service/
│ ├── build.gradle.kts
│ └── src/
├── config/ # конфигурации (например, checkstyle, spotbugs)
│ └── checkstyle.xml
└── docker/ # Dockerfile для каждого сервиса
├── user-service.Dockerfile
└── payment-service.Dockerfile
Ключевые принципы:
- Модули делятся на
libs(библиотеки) иservices(исполняемые приложения). - Корневой
build.gradle.ktsзадаёт общие плагины и версии. - Файл
settings.gradle.ktsвключает все подпроекты.
Пример settings.gradle.kts:
rootProject.name = "my-monorepo"
include(
"libs:common-utils",
"libs:logging",
"services:user-service",
"services:payment-service"
)
Настройка зависимостей между модулями
Главная задача монорепозитория — управлять внутренними зависимостями. Рассмотрим пример, когда user-service зависит от common-utils.
Шаг 1. В корневом gradle.properties объявляем версии:
# gradle.properties
kotlinVersion=1.9.0
springBootVersion=3.2.0
commonUtilsVersion=1.0.0
Шаг 2. В корневом build.gradle.kts подключаем плагины и задаём общие настройки:
plugins {
id("java-library") apply false
id("org.springframework.boot") version "3.2.0" apply false
id("io.spring.dependency-management") version "1.1.4" apply false
}
allprojects {
group = "com.example"
version = "1.0.0"
repositories {
mavenCentral()
}
}
subprojects {
apply(plugin = "java-library")
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
dependencies {
implementation("org.slf4j:slf4j-api:2.0.9")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}
tasks.withType {
useJUnitPlatform()
}
}
Шаг 3. В libs/common-utils/build.gradle.kts:
plugins {
`java-library`
}
dependencies {
implementation("org.apache.commons:commons-lang3:3.14.0")
}
Шаг 4. В services/user-service/build.gradle.kts подключаем внутреннюю библиотеку:
plugins {
id("org.springframework.boot")
id("io.spring.dependency-management")
}
dependencies {
implementation(project(":libs:common-utils")) // внутренняя зависимость
implementation("org.springframework.boot:spring-boot-starter-web")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
Теперь user-service может использовать классы из common-utils без публикации в Maven Central. Изменения в библиотеке сразу видны — это ускоряет разработку.
CI/CD для монорепозитория с Java
Одно из главных преимуществ монорепозитория — единый пайплайн. Однако нужно настроить умную сборку, чтобы не пересобирать все модули при изменении одного. Рассмотрим пример для GitHub Actions.
Файл .github/workflows/ci.yml:
name: Java CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Gradle dependencies
uses: actions/cache@v3
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build and test all modules