Системы сборок. Первое знакомство

Системы сборок. Первое знакомство

Дорогу осилит идущий

В прошлой статье мы рассмотрели, как можно подключить внешнюю библиотеку в собственный проект.

Полагаю, было достаточно очевидно, что процесс был нетривиальным и достаточно дорогостоящим в разрезе полноценных проектов, которые могут использовать десятки библиотек. Подключение каждой из них описанным способом - уже дорого. Повторение всех этих действий в каждом проекте - неприлично дорого.

Для решения этой проблемы, а также ряда других, рассматриваемых выше, были созданы специальные инструменты - системы сборки, которые позволяют оптимизировать и упростить подключение внешних библиотек и сопровождение проекта на порядки. С двумя наиболее популярными в Java системами сборки - Maven и Gradle - мы и будем знакомиться в ближайших уроках. И именно для них, в первую очередь, будет актуальна информация в этой статье.


Проблемы, решаемые системами сборок

Ниже разберем ситуацию в разрезе гипотетического проекта. Некоторые этапы еще не знакомы нам с практической стороны, но, надеюсь, их будет несложно понять по краткому описанию. Также опустим некоторые организационные и инфраструктурные детали, которые не касаются темы статьи.

Итак, вводная: мы разрабатываем проект с бэк-эндом (back-end, серверная часть приложения) на Java небольшой командой разработчиков. Нам необходимо пройти путь от создания репозитория до первого выхода проекта в продакшн(production) - его запуска для использования конечными пользователями - и дальнейшей поддержки.


В рамках анализа требований мы пришли к выводу, что бэк-энд приложения (далее - приложение) будет состоять из двух Java-приложений со своими зонами ответственности. Например, основное приложение и админка - не суть. Для каждого из приложений создан отдельный git-репозиторий для удобства разработки.

Приступаем к созданию каркаса приложения, добавляя основные фреймворки и библиотеки, которые понадобятся для работы. Пока что ведем работы только над основным проектом, не касаясь админки.

Первый разработчик - Вася - подключает логгер к проекту - без логов никуда. Он проделывает все те действия, которые мы делали в рамках предыдущей статьи. (1)

Тем временем, второй разработчик - Петя - подключает библиотеку для работы с БД - не будем углубляться в детали, основные шаги те же, что и у Васи.

Закончив, разработчики пушат внесенные изменения в удаленный репозиторий. Для простоты, пусть хранилищем выступает знакомый нам GitHub.

Чтобы каждый разработчик не думал о подключении уже “подключенных” библиотек, в репозитории создана папка libs, содержащая JAR-ники добавленных библиотек. JAR тоже лежат в репозитории - чтобы каждый разработчик не искал, где скачать каждую библиотеку.(2)


Петя и Вася обновляют проект, спуллив(вытащив, от англ. pull) изменения коллег. Что интересно, у Васи была добавлена одна библиотека, а у Пети - 5. Потому что его библиотека для работы с БД использовала еще 4 другие и без них не работала. Выяснялось это долго, путем проб и ошибок. Теперь вся команда молится, чтобы нигде не возникло конфликта версий.(3)

Итак, Петя и Вася пытаются запустить проект - но что-то идет не так. Библиотеки, конечно, есть, но проект их не видит. Приходится доставать бубен, и под танцы подключать каждую библиотеку через IDEA/прописывая руками в classpath. После этого проекты запускаются у обоих - но теперь каждая новая библиотека требует повторять указанные действия, а фраза “Cannot resolve symbol* снится всей команде в кошмарах.(4)

*Ошибка, возникающая в т.ч., если не удалось найти нужный пакет.


Тем не менее, разработка движется, в проекте добавляются юнит-тесты. Тесты добавляются и даже актуализируются по ходу разработки. Но запускаются они редко - нужно запускать их вручную, о чем постоянно забывают. Но ничего страшного - меньше упавших тестов, целее нервы команды.(5)

Вообще, можно было бы работать и быстрее. Но все как-то долго. Для каждого запуска приходится скомпилировать проект, все это засунуть в jar, jar подложить локальному серверу и только после этого запустить. Дело привычки, но муторно.(6)

