Монорепозиторий с Java: организация кода на практике

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

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

Начать курс

Монорепозиторий с 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                

Блоги

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