От переводчика: Привет, Хабр! Да, это очередная статья о преимуществах и недостатках монорепозиториев. Собирался написать свою статью о том, как мы используем монорепозиторий, как мы переходили с maven на bazel и что из этого получилось. Но пока собирался с мыслями, вышла отличная статья от разработчика из Lyft, которую я и решил для вас перевести. Обещаю опубликовать свои дополнения к статье, а также опыт с bazel в виде продолжения.
Мы в Новом 2019 году, и я настроен на еще одну дискуссию о преимуществах (или отсутствии таковых) в хранении всего исходного кода организации в «Монорепозитории». Для тех из вас, кто не знаком с этим подходом, идея состоит в том, чтобы хранить весь исходный код в едином репозитории системы контроля версий. Альтернатива, конечно, заключается в том, чтобы хранить исходный код в нескольких независимых репозиториях, разделяя их обычно по границе сервисов/приложений/библиотек.
В данном посте я буду называть такой подход «полирепозиторий».
Некоторые из ИТ-гигантов используют монорепозитории, включая Google, Facebook, Twitter и других. Конечно, если такие уважаемые компании используют монорепозитории, то преимущества такого подхода должны быть огромны, и мы все должны делать так же, верно? Нет! Как сказано в заголовке статьи: «Пожалуйста, не используйте монорепозиторий!» Почему? Потому что, на большом масштабе монорепозиторий будет решать все те же самые проблемы, которые решает и полирепозиторий, но при этом провоцируя вас к сильной связанности вашего кода и требуя невероятных усилий по увеличению масштабируемости вашей системы контроля версий . Таким образом, в среднесрочной и долгосрочной перспективе монорепозиторий не дает никаких организационных преимуществ, в то время как оставляет лучших инженеров компании с пост-травматическим синдромом (проявляется в виде пускания слюней и бессвязного бормотания о производительности git).
Короткое отступление: что я подразумеваю под «на большом масштабе»? Нет однозначного ответа на этот вопрос, но т.к. я уверен, что вы спросите меня об этом, давайте скажем, что это около 100 разработчиков, пишущих код фул-тайм.
Теоретические преимущества монорепозитория и почему они не могут быть достигнуты без инструментов, которые используются для полирерозиториев (или ложны)
Теоретическое преимущество 1: Более легкая коллаборация и совместное владение кодом
Сторонники монорепозиториев утверждают, что кода весь код находится в одном репозитории, вероятность дупликации кода меньше, и возрастает вероятность того, что разные команды будут работать вместе над общей инфраструктурой.
Вот вам горькая правда о монорепозиториях даже среднего размера (и это будет звучать постоянно в данном разделе): очень быстро становится нецелесообразным для разработчика держать весь код репозитория у себя на рабочей станции или искать по всех кодовой базе с помощью утилит вроде grep. Поэтому любой монорепозиторий, который хочет масштабироваться должен предоставлять 2 вещи:
1) нечто вроде виртуальной файловой системы, которая позволяет хранить локально только часть кода. Это может быть достигнуто с помощью проприетарной файловой системы как Perforce, которая поддерживает такой режим нативно, с помощью внутреннего инструмента G3 от Google или GVFS от Microsoft.
2) сложные инструменты как сервис (as a service) для индексирования/поиска/просмотра исходного кода. Т.к. никто из разработчиков не собирается хранить весь исходный код на своей рабочей станции в состоянии, пригодном для поиска, становится критичным иметь возможность проводить такой поиск по всей кодовой базе.
Исходя из того, что разработчик в каждый момент времени будет иметь доступ только к небольшой порции исходного кода, есть ли хоть какая-то разница между загрузкой части монорепозитория или загрузкой нескольких независимых репозиториев? Разницы нет.
В контексте индексирования/поиска/просмотра и сходного кода такой гипотетический инструмент с легкостью может осуществлять поиск и по нескольким репозиториям и объединять результат. Фактически именно так работает поиск на GitHub, так же как и более сложные инструменты для поиска и индексирования, такие как Sourcegraph.
Таким образом, с точки зрения совместной работы над кодом на большом масштабе разработчики в любом случае вынуждены работать лишь с частью кодовой базы и использовать более высокоуровневые инструменты. Без разницы, хранится код в монорепозитории или в нескольких независимых репозиториях, проблема решается одним и тем же способом, и эффективность совестной работы над кодом зависит только от инженерной культуры, а не от способа хранения исходных кодов.
Теоретическое преимущество 2: Одна сборка / нет управления зависимостями
Следующий аргумент, приводимый обычно сторонниками монорепозиториев, заключается в том, что хранение всего кода в одном монорепозитории лишает вас необходимости управлять зависимостями, т.к. весь код собирается в одно и то же время. Это ложь! На большом масштабе просто нет возможности пересобирать весь исходный код и прогонять все автоматизированные тесты каждый раз, когда кто-то коммитит изменения в систему контроля версий (или что более важно и чаще встречается, на сервере CI когда создается новая ветка или пулл-реквест). Чтобы решить эту проблему все большие монорепозитории используют свою сложную систему сборки (например Bazel/Blaze от Google или Buck от Facebook), которая спроектирована так, что следит за изменениями и их зависимыми блоками и строит граф зависимостей исходного кода. Этот граф позволяет организовать эффективное кэширование результатов сборки и тестов, так что только изменения и их зависимости нуждаются в пересборке и тестировании.
Более того, т.к. собранный код должен в итоге быть задеплоен, и, как известно, все ПО не может быть задеплоено одномоментно, важно, чтобы все артефакты сборки контролировались, чтобы артефакты были передеплоены заново по необходимости. По сути, это означает что даже в мире монорепозиториев несколько версий кода могут существовать в один и тот же момент времени в природе, и должны тщательно отслеживаться и согласовываться.
Сторонники монорепозиториев также будут утверждать, что даже с учетом необходимости отслеживания сборок/зависимостей это все равно дает неоспоримое преимущество, т.к. одиночный коммит описывает полное состояние всего мира. Я бы сказал, что это преимущество довольно спорное, учитывая, что граф зависимостей уже существует, и это выглядит довольно тривиальной задачей включить идентификатор коммита для каждого независимого репозитория как часть этого графа, и фактически Bazel может легко работать с несколькими независимыми репозиториями как и одним монорепозиторием, абстрагируя нижележащий уровень от разработчика. Более того, легко можно реализовать такие средства автоматизированного рефакторинга, которые автоматически обновляют версии зависимых библиотек в нескольких независимых репозиториях сразу, нивелируя разницу между монорепозиторием и полирепозиторием в этой части (более подробно об этом дальше).
Конечный результат таков, что реалии сборки/деплоймента на большом масштабе по большей части одинаковы для монорепозиториев и полирепозиториев. Для инструментов нет разницы, не должно ее быть и для разработчиков, пишущих код.
Теоретическое преимущество 3: Рефакторинг кода — это простой атомарный коммит
Наконец, последнее достоинство, которое упоминают сторонники монорепозиториев, это факт, что один репозиторий делает рефакторинг кода более простым, благодаря простоте поиска, и идее, что один коммит может охватывать весь репозиторий. Это неверно по нескольким причинам:
1) как было описано выше, на большом масштабе разработчик не будет способен редактировать или осуществлять поиск по всей кодовой базе на своей локальной машине. Таким образом, идея о том, что каждый может легко склонировать себе весь репозиторий и просто выполнить grep/replace, не так легко осуществима на практике.
2) Даже если мы предположим, что с помощью сложной виртуальной файловой системы разработчик может клонировать и редактировать всю кодовую базу, то как часто это будет происходить? Я не говорю про исправление бага в имплементации общей библиотеки, т. к. такая ситуация одинаково обрабатывается и в случае монорепозитория, и в случае полирепозитория (предполагая похожую систему сборки/деплоя, как описана выше). Я говорю об изменении API библиотеки, за которым последуют множество ошибок компиляции в тех местах, где эта библиотека вызывается. В очень большой кодовой базе почти невозможно сделать изменение базового API, которое будет проревьюено всеми задействованными командами до того, как мердж конфликты заставят вас начать процесс сначала. У разработчика есть 2 реальные возможности: он может сдаться и придумать обходной путь для проблемы с API (на практике это случается чаще, чем нам всем этого бы хотелось), или он может задепрекейтить существующий API, написать новый API и затем вступить на тропу долгого и мучительного обновления всех вызовов старого API по всей кодовой базе. В любом случае это абсолютно такой же процесс, как и при полирепозитории.
3) В сервис-ориентированном мире приложения состоят из множества слабо связанных компонентов, которые взаимодействуют между собой, используя некоторый тип хорошо описанного API. Большие организации рано или поздно переходят на использование IDL (язык описания интерфейсов), таких как Thrift или Protobuf, которые позволяют делать типо-безопасные API и производить обратно-совместимые изменения. Как было описано в предыдущем разделе о сборке/деплое, код не может быть задеплоен одномоментно. Он может деплоиться на протяжении некоторого периода времени: часов, дней или даже месяцев. Поэтому разработчики обязаны думать об обратной совместимости своих изменений. Такова реальность современной разработки ПО, которую многие хотели бы игнорировать, но не могут. Поэтому, когда это касается сервисов (в противовес API библиотек) разработчики должны использовать один из двух подходов, описанных выше (не менять API или пройти через цикл депрекации) и это абсолютно одинаково и для монорепозитория, и для полирепозитория.
К слову о рефакторинге на большой кодовой базе, многие большие организации приходят к тому, чтобы разработать свои автоматизированные инструменты для автоматического рефакторинга, как например fastmod, недавно выпущенный Facebook. Как и всегда, этот инструмент мог бы с легкостью работать с одним репозиторием или же несколькими независимыми. У Lyft есть инструмент, который называется «refactorator», который делать именно это. Он работает как fastmod, но автоматизирует изменения по нескольким нашим репозиториям, включая создание пулл-реквестов, отслеживание статусов ревью и т.д.
Уникальные недостатки монорепозиториев
В предыдущей секции я перечислил все теоретические преимущества, которые предоставляет монорепозиторий, и отметил, что, чтобы воспользоваться ими, требуется создать невероятно сложный инструментарий, который не будет отличаться от инструментария для полирепозиториев. В этом разделе я упомяну 2 уникальных недостатка монорепозиториев.
Недостаток 1: сильная связанность и ПО с открытым исходным кодом
Организационно, монорепозиторий провоцирует к созданию сильно связанного и хрупкого ПО. Он дает разработчикам ощущение, что они легко могут исправить ошибки в абстракциях, хотя на самом деле они не могут из-за нестабильного процесса сборки/развертывания и человеческих/организационных/культурных факторов, возникающих при попытке внести изменения сразу по всей кодовой базе.
Структура кода в полирепозиториях олицетворяет четкие и прозрачные границы между командами/проектами/абстракциями/владельцами кода и заставляет разработчика тщательно продумать интерфейс взаимодействия. Это малозаметное, но очень важное преимущество: это заставляет разработчиков думать более масштабно и в более длительной перспективе. Более того, использование полирепозиториев не значит, что разработчики не могут выходить за пределы границ репозитория. Происходит это или нет, зависит только от разработческой культуры, а не от того, используется монорепозиторий или полирепозиторий.
Сильное связывание также имеет серьезные последствия относительно открытия своих исходных кодов. Если компания хочет создавать или потреблять ПО с открытым кодом, использование полирепозиториев просто необходимо. Искажения, которые происходят, когда компания пытается выложить свой проект о open source из своего монорепозитория (импорт/экспорт исходных кодов, публичный/приватый баг трекер, дополнительные слои для абстрагирования разницы в стандартных библиотеках и т.д.) не приводят к продуктивной коллаборации и построению сообщества, а также создают существенные накладные расходы.
Недостаток 2: масштабируемость системы контроля версий
Масштабирование системы контроля версий для сотен разработчиков, сотен миллионов строк кода, и огромного потока коммитов — монументальная задача. Монорепозиторий Twitter, созданный 5 лет назад (на основе git), был одним из самых бесполезных проектов, которые я наблюдал за свою карьеру. Выполнение простейшей команды как git status
занимало минуты. Если локальная копия репозитория слишком устаревала, обновление могло занять часы (в то время даже была практика отправлять жесткие диски с копией репозитория удаленным сотрудникам со свежей версией кода). Я вспоминаю об этом не для того, чтобы поиздеваться над разработчиками Twitter, а чтобы проиллюстрировать, как сложна эта проблема. Я могу сказать, что 5 лет спустя, производительность монорепозитория Twitter все еще далека от той, которую хотели бы видеть разработчики Туллинг команды, и это не потому, что они плохо старались.
Конечно, за эти 5 лет в этой области происходило некоторое развитие. Git VFS от Microsoft, которая используется для разработки Windows, привела к тому, что появилась настоящая виртуальная файловая система для git, которую я описывал выше как необходимое условие для масштабирования системы контроля версий (и с покупкой Microsoft Github похоже, что этот уровень масштабирования найдет свое применение в фичах, которые GiHub предлагает своим корпоративным клиентам). И конечно, Google и Facebook продолжают вкладывать огромные ресурсы в свои внутренние системы, чтобы они продолжали функционировать, хотя почти ничего из этого не доступно публично.
Так почему нужно вообще решать эти проблемы с масштабированием системы контроля версий, если как описано в предыдущем разделе, инструментарий требуется ровно тот же самый, что и для полирепозитория? Для этого нет разумной причины.
Заключение
Как часто бывает в разработке ПО, мы смотрим на наиболее успешные софтверные компании как на пример и пытаемся заимствовать их лучшие практики без понимания того, что же именно привело эти компании к успеху. Монорепозитории, по моему мнению, это характерный пример такого случая. Google, Facebook и Twitter инвестировали огромное количество ресурсов в свои системы хранения кода только для того, чтобы прийти к решению, которое по сути не отличается от того, которе требуется и для полирепозитория, но провоцирует сильное связывание и требует огромных инвестиций в масштабирование системы контроля версий<i/>.
По сути, на большом масштабе то как компания действует с совместной работой с кодом, коллаборацией, сильным связыванием и т.д. напрямую зависит от инженерной культуры и лидерства, и не имеет отношения к тому, используется ли монорепозиторий или полиропозиторий. Оба решения выглядят одинаково для разработчика. Так зачем использовать монорепозиторий? Пожалуйста, не надо!
Автор: kanu