Введение: что такое монорепозиторий и зачем он нужен в 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-