Крейг Палермо, старший инженер-программист

Инженеры по взаимодействию с пользователем (UX) в Priceline часто создают компоненты React и Node.js для своих продуктов. У каждой команды свой способ работы; настраиваемые правила lint, соглашения об именах, выбор инструментов и т. д. Это создает проблемы, когда кому-то нужно внести изменения в кодовую базу другого продукта. Чувство незнакомости может оттолкнуть людей от работы, которая часто может расширить их навыки. Ограниченная видимость работы других команд снижает способность менеджеров по продукту планировать совместную работу, а также потенциальную совместимость существующей или текущей работы.

Такой разрозненный подход к разработке программного обеспечения в конечном итоге приводит к дублированию работы в рамках всей организации и ограничивает возможности бренда по оптимизации взаимодействия с пользователями по всем продуктам. Чтобы бороться с этой неэффективностью, недавно сформированная команда UX Platform начала создавать монорепортерное решение, цель которого - дать разработчикам возможность легко работать над проектами и линейками продуктов. Мы считаем, что этот подход, используемый некоторыми крупнейшими мировыми технологическими компаниями (Oberlehner, 2017), поможет технологической организации Priceline продвигать сотрудничество, индивидуальный рост и операционную эффективность.

Монорепозиторий - это стратегия разработки программного обеспечения, при которой код для многих проектов хранится в одном репозитории.

Сегодня на рынке с открытым исходным кодом существует несколько инструментов управления монорепозиториями, у каждого из которых есть свои плюсы и минусы. На этапе открытия мы исследовали трех потенциальных кандидатов: Lerna, Nx и Rush.

Выбираем фундамент

Priceline One, Система дизайна Priceline с открытым исходным кодом, уже использует Lerna для управления небольшим монорепозиторием, содержащим компоненты React, поэтому мы, естественно, решили изучить это как вариант для внутреннего монорепозитория компании. Однако мы обнаружили, что Lerna не хватало настраиваемости, которую мы хотели, а ее модель установки зависимостей на основе NPM означала, что она будет плохо масштабироваться, поскольку мы добавили большое количество проектов в репозиторий без дополнительных инструментов, таких как Yarn Workspaces. Альтернативы представили гораздо больше возможностей. Созданный Nrwl, Nx позиционирует себя как инструмент, который поможет вам развиваться, как Google, Facebook и Microsoft. Nx - более самоуверенный инструмент и казался хорошим вариантом, предлагая инструменты с встроенными Cypress, Jest и Prettier, но его применение политики единой версии для зависимостей сделало Nx для нас бесполезным, как и многие другие. проекты, которые нам нужно будет встроить, не имеют одинаковых версий зависимостей. Оценив Nx и Lerna, мы решили, что Rush предоставит золотую середину.

Разработанный Microsoft, Rush обеспечивает расширяемость для добавления пользовательских инструментов, таких как Lerna, а также гораздо более быструю модель управления зависимостями, основанную на PNPM, что сняло наши опасения по поводу длительного времени установки и обновления. Это также позволило нескольким проектам в одном монорепозитории зависеть от разных версий одной и той же зависимости. В конце концов, мы стремимся обеспечить соблюдение политики единой версии, потому что это может уменьшить время установки и путаницу во время разработки, по сравнению с необходимостью иметь дело с несколькими версиями одной и той же зависимости, используемой в нескольких проектах. Благодаря своей способности выполнять произвольные команды оболочки с небольшой конфигурацией, Rush позволил нам создавать глобальные команды для линтинга исходного кода и даже скаффолда проектов и компонентов (спасибо Plop, замечательному фреймворку микрогенератор). API Rush также позволяет командам ориентироваться на определенные проекты или подмножества дерева зависимостей проекта, что позволяет оптимизировать команды, интенсивно использующие процессор или диск; мы планируем исследовать эту функциональность больше по мере развития нашего монорепозитория.

Преимущества до сих пор

Общие инструменты

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

  • Сборник рассказов
  • Вавилон
  • ESLint

При этом мы создали согласованную среду для всех проектов, которая предоставила бы каждому проекту в монорепозитории свободный от конфигурации доступ к рекомендованным надстройкам Storybook, поддержку встряхивания дерева через Babel и возможность легко добавлять плагины для новых языковых функций. , а также согласованный стиль кода и правила автоматического форматирования. Кроме того, эта среда способствует быстрому развертыванию общекорпоративных инициатив, таких как инструменты обеспечения доступности и правила lint, для предотвращения использования кода, запрещенного InfoSec. Кроме того, рабочий процесс управления зависимостями Rush автоматически генерирует запись в журнале изменений и метку для каждого изменения, что обеспечивает одно из наиболее важных преимуществ работы над связанными проектами в монорепозитории: всегда актуальное управление зависимостями.

Сниженные накладные расходы разработчика