Параллельно ведется разработка админки. Там те же проблемы с подключением библиотек, но все уже привыкли.


К слову, недавно Петя улетел работать на Гоа, поэтому у него слабый Интернет. И, как назло, у него полетела винда - пришлось переустанавливать. 

Он знатно удивился, когда дошел до выкачивания проекта - оказывается, из-за библиотек, которые хранятся в гит-репозитории, исходники весят уже несколько сотен мегабайт - не критично, но неприятно. А выгружать пришлось два репозитория (помним про админку). По сути, 90% библиотек пришлось скачать дважды - они общие для этих двух проектов.(7)

Беды с библиотеками настигли и Васю - он весь день не мог запустить проект из-за ошибки - не находило какой-то пакет внутри библиотеки. Оказалось, накануне Петя обновил версию библиотеки, но не изменил название jar-файла - superLib.jar так и остался superLib.jar, а вот внутренняя структура пакетов в библиотеке в этой версии изменилась. Только к вечеру Вася понял, что дело не в том, что библиотека не подключилась - просто надо поправить импорт для конкретных пакетов. Ошибка от IDEA была одинаковой, а глаз уже замылился, так бывает.(8)


На каком-то этапе случилось страшное - к проекту решили подключить Lombok. Это библиотека, которая позволяет генерировать стандартный код, вроде конструкторов, сеттеров и геттеров декларативно - через указание соответствующих аннотаций. 

Проблема в том, что это происходит на этапе компиляции, посредством добавления нужных конструктов в байткод. И стандартный компилятор знать не знает, что делать с ломбоковскими аннотациями - их должен обрабатывать отдельный процессор от Lombok’а, который и сделает всю магию. 

Благо, можно объяснить компилятору, что он теперь должен запускать дополнительный процессор и все заработало. Но всю эту магию пришлось описывать в документации к проекту - теперь каждый разработчик должен настроить параметры запуска, чтобы проект компилировался. Ну, кроме того, что он еще должен добавить все библиотеки в classpath.(9)


Начали тестировать взаимодействие между основным сервисом и админкой. Они должны были обмениваться сообщениями, которые нужно было оборачивать с использованием библиотечного класса.

Оказалось, у нас были разные - конфликтующие - версии библиотек. Но никто не сопоставлял два жарника в разных репозиториях и раньше не заметили. Все испугались, но пофиксили за пару дней. Пришлось обновить еще несколько библиотек - чтобы не конфликтовали с той, из-за которой все сломалось. Больше всего времени потратили на то, чтобы понять, какие версии связанных библиотек подходят обновленной.(10)


Наконец, мы дошли до развертывания проекта на удаленном сервере - чтобы им могли начать пользоваться. Теперь уже настраивали classpath и javac (java-компилятор) там - но за столько месяцев мы с ними как родные. Вроде более-менее завелось.


После релиза команда разбежалась - кто слег с нервным истощением, кто решил, мол, ну в пень - лучше буду проституткой. Тим лид поматерился и пошел набирать новую команду для поддержки и развития продукта.


Взяли Илью. Вроде толковый малый, отправили разбираться. Через 3 недели он смог запустить проект в первый раз. Долго, но что поделать. Проверили успехи - оказалось, запустил только админку, основной проект не смотрел. Отправили курить дальше. Пришел на следующий день, обматерил нас, пояснил, что так работать нельзя и у двух проектов, написанных одной командой даже структура основных директорий отличается - непонятно, что вообще где лежит(11). И вообще, слишком много кода, могли бы хоть как-то логически разделить на части(12). Уволился.

Тим лид выслушал, купил смузи и ушел гуглить, как разделить проект на части, не разбивая на разные приложения. Узнал о модулях, был сильно удивлен. Много думал. Допил смузи. Подумал еще раз. Взглянул на пустой офис. И повесился на Ethernet-кабеле. 


Компания разорилась. Ни один из членов команды так и не вернулся в Java-разработку. Под плач половины маршрутки опустился занавес.

