Это вторая статья, посвященная мавену и его использованию для организации моих проектов. Целью этой статьи будет структура проекта под управлением мавен. Особых откровений вы не найдёте, скорее набор общих правил для моих проектов. Первую статью можно прочитать здесь.
Структура проекта
Как я уже сказал, я не буду описывать здесь типовую структуру проекта на мавен – вы её знаете или легко можете найти по ссылке: Standard Directory Layout. В этой части я остановлюсь на особенностях, которые я применяю в своих проектах, итак:
Модули
Практически любой мой проект имеет несколько модулей. Практика показывает, что, когда необходимо добавить еще один модуль в проект это гораздо проще сделать в случае изначально модульного проекта. В моих проектах существует 3 типа «модулей» – POM (всегда один), несколько девелоперских модулей с кодом и BOM – если дев. модулей больше одного. В принципе POM это не модуль в понимании мавен, но я всегда оформляю его почти как «модуль» (покажу ниже). В итоге получается, что-то вроде такого:
Общие принципы для всех POM‘ов – в них содержится минимум информации необходимый для построения проекта, все версии, все настройки плагинов, не специфичные для данного проекта, находятся в супер («корпоративном») POM'е. Все «модули», кроме проектного POM‘а, содержат ссылку на локальную версию родителя
relativePath
.Проектный POM
Начнём с проектного POM‘а. Я практически всегда убираю его в отдельный каталог с именем pom. Делаю я так по нескольким причинам:
- Импорт проектов в Eclipse. Это самый постой способ избежать импорта модулей вместе с проектнымPOM‘ем.
- Разделяются файлы относящиеся к целому проекту (например
.htignore
, на картинке выше) и «проекту» импортированному в IDE (например.settings
, который генерирует Eclipse для каждого «модуля» при импорте). - Мне так визуально проще это воспринимать как «модуль» проекта.
Проектный POM содержит ссылку на супер POM, список модулей, версии проектов от которых он зависит (не библиотек третьих стран, а именно проектов, которые находятся параллельно в разработке в этой компании) и определение версии для модулей и проектов (dependencyManagement). Вот типичный POM для маленького проекта:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>org.jresearch.pom</artifactId>
<groupId>org.jresearch</groupId>
<version>29-SNAPSHOT</version>
</parent>
<groupId>org.jresearch.commons.base.resources</groupId>
<artifactId>org.jresearch.commons.base.resources.pom</artifactId>
<version>1.0.43-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>../api</module>
<module>../impl</module>
<module>../test</module>
<module>../bom</module>
</modules>
<properties>
<jrs.open.base.domain.ver>1.0.34-SNAPSHOT</jrs.open.base.domain.ver>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jresearch.commons.base</groupId>
<artifactId>org.jresearch.commons.base.domain</artifactId>
<version>${jrs.open.base.domain.ver}</version>
</dependency>
<dependency>
<groupId>org.jresearch.commons.base.resources</groupId>
<artifactId>org.jresearch.commons.base.resources.api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jresearch.commons.base.resources</groupId>
<artifactId>org.jresearch.commons.base.resources.test</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
В этом примере:
версия проекта, от которого он зависит
<properties>
<jrs.open.base.domain.ver>1.0.34-SNAPSHOT</jrs.open.base.domain.ver>
</properties>
и определение версии для модулей и проектов
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jresearch.commons.base</groupId>
<artifactId>org.jresearch.commons.base.domain</artifactId>
<version>${jrs.open.base.domain.ver}</version>
</dependency>
<dependency>
<groupId>org.jresearch.commons.base.resources</groupId>
<artifactId>org.jresearch.commons.base.resources.api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
Зачем это нужно? Что бы полностью исключить использование версий в девелоперских модулях. С такой конфигурацией все версии фиксируются в супер POM‘е (для внешних библиотек) и в проектных POM‘ах (для самого проекта и его зависимостей на внутренние проекты). Это не только делает POM‘ы в модулях чище, но и необходимо для того релиз процесса, который я использую.
BOM POM
Это «bill of materials» — список артефактов, которые экспортирует проект. В моих проектах он появляется если количество девелоперских модулей больше одного. Это позволяет управлять версиями зависимостей между внутренними проектами с помощью только одной записи в секции dependencyManagement
.
Пример BOM:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>org.jresearch.commons.base.resources.pom</artifactId>
<groupId>org.jresearch.commons.base.resources</groupId>
<version>1.0.43-SNAPSHOT</version>
<relativePath>../pom/pom.xml</relativePath>
</parent>
<packaging>pom</packaging>
<artifactId>org.jresearch.commons.base.resources.bom</artifactId>
<name>JRS-COMMONS: Base resource (DAO) BOM</name>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jresearch.commons.base.resources</groupId>
<artifactId>org.jresearch.commons.base.resources.api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jresearch.commons.base.resources</groupId>
<artifactId>org.jresearch.commons.base.resources.impl</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jresearch.commons.base.resources</groupId>
<artifactId>org.jresearch.commons.base.resources.test</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</build>
</project>
Модуль с кодом
Самый примитивный POM. Включает в себя ссылку на проектный POM, artefactId, список зависимостей без версий. При необходимости секцию build c ссылками на плагины. Версия и groupId
наследуются из родительского POM’а.
Пример:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>org.jresearch.commons.base.resources.pom</artifactId>
<groupId>org.jresearch.commons.base.resources</groupId>
<version>1.0.43-SNAPSHOT</version>
<relativePath>../pom/pom.xml</relativePath>
</parent>
<artifactId>org.jresearch.commons.base.resources.api</artifactId>
<dependencies>
<dependency>
<groupId>org.jresearch.commons.base</groupId>
<artifactId>org.jresearch.commons.base.domain</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
</dependencies>
</project>
Имя артефакта
groupId
это имя пакета в Java для этого проекта – в последнее время это стало практически стандартом. С artifactId
– немного чудесатей, для своих проектов я всегда беру имя группы плюс имя модуля, что-то вроде этого:
<artifactId>org.jresearch.orika.spring</artifactId>
Почему? Имя у итогового артефакта должно быть уникальным (слишком часто все сваливают в один каталог), саму идею я вынес и мира eclipse — так там именуются плагины. Поначалу непривычно, но оно достаточно уникально, придумать его очень просто, увидев в каталоге по имени очень быстро можно найти артефакт в репозитории и source control‘е.
Почему не использовать finalName
в супер POM‘е (e.g. <finalName>${ groupId}.${artifactId}-${version}</finalName>
)? Так сложилось исторически, на заре мавенизации очень много плагинов игнорировало правила, и проще было написать это руками. Сейчас, наверное, это не так.
Следовать этой конвенции с именами не обязательно. Главное, это уникальность имени итогового артефакта.
Общие принципы по структуре для моих проектов.
- Версии зависимостей и плагинов только в супер или проектных POM‘ах
- Все модули наследуют версию и группу, которые определены в проектном POM‘е.
- Если я твёрдо (на 146%) уверен, что проект будет содержать только один модуль с кодом делаю классический мавен проект (e.g. https://bitbucket.org/JRS/open-base-util/src)
- Если знаю, что модулей будет несколько добавляю сразу bom модуль (e.g. https://bitbucket.org/JRS/open-base-resources/src)
- Если не уверен, то проект из одного модуля без BOM (e.g. https://bitbucket.org/JRS/open-base/src)
- artifactId это всегда groupId + имя модуля
- структура каталогов для проекта: корневой каталог – имя проекта (короткое), pom каталог для проектного POM’a, bom – для BOM POM‘а, остальные каталоги по имени модулей.
relativePath
— во всех «модулях», кроме проектного POM‘а, один мавен проект – это один проект в source control. Все связи между проектами только через мавен.
По структуре проектов это, наверное, все.
axmetishe
Дело конечно каждого, лично я больше за вложенную структуру проекта, визуально более понятно, прозрачное наследование, зависимости:
foal Автор
Да, конечно, проект может иметь несколько уровней вложенности, например, https://bitbucket.org/JRS/open-flexess/src. Но я и в этом случае придерживаюсь правила — создавать каталог для узловых "pom" файлов по причинам, изложенным выше в статье. Но у вашего варианта есть одно большое преимущество — он стандартный :)
axmetishe
На самом деле описанный мной способ и указанный в статье, оба являются стандартами — aggregation и inheritance, со своими особенностями. Просто лично я предпочитаю аггрегацию=)
foal Автор
Да, вот только, из вашего примера не было ясно, что в дочерних помах нету ссылок на родителя — только структура каталогов — а она как раз самая, что ни на есть классическая :) Что касается наследования и агрегирования, то мне ближе наследование между иерархией (деревом) супер помов и проектами (пример 1), тогда как внутри проектов я предпочитаю комбинацию агрегирования и наследования, как в 5 примере.
axmetishe
Опа…
Пойду перечитывать документацию — у меня подмена понятий произошла, поскольку я тоже использую комбинирование, только в качестве парента корневой от агрегации и суперпом на нем как парент, а не bom.