Часто путешествие может многому научить вас о пункте назначения. Компания Empathy.co уже много лет работает над проектами веб-интерфейса. Попутно мы создали множество библиотек для решения разных задач.

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

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

Предыдущий рабочий процесс

Допустим, мы разрабатываем функцию для нашего основного пакета X Components. Эта библиотека имеет две основные зависимости: адаптер X и типы X.

X Components — это библиотека Vue, которая позволяет разработчикам легко создавать поисковые приложения с компонентами, которые разумно взаимодействуют между собой.

X Adapter – это пакет, который служит для связи с API и преобразует его модель предметной области в модель, понятную X-компонентам.

Наконец, X Types — это проект, содержащий модель предметной области X Components. Просто куча типов и охранников типов, помогающих нам писать типобезопасный код.

На диаграмме зависимостей эти модули выглядят так:

Разработка новой функции подразумевает изменение этих трех пакетов в соответствии с последовательным подходом:

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

Теперь разработчик этой функции должен загрузить последние изменения проекта типов, скомпилировать их и каким-то образом установить локально. Здесь мы попробовали связывание NPM, но всегда боролись с дублированием зависимостей из-за того, как работает система разрешения модулей узла. Поэтому мы решили просто упаковать наши локальные сборки с помощью пакета NPM и установить сгенерированный tarball-файл в нужные нам проекты. Затем разработчик может начать работу над своей задачей и отправить запрос на включение после завершения. Их товарищи по команде рассмотрят его и объединят, как только все будет в порядке.

Наконец, мы можем разработать необходимые изменения в проекте X Components. Загрузите оба проекта, соберите их, выполните локальную установку обоих, разработайте изменения, отправьте запрос на включение и объедините его.

Это был просто простой пример только с некоторыми проектами, с которыми мы обычно работаем. Чем больше проектов у вас есть, тем больше проблем. Давайте посмотрим на некоторые из них.

Медленная разработка

Все ранее упомянутые шаги создавали для нас трения. Функции разрабатывались последовательно; мы не могли параллельно работать в разных пакетах одновременно.

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

Обработка версий зависимостей

Обработка версий зависимостей также была непростой. Мы используем TypeScript в каждом проекте. Если у нас есть 10 проектов, создание 10 пулл-реквестов только для того, чтобы поднять эту версию, — дело не быстрое.

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

Версии наших проектов

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

Это было подвержено ошибкам, медленно и непоследовательно. Поэтому мы решили возложить эту ответственность на разработчиков каждого пулреквеста. Когда был отправлен запрос на вытягивание, разработчик должен был обновить версию проекта с тегом предварительного выпуска, увеличив число до того, что, по их мнению, может быть правильным. Это тоже не было хорошей идеей, так как каждый пулл-реквест заканчивался комментариями типа «исправьте конфликты package.json и CHANGELOG.md, пожалуйста».

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

Ключевые изменения

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

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

Выпуски

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

Следуя предыдущему примеру, это означало сначала выпустить проект без зависимостей, X Types. Затем обновление и выпуск X-адаптера и, наконец, X-компонентов. Если вам нужно выпустить библиотеку, которая используется в 5–6 и более проектах… Держу пари, вы можете почувствовать нашу боль!

Поиск решения

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

В Empathy мы хотим поддерживать открытый исходный код. Если наш код опирается на библиотеки с открытым исходным кодом, почему бы не попытаться вернуть что-то сообществу? Просто открыть наши репозитории для публики со старыми подходами не получится. То, как они были структурированы, никому не помогало понять код, и давайте не будем говорить о публичных пулл-реквестах. Было слишком сложно ожидать, что какой-либо разработчик настроит локальную среду разработки и откроет запрос на вытягивание.

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

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

Первым шагом был выбор инструмента для работы с монорепозиторием. Есть много возможностей: Yarn, Pnpm, Lerna или даже TypeScript поддерживает что-то подобное через ссылки на проекты. Я здесь не для того, чтобы обсуждать, какой из них лучший, самый быстрый или тот, который использует меньше памяти. Все они являются замечательными инструментами, поддерживаемыми сообществом открытого исходного кода.

Мы выбрали Lerna. Это инструмент, предназначенный для облегчения работы с монорепозиториями. Это немаловажная его особенность — благодаря монорепозиториям родилась Lerna. Из-за этого он предоставляет несколько утилит, помогающих работать с ними: запуск команд в каждом пакете, импорт репозиториев git, управление версиями проектов с различными стратегиями, публикация, ведение журналов изменений для каждого проекта, связывание пакетов между собой, подъем зависимостей... Многое. вариантов, в которые мы вскоре влюбились.

Итак, это был наш путь к поиску многообещающего решения: перейти к монорепозиторию и открыть исходный код с Компонентами X интерфейса Empathy. Внимание, спойлер, это сработало! Но не раньше, чем столкнуться с новыми проблемами на пути к нашей цели. Оставайтесь с нами для части 2: Пункт назначения.