The end.


Конечно, описанная история утрирована и преувеличена. На самом деле, многие описанные ситуации можно автоматизировать с использованием скриптов. Или оптимизировать другими средствами. Какие-то из них - вроде несогласованности версий одной библиотеки в разных проектах - не решаются системами сборки, лишь упрощается процесс их отслеживания или даются иные инструменты, минимизирующие подобные риски.

В любом случае, предлагаю рассмотреть, как в описанной истории могла помочь система сборки. В истории выше есть нумерация ситуаций (или проблем), которые можно было избежать, используй команда условный Maven.


Возможности систем сборки

Централизация конфигурации

В отличии от подключения библиотек в ручном режиме, система сборки предоставляет один или несколько заранее определенных файлов, в которых можно найти информацию об используемых библиотеках (зависимостях), настройках окружения (вплоть до указания ожидаемой версии Java) и прочем.

Это, например, могло бы помочь с проблемой №8 - версия библиотеки была бы явно указана в файле, в котором должна быть указана. И ее изменение было бы легко заметить, просматривая коммит.

На самом деле, версия зависимости указывается не всегда. Но в случаях, когда она не указана - возникновение описанной ситуации маловероятно по другим причинам, которые мы тоже рассмотрим, но не скоро.


Управление зависимостями

Системы сборок предоставляют декларативный механизм, который позволяет указать названия и версии нужных библиотек. После чего система самостоятельно скачает и зарегистрирует нужные библиотеки. В большинстве случаев также будут скачаны транзитивные (используемые подключенными библиотеками) зависимости нужных версий. Иногда их будет необходимо указать вручную, но на практике это не самый частый сценарий и отследить, какой библиотеки не хватает обычно не сложно - как исходя из подсказок IDE, так и через изучение информации о зависимости в репозитории (об этом чуть ниже).

Эта функциональность спасает нас от танцев с бубном при подключении библиотек, решая проблемы 1, 2 (к ней еще вернемся), 3, 4, 10.


Репозитории

Тут можно уловить параллели с git. Системы сборки выкачивают зависимости из репозиториев (если возник вопрос, откуда же они берут их в автоматическом режиме). В рамках репозиториев зависимости структурированы по именам (если не вдаваться в подробности) и версиям.

Репозитории, в свою очередь, можно разделить на локальные (на компьютере) и удаленные (доступны через Интернет). 

Локальный репозиторий, если просто - это папка с установленным именем на вашем компьютере (или сервере). Путь папки определяется на этапе установки системы сборки. Учитывая, что вы вряд ли будете устанавливать ее вручную, воспользовавшись IDEA - для Windows такая папка будет лежать в директории вашего юзера, для Linux - в home (опять же, вашего юзера).

Название такой папки для Maven - .m2, для Gradle - .gradle. Именно туда, в первую очередь, пойдет система сборки, если вы укажете ей какую-то зависимость. И лишь если не найдет в локальном репозитории - пойдет искать в удаленных.

Именно это могло бы облегчить жизнь Пети, в случае с проблемой №7. Скачивать проект бы все равно пришлось. Но скачивания одних и тех же библиотек дважды можно было бы избежать. 
Впрочем, Петя сидит на винде, его уже ничто не спасет.

