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

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

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

Начать курс

Введение: что такое монорепозиторий и зачем он нужен в Java

В мире разработки Java существует два основных подхода к хранению кода: монорепозиторий (monorepo) и мультирепозиторий (multirepo). Монорепозиторий — это стратегия, при которой все кодовые базы проекта (библиотеки, сервисы, утилиты, конфигурации) хранятся в одном репозитории. Для Java-разработчиков, работающих с крупными корпоративными системами или микросервисами, монорепозиторий становится всё более популярным решением.

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

  • Единая версия зависимостей: Все модули используют одну версию библиотек, что исключает конфликты (classpath hell).
  • Упрощённый рефакторинг: Изменение API в одном модуле сразу видно во всех зависимых модулях.
  • Сквозная история изменений: Один коммит может содержать изменения в нескольких модулях, что упрощает отладку.
  • Стандартизация: Единые правила кодстайла, линтинга и CI/CD для всего проекта.

Однако монорепозиторий требует грамотной организации. В этой статье мы разберём, как правильно структурировать Java-проект в монорепозитории, используя Maven и Gradle.

Основная часть: организация монорепозитория в Java

1. Структура каталогов: модульный подход

Основа монорепозитория — модульная структура. Каждый модуль представляет собой независимую единицу (библиотеку, сервис, утилиту). Рассмотрим типовую структуру для Java-проекта:

my-monorepo/
├── build.gradle          # Корневой файл сборки (Gradle)
├── settings.gradle       # Список модулей
├── gradle.properties     # Общие настройки
├── gradle/
│   └── wrapper/
├── common/               # Общие библиотеки
│   ├── common-utils/     # Утилиты (строки, даты)
│   └── common-dto/       # DTO для обмена данными
├── services/             # Микросервисы
│   ├── user-service/     # Сервис пользователей
│   └── order-service/    # Сервис заказов
├── libs/                 # Внутренние библиотеки
│   └── logging-lib/      # Библиотека логирования
├── tests/                # Интеграционные/сквозные тесты
│   └── e2e-tests/
└── buildSrc/             # Общие плагины и конфигурации

Каждый модуль внутри имеет стандартную структуру Maven/Gradle:

user-service/
├── build.gradle
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/user/
│   │   │       ├── controller/
│   │   │       ├── service/
│   │   │       └── repository/
│   │   └── resources/
│   │       └── application.yml
│   └── test/
│       └── java/
└── Dockerfile

2. Управление зависимостями: Maven BOM и Gradle Version Catalog

Главная проблема монорепозитория — синхронизация версий зависимостей. В Java это решается двумя способами.

Вариант 1: Maven BOM (Bill of Materials)

Создаётся корневой POM, который объявляет все версии зависимостей. Дочерние модули используют dependencyManagement:

<!-- pom.xml (корневой) -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.2.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>common-utils</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>
</dependencyManagement>

Вариант 2: Gradle Version Catalog

В файле gradle/libs.versions.toml объявляются версии:

[versions]
spring-boot = "3.2.0"
lombok = "1.18.30"

[libraries]
spring-boot-starter-web = { group = "org.springframework.boot", name = "spring-boot-starter-web", version.ref = "spring-boot" }
lombok = { group = "org.projectlombok", name = "lombok", version.ref = "lombok" }

[plugins]
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }

В модулях используем:

dependencies {
    implementation libs.spring.boot.starter.web
    compileOnly libs.lombok
    annotationProcessor libs.lombok
}

3. Пример кода: многомодульная сборка с Gradle

Рассмотрим реальный пример: монорепозиторий с двумя модулями — библиотекой common-utils и сервисом user-service.

Шаг 1: Корневой settings.gradle

rootProject.name = 'my-monorepo'

include 'common:common-utils'
include 'services:user-service'

Шаг 2: Библиотека common-utils/build.gradle

plugins {
    id 'java-library'
}

dependencies {
    api 'org.apache.commons:commons-lang3:3.13.0'
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.0'
}

// Публикуем как локальную библиотеку
java {
    withSourcesJar()
}

Шаг 3: Сервис user-service/build.gradle

plugins {
    id 'application'
    id 'org.springframework.boot' version '3.2.0'
}

dependencies {
    implementation project(':common:common-utils')
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

application {
    mainClass = 'com.example.user.UserApplication'
}

Шаг 4: Пример класса из common-

Блоги

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