Всем привет. Меня зовут Сергей, я работаю в digital-агентстве Convergent лидером команды бэкенд разработки. Одно из основных направлений работы агентства ? это разработка под заказ веб-приложений. Такая деятельность подразумевает, что зачастую создается достаточно много однотипных проектов. Они могут отличаться механиками, но основные юзкейсы повторяются: регистрация, авторизация, личный кабинет, админка и т. д. В данной статье я хочу рассказать о том, как мы оптимизировали процессы переиспользования кода и пришли в итоге к модульному подходу, ну и затрону еще немного технической стороны вопроса. Основной язык программирования в компании — PHP, так что дальше я расскажу о работе с этим языком. В нашем случае он отлично подходит для создания сайтов различного уровня сложности, различных активаций и промо-кампаний.

Столкнувшись с большим количеством однотипных проектов, сразу захотелось создать универсальный шаблон для работы. Мне уже не раз доводилось создавать CMS и небольшие фреймворки, так что я захотел собрать на Yii свою собственную универсальную «админку».

«Сборка»

Так появилась «Сборка». Технически ? это простой шаблон проекта, который каждый раз удобно переносить в новый проект.

Пример того, как выглядит наша админка сейчас
Пример того, как выглядит наша админка сейчас

Потом начался переход на Yii 2.0, что позволило добавить в шаблон много различных фишек. Вместе с тем миграция с 1.1 на 2.0 далась непросто:

  • Бесшовной обратной совместимости новая версия не имела, поэтому потребовалось провести полный рефакторинг кода.

  • Новый API фреймворка ?  а значит, команде пришлось переучиться работать на нем. Это как-то само собой получилось, но сейчас я бы поставил это как цель в Personal Evaluation.

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

Параллельно с Yii 2 мы перешли на инфраструктуру доставки зависимостей с помощью Composer. До этого сам фреймворк и все его расширения хранились прямо в системе контроля версий вместе с кодом проекта.

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

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


Схема взаимодействия фреймворка и модулей
Схема взаимодействия фреймворка и модулей

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

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

Пример актуальной структуры модуля
Пример актуальной структуры модуля

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

Что я сделал:

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

  • Изменил неймспейсы на фирменные (convergent/…)

  • Оформил composer.json с зависимостями для каждого пакета.

  • Сделал несколько шаблонов приложений Yii, которые имели зависимости от нужных им модулей.

Таким образом любой новый проект теперь начинается в том числе с выбора шаблона:

  • Классический веб-сайт - обычный сайт, с шаблонизацией с помощью PHP и сборкой CSS/JS с помощью WebPack.

  • REST-приложение (с раздельным фронтом) - решение для проектов, которые пишутся например на Nuxt или как API для мобильных приложений.

  • Чатбот-приложение. У нас есть собственная разработка на базе Botman.

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

Packager

В итоге мы сделали собственный внутренний продукт - Packager, созданный на базе Satis.

Packager стал некой прослойкой, которая делает следующий вещи:

  • Подключает модули. Все они хостятся в GitLab, а в Packager указываются ссылки на них.

  • Создает CHANGE-лог модулей.

  • Организует работу с лицензиями и позволяет видеть общую картину по всем проектам и клиентам.

  • Управляет Composer-репозиториями. Мы решили разделить их по клиентам, т. к. они покупают лицензии на модули не в рамках проектов, а в рамках юридического лица. Это основная фича, которая нам требовалась.

  • Разделяет доступ к репозиториям для разработчиков и для клиентов (и для CI). Сам Satis не имеет встроенного механизма авторизации, поэтому была создана прослойка на Lua между nginx и PHP.

В целом получилась весьма неплохая Standalone-замена платному Packagist’у.

Так выглядит админка репозитория в Packager
Так выглядит админка репозитория в Packager

Версионирование модулей

Еще одним важным моментом в работе с модулями является их версионирование. Мы используем стиль SemVer (в формате Major.Minor.Patch):

  • Исправление багов увеличивает значение Patch

  • Новая фича увеличивает Minor

  • Если нарушается обратная совместимость, увеличивается Major

В каждом проекте, который подключает модули, по умолчанию прописывается ~1.0:

"convergent/yii2-admin": "~1.0"

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

При выпуске модуля с новой фичей, которая используется в приложении, разработчики просто меняют минимальную версию с ~1.0 например на ^1.1 (что значит > 1.1.0 и < 2).

CI/CD

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

Большое количество проектов ранее собиралось с помощью Jenkins. Обычно все сводится к composer install в воркере Jenkins, копированию файлов и выполнению миграций на production сервере. В реальности же, конечно, каждый проект чем-то отличается.

К переходу на GitLab CI и Docker в большей степени меня подтолкнула задача развертывания проекта во внутренней сети заказчика, доступ к которой извне недоступен.

Опишу ключевые вещи:

  • Образ приложения наследуется от PHP-FPM.

  • В нем устанавливаются все пакеты, необходимые для работы, в т. ч. cron и supervisor.

  • В образ приложения добавляются vendor библиотеки, в т. ч. модули Yii.

  • Также добавляются конфигурации и entry-скрипты для запуска fpm/cron/supervisor.

  • Создается docker-compose, с помощью которого настраиваются nginx, MySQL, а также задаются три контейнера от общего образа для запуска fpm/cron/supervisor.

  • На production сервере создается docker-compose, а при выходе новой версии образа приложения запускается docker-compose pull + down + up.

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

Что дальше?

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

Packager - внешний продукт? Пока мы используем только для своих нужд, но есть мысли, что инструмент может быть полезен другим разработчикам на PHP, как думаете?

Ресурсы по теме