Вернемся к удаленным репозиториям. Их, если не вдаваться в подробности, можно разделить на три категории:

  1. Центральные. Те, которые доступны по умолчанию. Именно в них лежит абсолютное большинство популярных зависимостей. Для мавена это mvnrepository (https://mvnrepository.com/). И именно он обычно будет в первых результатах поиска при запросе “someLib maven dependency”. Например, знакомый нам логгер: ссылка (https://mvnrepository.com/artifact/log4j/log4j). Вы также можете обнаружить, что для зависимостей описываются транзитивные, если они есть, с указанием требуемых версий - то, о чем говорилось выше в контексте проблемы №2;
  2. Публичные. Их можно подключить в файле конфигурации и тогда поиск зависимостей будет производиться в т.ч. в них. Может потребоваться, если какой-то библиотеке не оказалось в центральном репозитории;
  3. Приватные. Как правило, это репозитории конкретных компаний, которые хранят там свои библиотеки для внутреннего пользования. Или библиотеки, которые по иным причинам не предоставляются в открытый доступ. Их все так же можно подключить в файле конфигурации, но потребуется логин и пароль для доступа к репозиторию (или иной способ авторизации).

В целом, на данном этапе нам будет достаточно центрального репозитория.


Автоматизация сборки и иных задач

Системы сборок предоставляют возможности (название может отличаться от системы сборки) по автоматизированной компиляции и запуску проектов. Если мы говорим о Maven и Gradle, это декларативно-используемые элементы - нам нужно лишь указать перечень “задач” в консоли (или через интерфейс IDE) и отработают нужные скрипты. Сюда может входить компиляция проекта, упаковка в JAR, запуск тестов или приложения и многое другое. Сами задачи можно выбрать как из стандартного набора (покрывающего основные потребности), так и написать свою. Последнее - достаточно редкая история.

И, наконец, одни задачи могут включать в себя другие, что позволяет свести запуск проекта к нажатию одного “зеленого треугольника”.

Использование этой функциональности могло бы решить проблемы 5 и 6.

Кроме того, система сборки позволяет указывать дополнительные инструкции для существующих задач. Например, можно указать условной задаче “Компиляция” на необходимость запускать процессор для Lombok. Как правило, типовые “дополнительные инструкции”, если они привязаны к конкретной зависимости, можно также найти в репозиториях и использовать декларативно. Это решило бы проблему №9.


Стандартная структура проекта

В силу современных подходов этот пункт кажется незначительным - IDE, примеры в Интернете и других источниках достаточно быстро формируют общие черты структуры приложения в голове разработчика.

Тем не менее, системы сборок предоставляют стандартные варианты структуры, что упрощает старт проекта в новой команде или знакомство с существующим проектом. В данном случае речь о верхнеуровневой организации - структуре директорий для кода проекта, ресурсов и конфигураций, тестов, тестовых ресурсах и т.д.

Кроме того, Maven (в отличии от Gradle) предоставляет инструментарий архетипов - шаблонов для общей структуры проекта, позволяя использовать один из стандартных вариантов или добавить свой (например, для использования внутри компании).

Так или иначе, системы сборки делают весомый вклад в то, чтобы структура проектов была стандартизированной, что сильно упрощает знакомство с новым проектом или его старт в свежесобранной команде. 

В нашем же случае, это решило бы проблему 11. Если думать, что к этому моменту проект хоть что-то могло спасти.


Модульность

Системы сборки предоставляют возможность разбить проект на модули, которые можно рассматривать как мини-приложение или мини-библиотеку.

По сути, похожие возможности предоставляет и Java, начиная с Java 9. Однако системы сборок позволяют интегрировать модули в процессы управления зависимостями, что может упростить работу с приложением в целом. Особенно, если речь идет о крупных проектах.

В отличии от пакетов, модули являются более крупным элементам структуры - каждый модуль может иметь свои, независимые от других, файлы конфигураций и ресурсов, собственные тесты и пр.

В нашем примере модульность позволила бы даже комфортно объединить основное приложение и админку в один репозиторий.

Ну и, очевидно, это решает проблему декомпозиции приложения на логические блоки - проблема 12.


Заключение

Системы сборок - достаточно обширная тема с большим количеством подводных камней. Однако столкнуться с ними придется еще не скоро. 

В рамках курса я постараюсь дать минимальный набор знаний по Maven и Gradle, которые позволят примерно понять их возможности в привязке к конкретным инструментам. Надеюсь, это позволит избавиться от ощущения магии и будет достаточным базисом для первых лет работы. Но в дальнейшем, вероятно, вам придется углубиться в изучение этих инструментов самостоятельно.


На сегодня все!

Если что-то непонятно или не получается – welcome в комменты к посту или в лс:)

Канал: https://t.me/ViamSupervadetVadens

Мой тг: https://t.me/ironicMotherfucker

 

Дорогу осилит идущий!

Report Page