Работа над зависимыми проектами Javascript, каждый из которых находится в отдельном репозитории, обычно включает накладные расходы на публикацию тега предварительного выпуска, установку, тестирование и повторение при возникновении проблем. Изменения в каждом репо требуют отдельного запроса на вытягивание (PR) и этапа публикации, поскольку обновление до новой версии зависимости в каждом проекте выполняется вручную. Этот процесс быстро становится утомительным для разработчиков, повторяющих изменения. Кроме того, разработчик продукта не всегда может обновить всех потребителей измененного пакета до новой версии. Со временем это приводит к тому, что проекты в экосистеме устаревают, что может вызвать трудноотламываемые ошибки и головные боли, когда критический патч требует развертывания для всех проектов. Совместное размещение проектов в монорепозитории помогает решить эту проблему за счет автоматического управления обновлениями зависимостей.

Например, предположим, что у нас есть зависимые проекты A <- B <- C, и мы вносим изменения в Project C. Когда мы будем готовы к запуску Project C, Rush автоматически обновит версию Project C, установленную в Project B, что, в свою очередь, повысит версию Project B и так далее. Затем разработчик может убедиться, что его изменение не повлияло отрицательно на кого-либо из его потребителей, проверив изменение стандартными способами: запустив модульные и интеграционные тесты, проверив Storybook и развернув в среде для ручного или автоматического тестирования.

Хотя этот рабочий процесс может повлечь за собой более высокие начальные затраты, чем работа в среде polyrepo, он дает несколько явных преимуществ:

  • Разработчики должны учитывать исходное влияние своих изменений в режиме реального времени, вместо того, чтобы предполагать, что кто-то другой обработает это позже. Никто не понимает масштаб и последствия изменений лучше, чем разработчик, работая над ними.
  • Установки и обновления в большинстве случаев превосходят типичный рабочий процесс polyrepo NPM, потому что PNPM загружает каждую версию каждой зависимости один раз на диск и связывает их с проектами, которые в них нуждаются (см. Тесты).
  • Постоянно обновляя все проекты в ходе обычной работы, мы можем повысить точность наших прогнозов во время планирования проекта. Связь всех проектов и их тестов также обеспечивает большую уверенность при массовом обновлении общих зависимостей по сравнению с выполнением одного и того же обновления в нескольких репозиториях.

Время, необходимое для внесения изменений в несколько проектов polyrepo, зависит от количества затронутых проектов. Каждое изменение может потребовать нескольких утверждений, сборок CI и изменений версии, каждое из которых отнимает у разработчика время. С другой стороны, в проекте monorepo изменения в проектах объединяются в одно атомарное изменение с одним этапом утверждения и одной сборкой CI. Это резко снижает накладные расходы на внесение изменений и их распространение вверх по течению. Визуализируя типичный рабочий процесс разработки компонентов React, мы видим, что это равносильно распараллеливанию частей рабочего процесса, что приводит к сокращению времени цикла.

Не без проблем

При разработке стратегии миграции мы принимали во внимание различные соображения, как технические, так и культурные. Мы знали, что нельзя просто скопировать текущее состояние всех проектов в новое репо и положить этому конец. Историческая миграция сыграла большую роль в поддержании преемственности и вселила большее доверие у разработчиков, которые, как мы понимали, переходили в захватывающую, хотя и незнакомую среду. С этой целью мы обнаружили, что Shopsys's Monorepo Tools легко справились с большей частью тяжелой работы, которая позволила нам сохранить историю каждого проекта, в котором мы участвовали, хотя это составляло отдельный этап процесса миграции для каждого проекта. Чтобы помочь бортовым разработчикам, мы проводили тренинги для команд, когда переносили их компоненты в новый репозиторий. Мы также сформировали неофициальную группу сторонников монорепозитория в надежде привлечь внимание к поддержке и обучению, поскольку более сотни разработчиков Priceline начинают работать с монорепозиториями, многие - впервые. Во время процесса адаптации мы также настроили непрерывную интеграцию (CI) для монорепозитория, предоставив при сборке CI слишком много компонентов, которым раньше не хватало собственных, часто из-за ограничений по времени и опыту разработчика.

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

По мере того как в новом монорепозитории появляется все больше проектов, у нас возникают некоторые опасения по поводу масштабируемости нового рабочего процесса и среды разработки. Во-первых, с таким количеством разработчиков, которые вносят изменения в одну и ту же главную ветвь, мы ожидаем значительного оттока в истории коммитов, который может быстро превратиться в беспорядок без правильной стратегии слияния. Чтобы решить эту проблему, мы требуем, чтобы разработчики объединяли запросы на вытягивание только, подавляя свои изменения. Это позволит нам легко связать каждую фиксацию с конкретным изменением. Помимо сохранения читабельности истории коммитов, мы также должны позаботиться о том, чтобы не ухудшать производительность часто используемых команд git, таких как status, clone и push . На сегодняшний день наш монорепозиторий весит около 290 МБ. Хотя у нас еще не возникло проблем с производительностью, даже при удаленной работе, мы ожидаем, что этот размер со временем будет увеличиваться, и планируем изучить методы смягчения, такие как git sparse-checkout. Мы надеемся, что это позволит разработчикам работать над определенными наборами проектов без клонирования всего репозитория, что сократит время загрузки и сборки.

Заключение

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