Руководство по монорепозиториям для внешнего кода
Опубликовано: 2022-03-11Монорепозитории — горячая тема для обсуждения. В последнее время было много статей о том, почему вы должны и не должны использовать этот тип архитектуры для своего проекта, но большинство из них так или иначе предвзяты. Эта серия статей представляет собой попытку собрать и объяснить как можно больше информации, чтобы понять, как и когда использовать монорепозитории.
Монорепозиторий — это архитектурная концепция, в названии которой заключен весь смысл. Вместо того, чтобы управлять несколькими репозиториями, вы храните все свои изолированные части кода в одном репозитории. Имейте в виду слово изолированный — оно означает, что монорепозиторий не имеет ничего общего с монолитными приложениями. Вы можете хранить множество логических приложений внутри одного репозитория; например, веб-сайт и его приложение для iOS.
Это понятие относительно старое и появилось около десяти лет назад. Google была одной из первых компаний, применивших этот подход к управлению своими кодовыми базами. Вы спросите, если она существует уже десятилетие, то почему она стала такой горячей темой только сейчас? В основном за последние 5-6 лет многое претерпело кардинальные изменения. ES6, препроцессоры SCSS, диспетчеры задач, npm и т. д. В настоящее время для поддержки небольшого приложения на основе React приходится иметь дело с сборщиками проектов, наборами тестов, сценариями CI/CD, конфигурациями Docker и черт знает чем еще. А теперь представьте, что вместо небольшого приложения вам нужно поддерживать огромную платформу, состоящую из множества функциональных областей. Если вы думаете об архитектуре, вам нужно сделать две основные вещи: разделить проблемы и избежать дублирования кода.
Чтобы это произошло, вы, вероятно, захотите изолировать большие функции в некоторых пакетах, а затем использовать их через единую точку входа в своем основном приложении. Но как вы управляете этими пакетами? Каждый пакет должен иметь свою собственную конфигурацию среды рабочего процесса, а это означает, что каждый раз, когда вы хотите создать новый пакет, вам придется настраивать новую среду, копировать все файлы конфигурации и так далее. Или, например, если вам нужно что-то изменить в вашей системе сборки, вам придется просмотреть каждое репо, сделать коммит, создать запрос на включение и ждать каждую сборку, что сильно замедляет работу. На этом этапе мы встречаемся с монорепозиториями.
Вместо множества репозиториев со своими конфигами у нас будет только один источник правды — монорепозиторий: один загрузчик набора тестов, один файл конфигурации Docker и одна конфигурация для Webpack. И у вас по-прежнему есть масштабируемость, возможность разделения задач, совместное использование кода с общими пакетами и множество других плюсов. Звучит красиво, правда? Что ж, это так. Но есть и некоторые недостатки. Давайте внимательно рассмотрим точные плюсы и минусы использования монорепозитория в дикой природе.
Преимущества монорепозитория:
- Одно место для хранения всех конфигов и тестов. Поскольку все находится внутри одного репозитория, вы можете один раз настроить CI/CD и упаковщик, а затем просто повторно использовать конфигурации для сборки всех пакетов, прежде чем публиковать их на удаленном сервере. То же самое касается модульных, e2e и интеграционных тестов — ваш CI сможет запускать все тесты без дополнительной настройки.
- Простой рефакторинг глобальных функций с помощью атомарных коммитов. Вместо того, чтобы делать запрос на вытягивание для каждого репо, выясняя, в каком порядке создавать ваши изменения, вам просто нужно сделать атомарный запрос на вытягивание, который будет содержать все коммиты, связанные с функцией, над которой вы работаете.
- Упрощенная публикация пакетов. Если вы планируете внедрить новую функцию в пакет, который зависит от другого пакета с общим кодом, вы можете сделать это с помощью одной команды. Это функция, которая требует некоторых дополнительных настроек, которые будут обсуждаться позже в части обзора инструментов этой статьи. В настоящее время существует богатый выбор инструментов, включая Lerna, Yarn Workspaces и Bazel.
- Более простое управление зависимостями. Только один package.json . Нет необходимости переустанавливать зависимости в каждом репо всякий раз, когда вы хотите обновить свои зависимости.
- Повторно используйте код с общими пакетами, сохраняя при этом их изоляцию. Monorepo позволяет повторно использовать пакеты из других пакетов, сохраняя их изолированными друг от друга. Вы можете использовать ссылку на удаленный пакет и использовать их через единую точку входа. Чтобы использовать локальную версию, вы можете использовать локальные символические ссылки. Эта функция может быть реализована с помощью скриптов bash или с помощью дополнительных инструментов, таких как Lerna или Yarn.
Недостатки монорепо:
- Нет возможности ограничить доступ только к некоторым частям приложения. К сожалению, вы не можете поделиться только частью своего монорепозитория — вам придется предоставить доступ ко всей кодовой базе, что может привести к некоторым проблемам с безопасностью.
Низкая производительность Git при работе над крупномасштабными проектами. Эта проблема начинает появляться только в огромных приложениях с более чем миллионом коммитов и сотнями разработчиков, выполняющих свою работу одновременно каждый день над одним и тем же репозиторием. Это становится особенно проблематичным, поскольку Git использует ориентированный ациклический граф (DAG) для представления истории проекта. При большом количестве коммитов любая команда, проходящая по графику, может стать медленнее по мере углубления истории. Производительность также снижается из-за количества ссылок (то есть ветвей или тегов, которые можно решить, удалив ссылки, которые вам больше не нужны) и количества отслеживаемых файлов (а также их веса, хотя проблема с тяжелыми файлами может быть решена с помощью Git LFS).
Примечание. В настоящее время Facebook пытается решить проблемы с масштабируемостью VCS, исправляя Mercurial, и, возможно, вскоре это не будет такой большой проблемой.
- Более высокое время сборки. Поскольку у вас будет много исходного кода в одном месте, вашему CI потребуется гораздо больше времени, чтобы запустить все, чтобы утвердить каждый PR.
Обзор инструмента
Набор инструментов для управления монорепозиториями постоянно растет, и в настоящее время очень легко запутаться во всем многообразии систем построения монорепозиториев. Вы всегда можете быть в курсе популярных решений, используя этот репозиторий. А пока давайте быстро взглянем на инструменты, которые в настоящее время активно используются с JavaScript:

