PHP Profi

Монолитные репозитории для PHP и Composer Перевод

php composer package пакет fiddler repositories

Монолитные репозитории предоставляют множество преимуществ. Я создал прототип Fiddler - дополнение для Composer, добавляющее управление зависимостями для монолитных репозиториев в PHP.

Спасибо Александру за обсуждение этой темы со мной, а также за рассмотрение моего проекта и этого поста.

С широким распространением Git и Composer в проектах с открытым кодом и в компаниях, монолитные репозитории, содержащие несколько проектов, стали несколько плохой практикой. Такая практика, как и монолитные приложения, существует вне моды. А последняя тенденция - это всякие фишки, связанные с микросервисами и  Docker'ом.

Composer сделал возможным создавать множество маленьких пакетов и с лёгкостью  распространять их через Packagist. Это очень улучшило экосистему php путем увеличения использования повторного кода и предоставления доступа к нему извне.

Но важно учесть распространение пакетов и разработку каждого из них отдельно друг от друга. Развитие менеджера пакетов связано с продуктивностью при управлении версиями зависимостей, так как Composer, NPM, Bower вынуждают использовать ровно один репозиторий для одного пакета, чтобы выгодно распространять или повторно использовать этот пакет.

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

Рабочий процесс в Facebook, Google, Twitter

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

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

Недостатки подхода с использованием большого количества репозиториев

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

  1. Перекрестные изменения в репозиториях требуют определенных pull-реквестов на Github/Gitlab для слияния (merge) в определённом порядке или в комбинации. Имеющиеся инструменты не обеспечивают видимость этих зависимостей.
  2. Закрепление версий через NPM и пакетный менеджер в Composer отлично подходит для управления сторонними зависимостями до тех пор, пока их не слишком много, и они не часто меняются. Для внутренних зависимостей совершается много работы по постоянному обновлению связей между ними. Если зависимости неправильные, разработчики потеряют много времени из-за ошибок в процессе слияния.
  3. Изменения кода в библиотеках ядра могут нарушить зависимости без ведома разработчика, потому что тесты не могут запускаться вместе. Это приводит к более длительному циклу обратной связи между зависящими друг от друга частями кода, со всеми вытекающими отсюда негативными последствиями.

Одно важное замечание: монолитные репозитории не значит монолитный код. ZF2 и Symfony2 хороший пример того, как можно построить отдельные компоненты с графом чистых зависимостей в одном большом репозитории.

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

Чем выгодны монолитные хранилища

Даже если рассматривать единый репозиторий не в масштабах Facebook и Google, он по-прежнему даёт преимущества:

  • Адаптация к постоянным изменениям при операциях с внешними библиотеками, слияниях (merge) библиотек, операциях по внедрению новых зависимостей для нескольких проектов гораздо легче для единичных коммитов.
  • Понятность кода намного выше, если он находится весь в одном месте. У Github и Gitlab нет мощных инструментов для grep, find, sed при количестве репозиториев больше одного. Спускаться вниз по дереву зависимостей займёт много времени.
  • Увеличивается переиспользование, так как гораздо проще взять код из того же самого хранилища, чем из другого репозитория. Composer и NPM упрощают сочетания репозиториев в определённых случаях, но при этом всё же нельзя быть уверенным, что код существует в первоначальном месте.
  • При использовании единого репозитория, проще подключить к проекту нового разработчика. На практике просто добавить его публичный ключ только для одной команды/репозитория/директории, чем для сотен. Ну и конечно, создание маленьких репозиториев и ознакомление с ними занимает кучу времени.

Вот почему я борюсь с тем, как Packagist и Satis заставляют двигаться в сторону более мелких хранилищ путем технических ограничений "один репозиторий - один файл composer.json". Для многократного использования проектов с открытым исходным кодом это прекрасно, но для проектов компаний этот подход используют чаще, чем надо, что ухудшает производительность разработчиков.

Введение в Fiddler

На сегодняшний день я создал прототип сборки системы, дополняющий Composer для управления несколькими раздельными пакетами/проектами для единого репозитория. Я назвал его Fiddler ( скрипач ). Fiddler вводит подход в управлении зависимостями для нескольких проектов в одном репозитории без потери преимуществ, связанных с наличием явных зависимостей для каждого отдельного проекта.

На практике Fiddler позволяет управлять всеми сторонними зависимостями с использованием файла composer.json, с добавлением нового способа управления вашими внутренними зависимостями. Он объединяет как внешние, так и внутренние пакеты в единый пул и позволяет выбрать их в качестве зависимостей для ваших проектов.

Для каждого проекта вы добавляете файл fiddler.json, где указываете и те, и те зависимости. Fiddler позаботится о создании конкретного автозагрузчика для каждого проекта, содержащего только зависимости проекта. Это позволяет вам иметь только один репозиторий, пользуясь явными зависимостями для каждого проекта.

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

Пример проекта

Допустим, в вашем приложении три пакета: Library_1, Project_A, Project_B. Оба проекта зависят от библиотеки, которая, в свою очередь, зависит от symfony/dependency-injection. Репозиторий будет иметь следующую структуру:

projects
├── components
│   ├── Project_A
│   │   └── fiddler.json
│   ├── Project_B
│   │   └── fiddler.json
│   └── Library_1
│       └── fiddler.json
├── composer.json

Для Library_1 файл fiddler.json выглядит так:

{
    "autoload": {"psr-0": {"Library1\\": "src/"}},
    "deps": ["vendor/symfony/dependency-injection"]
}

fiddler.json для проектов A и В выглядит похоже:

{
    "autoload": {"psr-0": {"ProjectA\\": "src/"}},
    "deps": ["components/Library_1"]
}

Глобальный composer.json такой:

{
    "require": {
        "symfony/dependency-injection": "~2.6"
    }
}

Как видите, зависимости указаны без ограничений версий и с относительными путями к файлу проекта. Так как всё находится в одном репозитории, весь внутренний код всегда версионирован, тестирован и развернут одновременно. Для сброса необходимо явное версионирование при указании внутренних зависимостей.

С таким подходом вы можете генерировать файлы автозагрузки для каждого пакета, точно так же, как это сделал бы Composer, вызвав:

$ php fiddler.phar build
Building fiddler.json projects.
 [Build] components/Library_1
 [Build] components/Project_A
 [Build] components/Project_B

Теперь в каждом пакете вы можете  подключить "vendor/autoload.php". Он подтянет автозагрузчик со всеми  зависимостями, указанными для каждого компонента, например, в components/Library_1/index.php

require_once "vendor/autoload.php";

$container = new Symfony\Component\DependencyInjection\ContainerBuilder;

Это все пока предварительно, пожалуйста, попробуйте у себя и пишите ваше мнение. Ценно это или нет и о  возможностях расширения. Смотрите README. Код грубый и простой на данный момент, наверняка вы найдёте баги, сообщайте о них, пожалуйста.

2015-05-05 irul Поделиться: оригинал