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

командой Whitespectre Rails

Представьте себе проект, работающий на Ruby on Rails, который используется несколькими клиентами и/или службами, каждый из которых использует свою версию проекта. Одна команда может работать над новой фичей для конкретного клиента, и с их стороны все работает нормально, но когда они сливают эту фичу на основной гем, она ломается для других клиентов, и все в беспорядке.

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

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

Короче говоря, монорепо

При рассмотрении SaaS мы рассмотрели различные варианты, но с одной четкой целью: разработать решение Ruby on Rails, которое можно было бы настроить для каждого клиента, но при этом использовать общие функции через библиотеки. Мы обнаружили, что подход монорепозитория был ответом на нашу проблему, как с технической точки зрения, так и с точки зрения рабочего процесса.

Проще говоря, он заключается в объединении всех проектов и их зависимостей (gems) в один репозиторий. Это означает, что версии гемов каждого проекта перестанут использоваться, и все они будут использовать текущую версию.

От структуры мультирепозитория к монорепозиторию

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

CI — кодовая поставка:

  • Многорепозиторий: один конвейер доставки кода на репозиторий.
  • Monorepo: пользовательские скрипты, которые запускают спецификации только для затронутых проектов.

CD — сборка Heroku через Codeship:

  • Многорепозиторий: автоматическое развертывание, привязанное к каждому конвейеру.
  • Monorepo: автоматически развертывается на определенном сервере на основе изменений или ответвлений.

Организация папок:

  • Многорепозиторий:
  • Одно репо на папку
  • Общие библиотеки поддерживаются и поддерживаются в Gemfury для обеспечения конфиденциальности.
  • Монорепо:
  • Bin: Скрипты, упрощающие управление репозиторием CI/CD, настройку и т. д.
  • Реализации: где у нас есть настройка каждого проекта для каждого клиента. Все реализации: имя как client-implementation
  • Shared: эта папка содержит папку gems, которая помещается в корень проекта в момент развертывания (с использованием пакета сборки). Здесь должны быть добавлены новые драгоценные камни/общие библиотеки.

ГИТ:

  • Многорепозиторий: Мастер – › Постановка – › Производство
  • Monorepo: Master > ветки Staging и Production для каждого проекта.

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

К сожалению, вариантов инструментов для монорепозитория на Rails не так много, и мы не смогли вернуться к готовому решению. Например, популярные инструменты, такие как Bazel или Yarn, сложно настроить, но самое главное, они не специфичны для Ruby. Если бы мы использовали их, нам все равно пришлось бы адаптировать их для этой цели. Поэтому мы решили собрать наши скрипты; некоторые основаны на существующих решениях; и другие (например, CI/CD, которые мы увидим ниже), были написаны с нуля.

Улучшение процесса разработки с помощью настраиваемых скриптов

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

  • Выполнение любой команды, связанной с rails: миграция, создание модели и т. д.
  • Запуск тестов/линтер.
  • Запуск определенного приложения.
  • Дедупликация миграций между проектами.
  • Запустите развертывание специально для приложения.

Основные изменения в процессе развертывания

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

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

Как работает процесс CI/CD в QA?

CI автоматически определяет реализацию с изменениями или изменениями в общем геме, а затем запускает спецификацию для всех измененных при развертывании. На стороне CD делаем нечто подобное: выявляются изменения, а затем строятся только реализации с изменениями. Однако это работает по-разному для QA и Stage/Prod, как вы можете видеть на следующей диаграмме:

Сценарии CI/CD в действии

В нашем процессе CI мы автоматически определяем, какая реализация изменилась, используя команду git и некоторую справку ruby:

def ci_changed_projects
  return @ci_changed_projects if defined?(@ci_changed_projects)
  @ci_changed_projects = {}
  changes_cmd = `git log -m -n 1 --name-only --pretty=format:"" | sort | uniq -u`
  if changes_cmd.size.zero?
    print_log 'no changes'
    return @ci_changed_projects
  end
  changes_cmd.split.each do |change|
    dir_struct = change.split('/')
    next unless %w[implementations shared].include? dir_struct[0]
    is_a_gem = dir_struct[0] == 'shared'
    next if is_a_gem && !dir_struct.include?('god-gem')
    project = dir_struct[is_a_gem ? 2 : 1]
    @ci_changed_projects[project] = is_a_gem unless @ci_changed_projects.key?(project)
  end
  @ci_changed_projects
end

Обнаружение изменений для каждой папки

Для развертывания на конкретном сервере на компакт-диске мы используем следующий скрипт плюс пользовательский пакет сборки, который специально выбирает проект для этого экземпляра Heroku и переупорядочивает gemfile и package.json, чтобы они находились в корневом каталоге. Таким образом, мы сохраняем небольшой размер слагов и хорошо управляем зависимостями.

Процесс CI/CD в стадии подготовки

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

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

Подведение итогов: монорепо, да или нет?

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

Преимущества монорепозитория

После внедрения этого решения преимущества стали очевидны в рабочем процессе нашего проекта. Вот основные преимущества, которые мы обнаружили:

  • Улучшение адаптации разработчиков
  • Все становится проще, поскольку разработчики получают все необходимое для внесения изменений в любой проект с помощью простой команды клонирования.
  • Быстрая и плавная разработка
  • Легче запускать настройки для общих библиотек локально, поскольку они не имеют версий.
  • Каждая реализация имеет более чистую миграцию, драгоценные камни и файлы зависимостей js. Для нашего подхода, в частности, каждая новая реализация в основном является ответвлением стандартной, при этом у нас есть монорепозиторий, в котором мы можем создавать сценарии, избегающие их дублирования в проектах.
  • Простое развертывание
  • Нам больше не нужно обновлять версии gem перед каждым развертыванием.
  • Проверка кода становится проще
  • Несмотря на большие PR, что на самом деле не очень хорошо, теперь мы можем видеть, как изменение влияет на несколько проектов/библиотек в одном запросе на включение.

Недостатки монорепозиториев

Как обычно, не все так гладко, и у этого подхода есть свои недостатки:

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

3 момента, о которых следует помнить при переходе на монорепозиторий

Итак, если после взвешивания преимуществ и недостатков вы хотите продолжить работу с подходом Monorepo, вот 3 совета на переходный период. Их применение изменит все.

  • Проекты нельзя обновить сразу
  • Чтобы решить эту проблему, мы сохранили некоторые драгоценные камни, указывающие на версионную версию драгоценного камня, а не на версию в монорепозитории, чтобы мы могли обновить ее позже.
  • В проектах временно будут использоваться разные версии из-за разных функций/уровней
  • Здесь мы разработали механизм ветвления, где ветки будут содержать определенные версии проекта для релизов. Благодаря этому мы смогли решить проблему наличия разных версий каждого проекта в разное время.
  • Выбирать только конкретный проект и зависимости при развертывании
  • Это было достигнуто за счет настройки существующего пакета сборки heroku для монорепозитория. Мы выбрали реализацию на основе переменной env и переместили ее зависимости в корневую папку проекта. Также нам пришлось изменить gemfile.lock и package.json, чтобы они указывали на правильное место.