- PVSM.RU - https://www.pvsm.ru -
Когда проект разрастается, обзаводится множеством модулей и зависимостей, настройка сборки проекта вручную может занять непомерно много времени. К тому же настройкой, а значит и тратой времени, занимаются все технические участники проекта от разработчиков, тестировщиков, до администраторов.
К тому же надо постоянно обновлять разработческие и тестовые системы, да еще и ничего не перепутать. Тут не обойтись без практики непрерывной интеграции [1].
В этой статье приведен опыт внедрения сборок с помощью maven, поэтому базовые знания по maven необходимы. Для быстрого старта будет достаточно изучения сайта http://www.apache-maven.ru [2], для более детального освоения темы — стоит обратиться к первоисточнику http://maven.apache.org [3]
Цель статьи не научить пользоваться майвеном, а показать опыт внедрения с положительными и отрицательными решениями. Обладая этими знаниями, гораздо проще и быстрее внедрить сборки maven у себя в команде.
Предупреждая подобного рода вопросы, скажу, что мы используем и ant [4], но не для самой сборки, а для технических функций, об этом ниже.
Еще важным преимуществом maven является возможность собирать только тот код, с которым ты работаешь, все остальное собирается по ночам на отдельном сервере.
Сначала стоит рассказать о структуре наших проектов и подходах к разработке. Это очень важно, в силу того, что проект развивается уже много лет и имеет структуру, не соответствующую maven-webapp-artifact [5]
И так, у нас имеется множество модулей, хранящихся в одном SVN репозитарии. Каждый модуль имеет основную ветку разработки (trunk), а также метки (tags) и ветки (branches) по необходимости. Большинство модулей содержат как java-классы, так и jsp-страницы, скрипты, графику и пр. Большинство модулей являются самостоятельными веб-приложениями и могут работать независимо. Примеры модулей: core, soap, workflow, addons, common… Их я насчитал порядка 100.
Проект чаще всего состоит из отдельного модуля, в котором размещаются специфические ресурсы и логика в java-классах. Иногда, такого модуля нет. Итоговая сборка проекта состоит из набора модулей в виде одного web-приложения.
Необходимые модулю библиотеки хранятся в папке lib прямо в репозитарии, для веб-ресурсов имеется своя папка sx-war. А также стандартные папки src и test.
Естественно, почти все модули зависят от core, и друг от друга, по необходимости.
Еще есть база данных с мета-информацией. На основе нее взаимодействуют модули, строится вся работа с пользователем. Например, блок публикации — тоже является единицей мета-информации и хранится в БД. То есть помимо самих данных, в базе хранятся настройки работы с этими данными. Они настраиваются в консоли, которая тоже построена на блоках публикации. Есть возможность переносить мета-информацию с помощью пакетов обновлений.
В идеале, каждый модуль имеет свой общий пакет с мета-информацией. БД для core самая легкая БД и может расширяться любым модулем. Ядро — почти application-server, только без возможности динамической загрузкивыгрузки многофункциональных модулей. Не все модули имеют отделенную мету, но мы к этому стремимся :)
Ходит у нас такая фраза. Означает, что «это» было очень давно, еще до начала нашей работы в компании. Расскажу, как проходила настройка сборки до меня. Мне тоже пришлось потратить немало времени на настройку в первый раз.
И вот вы устроились к нам на работу. Идет второй день. Первый ушел на просмотр скринкастов по подсистемам и тренировки работы с мета-информацией. Надо настроить сборку. Хорошо, если в базе знаний по проекту есть информация по необходимым модулям для проекта. Часто эта информация не актуальна (забывают обновлять или обновляют от случая к случаю). Информации о зависимостях самих модулей не найти, а та что есть, тоже устаревает.
Из SVN извлекаются trunk-версии всех необходимых модулей, создается проект в IDE. А дальше самое сложное: настроить импорт всех необходимых библиотек и связи модулей. Все еще осложняется, когда надо исключать одни версии библиотек из одних модулей и использовать другие. Настроить сборку всех модулей в одну папку. Далее настроить сервер веб-приложений (используется tomcat).
И вот проект настроен, затем надо настроить второй, третий, версию первого (использовать branches для работы над ошибками). Разработчику становится не по себе.
Поменялись зависимости, сломались сборки у всех. А администраторы используют ant для создания production-сборок — у них тоже все ломается. Часто возникают проблемы из-за разных сборок у администратора и разработчика.
У нас не было специальных людей, отвечающих за сборку, как не было и хороших знаний maven. Поэтому первая попытка внедрения maven на проектах провалилась.
Родительский pom.xml был помещен в отдельную папку репозитария. После чего в отдельной проектной папке настраивались svn-externals [6] и включались все необходимые проекту модули, плюс родительский pom.xml в отдельной папке:
Классический подход с использованием иерархии модулей в единой структуре SVN нам не подошел, потому что каждый модуль используется в десятках проектов. А не только в одном единственном. А любые манипуляции с parent pom на проектах нежелательны. К тому же фиксация версии любого модуля автоматически отразится на всех проектах.
Все необходимые библиотеки не искались на http://search.maven.org [7], потому что их очень много и все они обезличены в репозитарии (в папке lib хранятся библиотеки без версий. Например axis.jar, jtds.jar, velocity.jar). Поступили просто: все библиотеки с помощью скрипта загрузили в Nexus. GroupId и ArtifactId были заданы по маске sx.<имя библиотеки> (sx.axis).
Вот как выглядел типичный pom.xml модуля:
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Родительский проект -->
<parent>
<groupId>sx.config</groupId>
<artifactId>project-settings</artifactId>
<version>2.0.1</version>
<relativePath>project-settings/pom.xml</relativePath>
</parent>
<!-- Координаты -->
<groupId>sx.builds</groupId>
<artifactId>wfBuilder</artifactId>
<version>2.0.1</version>
<packaging>pom</packaging>
<!-- Репозиторий плагинов -->
<pluginRepositories>
<pluginRepository>
<id>sx-repository</id>
<url>http://v5-neptun/nexus/content/repositories/sx-repository</url>
</pluginRepository>
</pluginRepositories>
<!-- Репозиторий Зависимостей -->
<repositories>
<repository>
<id>sx-repository</id>
<url>http://v5-neptun/nexus/content/repositories/sx-repository</url>
</repository>
</repositories>
<!--URL SVN-->
<scm>
<connection>scm:svn:svn://svn-server/buildsMaven/trunk/workflowBuild</connection>
</scm>
<!--Модули-->
<modules>
<module>core</module>
<module>workflow</module>
</modules>
</project>
Видно, что необходимо менять в нем список модулей для каждого проекта.
Во время сборки, каждый модуль собирался в отдельный war, а потом распаковывался в общий деплой с помощью maven-overlay [8].
Полностью перечеркнул преимущества maven подход использования svn-externals. Это свойство не позволяет однозначно зафиксировать код всех модулей без правки свойств svn папки вручную. Это становится практически нереально при наличии 10-15 модулей и выпуске патча для некой версии. А так хотелось использовать maven release plugin [9].
Вторая попытка внедрения сборок развивалась в несколько итераций.
В первую очередь необходимо было получить механизм фиксации кода (выпуска версии) как отдельных модулей, так и всего продукта в целом. Для этого мы избавились от использования svn-externals. Каждый модуль получился автономным и собирался отдельно. В конечную сборку попадали все зависимости, которые есть у модуля.
Также был модифицирован родительский pom.xml в соответствии с задачами. Все модули используют его в качестве базового:
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Координаты -->
<groupId>sx.config</groupId>
<artifactId>main-settings</artifactId>
<version>1.1-SNAPSHOT</version>
<packaging>pom</packaging>
<!--URL SVN-->
<scm>
<connection>scm:svn:svn://svn-server/maven/main-settings/trunk</connection>
</scm>
<!-- Репозиторий плагинов -->
<pluginRepositories>
<pluginRepository>
<id>sx-repository</id>
<url>http://v5-neptun/nexus/content/repositories/sx-repository</url>
</pluginRepository>
</pluginRepositories>
<!-- Репозиторий Зависимостей -->
<repositories>
<repository>
<id>sx-repository</id>
<url>http://v5-neptun/nexus/content/repositories/sx-repository</url>
</repository>
<repository>
<id>releases-repository</id>
<url>http://v5-neptun/nexus/content/repositories/releases</url>
</repository>
<repository>
<id>snapshots-repository</id>
<url>http://v5-neptun/nexus/content/repositories/snapshots</url>
</repository>
<repository>
<id>central-repository</id>
<url>http://v5-neptun/nexus/content/repositories/central</url>
</repository>
</repositories>
<profiles>
<profile>
<id>production</id>
<!-- На ФТП публикуются только конечные сборки продуктов. Модули публиковать не нужно -->
<distributionManagement>
<repository>
<id>ftp-repository</id>
<url>ftp://ftp.sample.com/builds</url>
</repository>
</distributionManagement>
</profile>
<profile>
<id>development</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<!-- Этот репозиторий указывается только для дочерних модулей -->
<distributionManagement>
<snapshotRepository>
<id>snapshots-repository</id>
<url>http://v5-neptun/nexus/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>releases-repository</id>
<url>http://v5-neptun/nexus/content/repositories/releases</url>
</repository>
</distributionManagement>
</profile>
</profiles>
<!-- Настройки сборки -->
<build>
<defaultGoal>package</defaultGoal>
<!--Исходный код -->
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>test</testSourceDirectory>
<!--Ресурсы -->
<resources>
<resource>
<directory>src</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.sql</include>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
<extensions>
<!-- Enabling the use of FTP -->
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ftp</artifactId>
<version>1.0-beta-6</version>
</extension>
</extensions>
<plugins>
<!--Сборка -->
<!--Синтаксический сахар для идеи -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.2</version>
<configuration>
<!-- У нас не стандартные директории -->
<warSourceDirectory>sx-war</warSourceDirectory>
<attachClasses>true</attachClasses>
</configuration>
</plugin>
<!--Задаем опции выделения памяти, на больших модулях может выпадать out of memory-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<minmemory>256m</minmemory>
<maxmemory>512m</maxmemory>
</configuration>
</plugin>
<!-- Настройки Ресурсов -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.4.3</version>
<configuration>
<encoding>cp1251</encoding>
</configuration>
</plugin>
<!-- Версия плагина для вычекивания -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-scm-plugin</artifactId>
<version>1.4</version>
<configuration>
<username>${SvnUsername}</username>
<password>${SvnPassword}</password>
</configuration>
</plugin>
<!-- Плагин выпуска релизов модуля -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<configuration>
<tagBase>svn://gelios/cms/maven/main-settings/tags</tagBase>
<branchBase>svn://gelios/cms/maven/main-settings/branches</branchBase>
<preparationGoals>clean install</preparationGoals>
<goals>deploy</goals>
<autoVersionSubmodules>true</autoVersionSubmodules>
</configuration>
</plugin>
</plugins>
</build>
</project>
Во все модули проекта внедрили плагин выпуска версий [10]. Для этого в каждый модуль были добавлены блоки:
<!-- Путь до модуля в SVN. Меняется при выпуске версий автоматически -->
<scm>
<connection>scm:svn:svn://svnserver/module/trunk</connection>
</scm>
<build>
<plugins>
<!-- Плагин выпуска релизов модуля -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<configuration>
<!-- Пути в SVN для хранения версий модуля -->
<tagBase>svn://svnserver/module/tags</tagBase>
<branchBase>svn://svnserver/module/branches</branchBase>
<preparationGoals>clean install</preparationGoals>
<goals>deploy</goals>
<autoVersionSubmodules>true</autoVersionSubmodules>
</configuration>
</plugin>
...
</plugins>
Были мысли вынести эту конфигурацию в базовый pom.xml, и использовать свойство ArtifactId-Version, но не во всех проектах это значение совпадает с названием папки в репозитарии. Поэтому мысль была отложена до лучших времен. Первым версию получил как раз базовый для всех модулей pom.xml.
В базовый pom.xml добавили новый профиль: production. Он закачивает сборку на корпоративный FTP и может дальше может распространяться на проекты.
После неприятного столкновения с jar-hell [11], решено было привести библиотеки в порядок во всех сборках. А это было не просто, потому что только в ядре оказалось больше 100 библиотек и почти все без версий. Каждую надо было найти в публичном репозитарии, “вычислить” версию. Вскрылись библиотеки “сюрпризы” с внутренними сборками и патчами, измененными версиями. Их мы бережно поместили в maven-репозиторий с сохранением координат артифакта, но с припиской _custom в Version.
В качестве репозитория у нас используется Nexus [12]. На нем был настроен шлюз к публичным репозиториям с кешированием. Появилась единая точка входа: корпоративный maven-репозиторий. До этого их было больше трех: библиотеки, релизы, ежедневные сборки и остальные публичные репозитории. Все требовали авторизацию. Три похожих репозитория в каждом pom.xml, да еще параметры доступа в settings.xml [13] вызывали постоянную путаницу.
Делали попытку оставить ссылку на репозиторий только в базовом pom.xml. Это вполне реально, но при первой сборке любого модуля майвен должен загрузить базовый модуль из репозитория, а его координаты неизвестны. Можно это сделать вручную, после этого все отлично работает. Отказались из-за непрозрачности этого действия.
Настроили еженощные сборки всех базовых и проектных модулей в системе непрерывной интеграции Hudson [14]. С ними не возникло проблем, потому что модули теперь полностью независимы. Нет никакой разницы, публичная это библиотека или проектный модуль. Сборки на основе maven запускаются после каждого изменения кода в SVN и успешные артифакты попадают в репозитарий Nexus.
Со временем, настроили каскадную сборку модулей в задачах, а также деплой на разработческие сборки и сборки для тестирования. Деплой выполняется с помощью достаточно простых ant-скриптов. Они извлекают из Nexus последнюю сборку модуля (dependency:get [15]), распаковывают ее на сервере и перезапускают сервер приложений (сами скрипты можно найти в конце статьи).
В процессе работы, в сборки вносятся правки. Для тех, кто все еще собирает «по-старинке» заведена страница в базе знаний, которая содержит зависимости модулей. Эта информация обновляется на основе pom.xml модуля каждую ночь отдельной задачей в Hudson (не забываем о DRY [16]).
Для ускорения сборки модулей была исключена лишняя работа во время сборки. Например, мы собираем модуль soap, который зависит от core. Код core попадает в конечную сборку soap. Далее собирается project, который зависит от core и soap. soap в отдельности не используется, поэтому из его сборки исключается core, а в project он включается. Тем самым мы ускоряем сборку. Для возможности сборки soap с включением зависимого core был добавлен профиль сontext, который запускается при необходимости.
Можно заметить, что все модули встречаются в pom.xml дважды: одна запись используется при компиляции, вторая означает, что модуль попадает в деплой. Чтобы не подменять версии модулей по всему pom.xml, все версии корпоративных модулей были вынесены в отдельные свойства и располагаются в самом начале файла. Это снижает вероятность ошибки при переходе на другую версию:
<!-- Версии компонент и другие свойства.
В самом начале, чтобы проще искать -->
<properties>
<sx.core>4.5-SNAPSHOT</sx.core>
</properties>
...
<profiles>
<profile>
<!--Профиль сборки контекста-->
<id>context</id>
<dependencies>
<dependency>
<groupId>sx.core</groupId>
<artifactId>core</artifactId>
<version>${sx.core}</version>
<type>war</type>
<!-- Модуль попадет в деплой через maven-overlay -->
<scope>runtime</scope>
</dependency>
</dependencies>
</profile>
</profiles>
<!-- Настройка зависимостей -->
<dependencies>
<dependency>
<groupId>sx.core</groupId>
<artifactId>core</artifactId>
<version>${sx.core.core}</version>
<classifier>classes</classifier>
<!-- Модуль используется только для сборкикомпиляции -->
<scope>provided</scope>
</dependency>
...
</dependencies>
ant-cкрипты для работы с maven-артифактами [17]
Так была внедрена непрерывная интеграция в нашем проекте. Пропала необходимость в ручных обновлениях кода на серверах. Всегда видны результаты сборки, которые рассылаются по RSS. Большой экран мониторинга сборки еще не поставили.
Новые разработчики не тратят много времени на настройку сборок. А это только радует.
Наладился выпуск версий кода, теперь этот процесс почти полностью автоматизирован и не занимает много времени. Стало гораздо легче сопровождать ранее выпущенные версии.
Новые наработки попадают на контексты разработки в течение суток, а значит и к заказчику они попадают быстрее. Сложно уловимые ошибки интеграции тоже стали проявляться, а значит, и исправляться, быстрее.
Автор: VaiMR
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/maven/17307
Ссылки в тексте:
[1] непрерывной интеграции: http://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%BF%D1%80%D0%B5%D1%80%D1%8B%D0%B2%D0%BD%D0%B0%D1%8F_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B0%D1%86%D0%B8%D1%8F
[2] http://www.apache-maven.ru: http://www.apache-maven.ru
[3] http://maven.apache.org: http://maven.apache.org
[4] ant: http://ant.apache.org/
[5] maven-webapp-artifact: http://maven.apache.org/plugins/maven-archetype-plugin-1.0-alpha-7/examples/webapp.html
[6] svn-externals: http://habrahabr.ru/post/109291/
[7] http://search.maven.org: http://search.maven.org
[8] maven-overlay: http://maven.apache.org/plugins/maven-war-plugin/overlays.html
[9] maven release plugin: http://maven.apache.org/plugins/maven-release-plugin/
[10] плагин выпуска версий: http://maven.apache.org/plugins/maven-release-plugin/plugin-info.html
[11] jar-hell: http://en.wikipedia.org/wiki/Java_Classloader
[12] Nexus: http://www.sonatype.org/nexus/
[13] settings.xml: http://maven.apache.org/settings.html
[14] Hudson: http://hudson-ci.org/
[15] dependency:get: http://maven.apache.org/plugins/maven-dependency-plugin/get-mojo.html
[16] DRY: http://en.wikipedia.org/wiki/Don't_repeat_yourself
[17] ant-cкрипты для работы с maven-артифактами: http://yadi.sk/d/efE-g5aK0DbJX
Нажмите здесь для печати.