- Bazel — это система сборки Google, ориентированная на монорепозитории. Подробнее о Базеле: awesome-bazel
- Yarn — это инструмент управления зависимостями JavaScript, который поддерживает монорепозитории через рабочие пространства.
- Lerna — это инструмент для управления проектами JavaScript с несколькими пакетами, построенный на Yarn.
Большинство инструментов используют действительно похожий подход, но есть некоторые нюансы.
Мы углубимся в рабочий процесс Lerna, а также в другие инструменты во второй части этой статьи, так как это довольно большая тема. А пока давайте просто посмотрим, что внутри:
Лерна
Этот инструмент действительно помогает при работе с семантическими версиями, настройке рабочего процесса сборки, отправке ваших пакетов и т. д. Основная идея Lerna заключается в том, что ваш проект имеет папку пакетов, которая содержит все ваши изолированные части кода. И помимо пакетов у вас есть главное приложение, которое, например, может жить в папке src. Почти все операции в Lerna работают по простому правилу — вы перебираете все свои пакеты и выполняете над ними какие-то действия, например, увеличиваете версию пакета, обновляете зависимости всех пакетов, собираете все пакеты и т. д.
С Lerna у вас есть два варианта использования ваших пакетов:
- Не нажимая их на удаленный (NPM)
- Отправка ваших пакетов на удаленный сервер
При использовании первого подхода вы можете использовать локальные ссылки для своих пакетов и в основном не заботитесь о символических ссылках для их разрешения.
Но если вы используете второй подход, вы вынуждены импортировать свои пакеты с удаленного компьютера. (например, import { something } from @yourcompanyname/packagename;
), что означает, что вы всегда будете получать удаленную версию своего пакета. Для локальной разработки вам нужно будет создать символические ссылки в корне вашей папки, чтобы упаковщик разрешал локальные пакеты вместо использования тех, которые находятся внутри вашего node_modules/
. Вот почему перед запуском Webpack или вашего любимого бандлера вам придется запустить lerna bootstrap
, который автоматически свяжет все пакеты.
Пряжа
Изначально Yarn — это менеджер зависимостей для пакетов NPM, который изначально не был создан для поддержки монорепозиториев. Но в версии 1.0 разработчики Yarn выпустили функцию под названием Workspaces . Во время выпуска он не был таким стабильным, но через некоторое время его можно было использовать для производственных проектов.
Рабочая область — это, по сути, пакет, который имеет свой собственный package.json и может иметь некоторые определенные правила сборки (например, отдельный tsconfig.json , если вы используете TypeScript в своих проектах). На самом деле вы можете каким-то образом обойтись без Yarn Workspaces с помощью bash и иметь точно такую же настройку, но этот инструмент помогает упростить процесс установки и обновления зависимостей для каждого пакета.
На первый взгляд, Yarn с его рабочими пространствами предоставляет следующие полезные функции:
- Единая папка
node_modules
в корне для всех пакетов. Например, если у вас естьpackages/package_a
иpackages/package_b
— со своимpackage.json
— все зависимости будут установлены только в корень. Это одно из различий между тем, как работают Yarn и Lerna. - Символическая ссылка на зависимость, позволяющая разрабатывать локальные пакеты.
- Один файл блокировки для всех зависимостей.
- Фокусированное обновление зависимостей в случае, если вы хотите переустановить зависимости только для одного пакета. Это можно сделать с помощью флага
-focus
. - Интеграция с Лерной. Вы можете легко заставить Yarn взять на себя всю установку/ссылку, а Lerna позаботится о публикации и контроле версий. На данный момент это самая популярная установка, поскольку она требует меньше усилий и с ней легко работать.
Полезные ссылки:
- Пряжа Рабочие пространства
- Как собрать проект монорепозитория TypeScript
Базель
Bazel — это инструмент сборки крупномасштабных приложений, который может работать с многоязычными зависимостями и поддерживает множество современных языков (Java, JS, Go, C++ и т. д.). В большинстве случаев использование Bazel для небольших и средних JS-приложений является излишним, но в больших масштабах оно может принести большую пользу благодаря своей производительности.
По своей сути Bazel похож на Make, Gradle, Maven и другие инструменты, которые позволяют выполнять сборку проекта на основе файла, содержащего описание правил сборки и зависимостей проекта. Этот же файл в Bazel называется BUILD и находится внутри рабочей области проекта Bazel. Файл BUILD использует свой Starlark, удобочитаемый язык сборки высокого уровня, который очень похож на Python.
Обычно вы не будете много иметь дело с BUILD , потому что есть много шаблонов, которые можно легко найти в Интернете, которые уже настроены и готовы к разработке. Всякий раз, когда вы хотите построить свой проект, Bazel в основном делает следующее:
- Загружает файлы BUILD , относящиеся к цели.
- Анализирует входные данные и их зависимости, применяет указанные правила построения и создает график действий.
- Выполняет действия сборки над входными данными, пока не будут получены окончательные выходные данные сборки.
Полезные ссылки:
- JavaScript и Bazel — документация по настройке проекта Bazel для JS с нуля.
- Правила JavaScript и TypeScript для Bazel — Boilerplate для JS.
Заключение
Монорепозитории — это всего лишь инструмент. Есть много аргументов о том, есть ли у него будущее или нет, но правда в том, что в некоторых случаях этот инструмент делает свою работу и справляется с ней эффективно. За последние несколько лет этот инструмент развился, стал гораздо более гибким, устранил множество проблем и удалил уровень сложности с точки зрения конфигурации.
Есть еще много проблем, которые нужно решить, например, низкая производительность Git, но, надеюсь, это будет решено в ближайшем будущем.
Если вы хотите научиться создавать надежный конвейер CI/CD для своего приложения, я рекомендую статью How to Build a Effective Initial Deployment Pipeline with GitLab CI .