Прочитав статью
«Простой релиз-менеджмент средствами Git»
захотелось поделиться парой своих мыслей по поводу релиз-менеджмента maven проектов.
В этой заметке я не столько предложу решение проблем, сколько хочу правильнее их сформулировать.
В кратце: главная проблема релиз-менеджмента maven проектов — это сам maven, который, не был разработан с учетом требований CI (Continuous Integration) подхода к разработке ПО.
Парадигма SNAPSHOT версий устарела с тех пор, как CI стало стандартным подходом к разработке ПО.
Более подробно далее.
Современный взгляд на CI изложенный например в книге «Continuous Delivery» исходит из того, что любая branch — это зло.
В идеале разработка должна вестись в единственной ветке trunk. Следует избегать создания любой новой ветки разработки настолько, насколько это возможно.
Особенно вредны так называемые "feature branches", ветки, единственное предназначение которых — отложить трудности интеграции новых feature до последнего момента, что полностью противоречит приципу Continuous Integration («постоянная интеграция»).
Единственно, где с моей точки зрения, могут использоваться ветви — это случай "release branch", т.е. ветки созданные для исправления ошибок в production версии в то время, когда разработка идет параллельно в транке.
Причем даже их не стоит торопиться создавать. В случае крайней необходимости release branch всегда можно создать позже, когда понадобится, на основе tag'a или ревизии системы контроля версий.
Branch следует рассматривать как «линию параллельной разработки».
Если вам надо вести параллельную разработку одновременно нескольких линий кода, значит вам нужно создавать новый branch. На практике такое встречается [должно встречаться] исключительно редко. Единственный случай — багфиксы и сервис релизы предыдущих версий, для чего используются release ветки. Повторюсь: комфорт разработчика, получающего «кажущееся спокойствие» при работе в feature branch, не является достаточным аргументом для создания новой ветки.
Поэтому, с моей точки зрения, упомянутое в статье наличие единственного trunk'a во многих open source проектах — это хороший пример для подражания.
Другой принцип в рамках методологии Continuous Delivery заключается в том, что каждый build artifact генерируемый CI системой должный получать уникальный номер версии. Это необходимо для того, чтобы не строить проект по-новой, тратя время и рискуя внести ненамеренные изменения, каждый раз, когда надо передать определенную версию кода на следующую стадию (stage) конвейера разработки. Например на Staging после того, как прошли UAT (User Acceptance Test) тесты определенной версии в Test среде. Вместо этого артефакт с уникальной версией следует сохранять в репозитории артефактов (например Nexus, и ни в коем случае не в VCS (version control system) вместе с кодом).
Если Ваш проект использует maven для управления зависимостями, то настоящая проблема с релиз-менеджментом заключается в том, что maven не расчитан на то, что каждый новый билд получает новую уникальную версию.
Классическая последовательность действий по выпуску новой версии с использованием maven заключается в ручном создании новой release branch, обновлении pom.xml, построении проекта и, в случае успеха, коммита изменений в VCS.
Неразбериха с версиями происходит уже на этом этапе, поскольку одному и тому же артефакту теперь соответствуют две ревизии в VCS. Тему транзакциональности этих изменений поднимать здесь не будем.
Для автоматизации этих действий можно использовать maven release плагин, который тем не менее не решает проблему по сути и не делает maven проект более пригодным для CI и Continuous Delivery/Deployment.
Использование SNAPSHOT версий, на первый взгляд, может сделать возможным CI билды. На самом деле, SNAPSHOT версии еще более усугубляют неразбериху и требуют дополнительных трудозатрат по обновлению pom-файлов, поскольку при релизе теперь надо не забыть вручную обновить не только версию проекта, но и все SNAPSHOT зависимости данного проекта, а также всех проектов, которые на него ссылаются. А если сюда еще приплюсовать version ranges из Maven 3, то сложность согласования всех частей головоломки быстро превышает целесообразную.
В идеале, CI билд-система должна сама генерировать номер версии артефакта и передавать его во все билды конвейерной цепочки Continuous Delivery.
Hudson/Jenkins, например, не умеют этого делать и обладают двумя основополагающими недостатками, которые исключают их из серьезного рассмотрения при выборе CI системы:
Приличная CI-билд система с легкостью справляется с задачей генерации и передачи версий билдов. Например мой любимый Quickbuild, который по соотношению цена-качество, пожалуй, лучший выбор на сегодняшний день (сравнение я проводил, правда, пару лет назад; интересно, куда продвинулся за это время Thoughtworks Go).
К сожалению, передать версию в maven билд без побочных эффектов (было?) нельзя.
Максимум что можно сделать:
Но в таком случае в pom.xml сохраненный в maven репозитории после деплоймента будет хранить
без изменений, что станет проблемой при постройке проектов, зависящих от данного.
Итак, проблемы на пути эффективного CI maven проекта грубо говоря две:
Первая проблема решается правильным выбором CI системы или дополнительными трудозатратами на кастомизацию существующей.
Вторая проблема может решиться например переходом на использование Ant+Ivy. Ivy изначально не страдает детскими болезнями maven, однако требует существенно более длительной подготовки, а также более сложен в настройке (хотя сделать это придется, пожалуй, всего один раз).
В качестве компромисса мы используем сейчас следующий подход.
В реальности конфигурация наших CI джобов несколько сложнее, поскольку для важных интерфейсов, которые совместно используются нашими подсистемами, ведется собственное версионирование и ветвление. Соответственно, для каждого такого под-проекта сконфигурированы свои CI и release джобы. Если CI-билд система хорошо поддерживает иерархичную структуру джобов, то управление многчисленными конфигурациями не представляет проблем, поскольку все совместно используемые настройки следуют DRY принципу и хранятся только в одном месте.
Возвращаясь к заметке «Простой релиз-менеджмент средствами Git», хочу заметить, что отказ от relase веток для многих станет непреодолимым препятствием для внедрения описанного подхода.
Каждая новая ветвь — зло. Намерение минимизировать количество ветвей раз и навсегда весьма похвально, но при попытке поддержать более сложные варианты использования (как, например, независимый выпуск хотфиксов) растет количество нестандартно исползуемых живых ветвей а также сложность их использования.
А реальная проблема, решение которой предлагается в статье — не проблема организации веток/порядка работы с VCS, а проблема неприспособленности maven для Continuous Integration подхода к разработке ПО.
По итогам комментариев, немного подумав о том, что же можно предложить взамен SNAPSHOT-ам, пришел к выводу, что поприветствовал бы развитие maven (или другой билд системы и системы контроля зависимостей) в следующих направлениях:
«Простой релиз-менеджмент средствами Git»
захотелось поделиться парой своих мыслей по поводу релиз-менеджмента maven проектов.
В этой заметке я не столько предложу решение проблем, сколько хочу правильнее их сформулировать.
В кратце: главная проблема релиз-менеджмента maven проектов — это сам maven, который, не был разработан с учетом требований CI (Continuous Integration) подхода к разработке ПО.
Парадигма SNAPSHOT версий устарела с тех пор, как CI стало стандартным подходом к разработке ПО.
Более подробно далее.
Современный взгляд на CI изложенный например в книге «Continuous Delivery» исходит из того, что любая branch — это зло.
В идеале разработка должна вестись в единственной ветке trunk. Следует избегать создания любой новой ветки разработки настолько, насколько это возможно.
Особенно вредны так называемые "feature branches", ветки, единственное предназначение которых — отложить трудности интеграции новых feature до последнего момента, что полностью противоречит приципу Continuous Integration («постоянная интеграция»).
Единственно, где с моей точки зрения, могут использоваться ветви — это случай "release branch", т.е. ветки созданные для исправления ошибок в production версии в то время, когда разработка идет параллельно в транке.
Причем даже их не стоит торопиться создавать. В случае крайней необходимости release branch всегда можно создать позже, когда понадобится, на основе tag'a или ревизии системы контроля версий.
Branch следует рассматривать как «линию параллельной разработки».
Если вам надо вести параллельную разработку одновременно нескольких линий кода, значит вам нужно создавать новый branch. На практике такое встречается [должно встречаться] исключительно редко. Единственный случай — багфиксы и сервис релизы предыдущих версий, для чего используются release ветки. Повторюсь: комфорт разработчика, получающего «кажущееся спокойствие» при работе в feature branch, не является достаточным аргументом для создания новой ветки.
Поэтому, с моей точки зрения, упомянутое в статье наличие единственного trunk'a во многих open source проектах — это хороший пример для подражания.
Другой принцип в рамках методологии Continuous Delivery заключается в том, что каждый build artifact генерируемый CI системой должный получать уникальный номер версии. Это необходимо для того, чтобы не строить проект по-новой, тратя время и рискуя внести ненамеренные изменения, каждый раз, когда надо передать определенную версию кода на следующую стадию (stage) конвейера разработки. Например на Staging после того, как прошли UAT (User Acceptance Test) тесты определенной версии в Test среде. Вместо этого артефакт с уникальной версией следует сохранять в репозитории артефактов (например Nexus, и ни в коем случае не в VCS (version control system) вместе с кодом).
Если Ваш проект использует maven для управления зависимостями, то настоящая проблема с релиз-менеджментом заключается в том, что maven не расчитан на то, что каждый новый билд получает новую уникальную версию.
Классическая последовательность действий по выпуску новой версии с использованием maven заключается в ручном создании новой release branch, обновлении pom.xml, построении проекта и, в случае успеха, коммита изменений в VCS.
Неразбериха с версиями происходит уже на этом этапе, поскольку одному и тому же артефакту теперь соответствуют две ревизии в VCS. Тему транзакциональности этих изменений поднимать здесь не будем.
Для автоматизации этих действий можно использовать maven release плагин, который тем не менее не решает проблему по сути и не делает maven проект более пригодным для CI и Continuous Delivery/Deployment.
Использование SNAPSHOT версий, на первый взгляд, может сделать возможным CI билды. На самом деле, SNAPSHOT версии еще более усугубляют неразбериху и требуют дополнительных трудозатрат по обновлению pom-файлов, поскольку при релизе теперь надо не забыть вручную обновить не только версию проекта, но и все SNAPSHOT зависимости данного проекта, а также всех проектов, которые на него ссылаются. А если сюда еще приплюсовать version ranges из Maven 3, то сложность согласования всех частей головоломки быстро превышает целесообразную.
В идеале, CI билд-система должна сама генерировать номер версии артефакта и передавать его во все билды конвейерной цепочки Continuous Delivery.
Hudson/Jenkins, например, не умеют этого делать и обладают двумя основополагающими недостатками, которые исключают их из серьезного рассмотрения при выборе CI системы:
- Отсутствие поддержки Expression Language в настройках Job'ов
- Отсутствие возможности иерархического построения конфигураций с наследованием настроек плагинов (существующие плагины, решающие похожую проблему, имеют весьма незначительную практическую пользу).
Приличная CI-билд система с легкостью справляется с задачей генерации и передачи версий билдов. Например мой любимый Quickbuild, который по соотношению цена-качество, пожалуй, лучший выбор на сегодняшний день (сравнение я проводил, правда, пару лет назад; интересно, куда продвинулся за это время Thoughtworks Go).
К сожалению, передать версию в maven билд без побочных эффектов (было?) нельзя.
Максимум что можно сделать:
<project xmlns="..." xmlns:xsi="..." xsi:schemaLocation="...">
<modelVersion>4.0.0</modelVersion>
<groupId>org.xxx</groupId>
<artifactId>xxx</artifactId>
<version>${ciVersion}</version>
<packaging>jar</packaging>
<properties>
<ciVersion>0.0.0</ciVersion>
</properties>
mvn -DciVersion=1.1.1 deploy
Но в таком случае в pom.xml сохраненный в maven репозитории после деплоймента будет хранить
${ciVersion}
без изменений, что станет проблемой при постройке проектов, зависящих от данного.
Итак, проблемы на пути эффективного CI maven проекта грубо говоря две:
- Сложность генерации и передачи по цепочки связанных джобов уникальной версии средствами CI системы.
- Невозможность передать сгенерированную версию в maven билд.
Первая проблема решается правильным выбором CI системы или дополнительными трудозатратами на кастомизацию существующей.
Вторая проблема может решиться например переходом на использование Ant+Ivy. Ivy изначально не страдает детскими болезнями maven, однако требует существенно более длительной подготовки, а также более сложен в настройке (хотя сделать это придется, пожалуй, всего один раз).
В качестве компромисса мы используем сейчас следующий подход.
- Все разработка ведется в trunk, который настроен на SNAPSHOT версию
- CI строит trunk как только видит новый чекин в VCS.
- Когда trunk готов к UAT, вручную запускается другая конфигурация/job CI системы, которая:
- генерирует уникальную версию билда (например на основе VCS ревизии)
- забриает строющуюся ветку из VCS
- использует maven versions плагин для обновления версии проекта и всех под-проектов на relase-версию, сгенерированную на шаге 1 (опционально можно обновить SNAPSHOT зависимотей на релиз версии на этом шагу)
- строит проект
- деплоит построенные артефакты с релиз-версией в Nexus
(замечу, что после этого изменения в pom файлах не коммитятся в VCS) - VCS ревизия запоминается или создается tag
- В случае успешного выполнения предыдущего шага, следом автоматически запускается связанная конфигурация/job, ответственная за развертывание приложения в целевой среде (например тестовой среде), которой с помощью EL выражений, среди прочих параметров, передается версия артефакта, построенного на предыдущем шагу. Эта работа запускается прямо на целевой машине через удаленного агента CI системы, или же все развертывание можно сделать через ssh соединение. Скрипт развертывания также хранится в VCS.
- В случае необходимости выпсука хотфикса или подготовки сервис-пака мы брэнчим желаемую ревизию (или tag) и меняем параметры release джоба CI системы чтобы он использовал новую ветку и генерировал подходящие major/minor версии построенным артефактам. В случае использования Quickbuild данная процедура сводится буквально к исправлению двух трех значений параметров существующей конфигурации без необходимости создавать новый джоб из шаблона.
В реальности конфигурация наших CI джобов несколько сложнее, поскольку для важных интерфейсов, которые совместно используются нашими подсистемами, ведется собственное версионирование и ветвление. Соответственно, для каждого такого под-проекта сконфигурированы свои CI и release джобы. Если CI-билд система хорошо поддерживает иерархичную структуру джобов, то управление многчисленными конфигурациями не представляет проблем, поскольку все совместно используемые настройки следуют DRY принципу и хранятся только в одном месте.
Возвращаясь к заметке «Простой релиз-менеджмент средствами Git», хочу заметить, что отказ от relase веток для многих станет непреодолимым препятствием для внедрения описанного подхода.
Каждая новая ветвь — зло. Намерение минимизировать количество ветвей раз и навсегда весьма похвально, но при попытке поддержать более сложные варианты использования (как, например, независимый выпуск хотфиксов) растет количество нестандартно исползуемых живых ветвей а также сложность их использования.
А реальная проблема, решение которой предлагается в статье — не проблема организации веток/порядка работы с VCS, а проблема неприспособленности maven для Continuous Integration подхода к разработке ПО.
По итогам комментариев, немного подумав о том, что же можно предложить взамен SNAPSHOT-ам, пришел к выводу, что поприветствовал бы развитие maven (или другой билд системы и системы контроля зависимостей) в следующих направлениях:
- Поддержка «стратегий» автоматической генерации версий. Что-то вроде: «при постройке присвоить артефакту версию по таком образцу, используя генератор случайных чисел, ревизию VCS, или что угодно по вкусу» (вот где сегодня приходят на помощь гибкие EL выражения Quickbuild)
- Вместо SNAPSHOT-ов ввести понятие release-билда, чтобы maven знал, что это построенные и референциремые именно в этом билде артефакты более нельзя убирать из репозитория. Зато другие, номера версий которых не отличаются в общем случае от версий release артефактов, можно удалять со временем безболезненно. В отличие от существющей системы, по умолчанию все артефакты получают уникальную версию и являются кандидатами на удаление из репозитория. Если вдруг какой-либо из них был референцирован из «release»-билда, то он автоматически переходит в статус release-артефакта