Управление зависимостями на примере composer
Недавно я объяснял Илье Бирману, как инструменты управления зависимостями помогают разрабатывать программное обеспечение. С его разрешения публикую адаптированный вариант.
Я спрашиваю, зачем это, пытаюсь увидеть пользу. Я вижу, как это усложняет систему, но не вижу, как помогает […] Я пытаюсь разобраться.
Объясняю на примере Эгеи — движка блогов, который разрабатывает сам Илья.
Суть проблемы
У Эгеи есть несколько зависимостей. Ты разрабатываешь Лайкли, Жуэль, Нисден — каждый продукт в своем репозитории. А еще есть jQuery, плагины для календаря, растягивания полей ввода текста по содержимому и т. д.
В «традиционном» подходе ты скачал компоненты, положил рядом с кодом. Может быть, даже добавил в гит, чтобы не потерять.
Прошло полгода. Пора делать новый релиз. И если нет смысла торопиться обновлять jQuery, наверняка ты захочешь обновить свои же зависимости. Опять копируешь файлики, делаешь коммит Эгеи.
В чем недостатки этого подхода?
- История в репозитории Эгеи оказывается забитой посторонними изменениями. Кому интересно видеть в git log, что добавилось в jQuery между версиями 2.1.3 и 2.1.4?
- На рутинную работу уходит всё больше и больше времени.
- Более того, рутинная работа — потенциальный источник ошибок. Когда нет скрипта, который собирает дистрибутив по одной команде, приходится собирать архив вручную. Смотреть, чтобы в нем были все нужные файлы и не попало
что-нибудь лишнее.
Инструменты управления зависимостями
Инструменты управления зависимостями решают не только проблему их установки. Пусть наш проект зависит от пакетов А и Б. В свою очередь, пакеты А и Б зависят от пакета В. Все три пакета развиваются с переменной скоростью, и каждый существует во многих версиях. Инструменты вычисляют все зависимости и устанавливают совместимые друг с другом версии пакетов А, Б и В. Например, если пакет Б давно не обновлялся и не работает со свежей версией В, будут установленные ранние версии пакетов А и В.
В каждой области существуют свои инструменты управления зависимостями. В серверном яваскрипте — npm. В браузерных js- и
В мире PHP ту же работу выполняет composer. Мне больше нравится его модель работы с фиксацией версий в
Composer
Composer работает с двумя файлами: composer.json и composer.lock. В первом мы перечисляем пакеты и версии, от которых зависит наш проект, например, "jquery": "2.1.*"
. Команду composer update
запускаем в ходе разработки при обновлении версии имеющейся зависимости или добавлении новой. composer update
скачивает пакеты в папку vendor и запишет точные версии пакетов (например, 2.1.4) во второй файл composer.lock.
$$\text{\tt\large composer update:}\vspace{0.3cm} \tikzstyle{block} = [rectangle, fill=red!15, text width=7em, text centered, minimum height=3em] \begin{tikzpicture}[node distance = 3.6cm,font=\sffamily] \node [block] (json) {\tt composer.json\\ 2.1.*}; \node [block, right of=json] (lock) {\tt composer.lock\\ 2.1.4}; \node [block, right of=lock] (vendor) {\tt vendor/}; \draw [->] (json) to[out=-30, in=210] node[below] {\text{\small определяет версию}} (lock); \draw [->] (lock) to[out=-30, in=210] node[below] {\text{\small скачивает}} (vendor); \end{tikzpicture}$$
Оба файла composer.json и composer.lock добавляются в гит. Тем самым автор коммита гарантирует, что код заработает с указанными конкретными версиями зависимостей (2.1.4). А вот папка vendor в гит не добавляется. В итоге
Команда composer install
разворачивает код на сервере или у другого разработчика. Она не проверяет существование новых версий пакетов, а восстанавливает зависимости на момент коммита из файла composer.lock.
$$\text{\tt\large composer install:}\vspace{0.3cm} \tikzstyle{block} = [rectangle, fill=green!18, text width=7em, text centered, minimum height=3em] \begin{tikzpicture}[node distance = 3.6cm,font=\sffamily] \node [block, right of=json] (lock) {\tt composer.lock\\ 2.1.4}; \node [block, right of=lock] (vendor) {\tt vendor/}; \draw [->] (lock) to[out=-30, in=210] node[below]{\text{\small скачивает}} (vendor); \end{tikzpicture}$$
Пакеты композера хранятся на packagist.org. Но есть возможность подключить любой
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/ilyabirman/Jouele"
}
],
"require": {
"ilyabirman/jouele": "dev-master"
}
}
и composer update
всегда будет обновлять библиотеку до последнего
коммита в ветке master.
Composer делает еще одну важную вещь — настраивает автоматическую загрузку библиотек. Подключаем сгенерированный файл autoload.php:
require __DIR__ . '/vendor/autoload.php';
и используем классы из зависимостей без дополнительных инструкций include/require.
Особенности bower и npm
Недостаток менеджеров bower и npm в том, что у них нет аналогов команды composer update
и файла composer.lock. Они не гарантируют, что восстановят все зависимости до того состояния, на котором ты проверил, что всё работает. Например, если в bower.json написали "jquery": "2.1.*"
, то команда bower install
будет скачивать новые версии jquery по мере их выхода, даже если ты этого не хочешь.
Как это всё использовать?
Для разработки: ты добавляешь к своим зависимостям composer.json и делаешь их composer update
скачивает в папку vendor последние версии зависимостей.
Для сборки: ты не хранишь в репозитории саму папку vendor. Пишешь git clone
и composer install --no-dev
, а потом архивирует результат.
Инструменты управления зависимостями обеспечивают масштабируемый подход. Они одинаково хорошо работают и с несколькими зависимостями, и с десятками или даже сотнями. Последняя ситуация — не редкость при использовании composer create-project symfony/framework-standard-edition my_project_name
скачивает типовое приложение на symfony и устанавливает 35 зависимостей суммарным объемом 33 мегабайта.
Комментарии
Добавил картинок, чтобы было понятнее.
Я так понимаю движок S2 начал использовать composer? :)
P.S.
А какие именно темы интересуют? Мне кажется, что любая статья будет тривиальным повторением того, о чем уже и так много раз везде писали.
Например:
— «Я не понимаю зачем этот Dependency injection когда есть.»
— Как я использовал Service Locator для своего проекта
— Memcached vs Redis
— А вот как я отбился от DoS
— Появился PHP7 — Ruby умирает (холиварчик)
Статья понравилась, спасибо!
Но с появлением yarn пользоваться npm больше нет смысла.
Оставьте свой комментарий