От переводчика: Привет, Хабр! Да, это очередная статья о преимуществах и недостатках монорепозиториев. Собирался написать свою статью о том, как мы используем монорепозиторий, как мы переходили с 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 инвестировали огромное количество ресурсов в свои системы хранения кода только для того, чтобы прийти к решению, которое по сути не отличается от того, которе требуется и для полирепозитория, но провоцирует сильное связывание и требует огромных инвестиций в масштабирование системы контроля версий.
По сути, на большом масштабе то как компания действует с совместной работой с кодом, коллаборацией, сильным связыванием и т.д. напрямую зависит от инженерной культуры и лидерства, и не имеет отношения к тому, используется ли монорепозиторий или полиропозиторий. Оба решения выглядят одинаково для разработчика. Так зачем использовать монорепозиторий? Пожалуйста, не надо!
Комментарии (79)
alexesDev
07.01.2019 10:12+1Мне нравится делать
docker-compose up
в корне монорепы и получать целиком рабочий проект. И эту команду выполняет каждый разработчик каждый день и она редко ломается, показывает как проект работает целиком. Монорепа уменьшает страхи сделать ещё один подпроект в виде микросервиса (в большой компании вам нужно пройти все круги ада на одобрение такого и рассказать всей команде, что нужно запускать +1 проект в деве). Единый процесс CI/CD, который крайне просто менять, потому что он один, а не 10+ в разных проектах (infrastructure as code, сейчас код деплоя лежит рядом с кодом приложения).
На другой стороне примеры проектов с десятком репозиториев, которые поддерживаются уже второй-третий год. Чтобы запустить каждый нужно немного магии, немного магии * 10 — это крайне МНОГО магии. Часто граф зависимостей не описан.
В идеальной компании полирепозиторий — хорошо, но такий крайне мало. В стартапе на 3-5 человек 10 репозиториев не нужны.
PS. Мы держим каждый frontend на react отдельно, потому что чисто фронтендщики не хотят заморачиваться или это не позволяет делать windows без боли. А весь бек лежит в монорепе.vintage
07.01.2019 10:38Смотрите, как это сделано у нас: есть репозиторий с инфраструктурой, к нему через git-submodules подключены уже конкретные проекты. По умолчанию субмодули не выкачиваются. А сборщик умеет выкачивать субмодули по требованию. При этом разработчик может в любой момент переключить любой субмодуль на любую ветку. А когда надо сделать релиз — в инфраструктурной репе комитятся новые ревизии субмодулей.
alexesDev
07.01.2019 10:39Я боюсь submodules. Только печальные практики с ним. Скорее всего я не умею готовить. Может подскажите полезные статьи?
vintage
07.01.2019 10:59А что за печальные практики?
ГитБук же: git-scm.com/book/ru/v1/%D0%98%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D1%8B-Git-%D0%9F%D0%BE%D0%B4%D0%BC%D0%BE%D0%B4%D1%83%D0%BB%D0%B8alexesDev
07.01.2019 11:20Крайне сложно работать в ветках и не сломать себе мозг при обновлении нескольких submodules. Я сломал.
mapron
07.01.2019 11:56Переходили с subtree на submodule пару лет назад. Сперва было больно, а потом ничего, понравилось. Из плюсов — быстрая синхронизация кода (и предсказуемая)
из минусов — надо всегда держать в голове, какие коммиты ты должен сохранить и не ребейзить (обычно это все release/* ветки, но бывают исключения).
Мне они нравятся из-за какой-то своей ODR-ности, что ли.
В один репозиторий 10-15 сабмодулей подключается если что.
heleo
07.01.2019 13:19Если вы активно работаете с самими подмодулями, тогда возможно. Из моей практики единственное место которое влекло за собой «поломку» проекта, это вынос части проекта в отдельный подмодуль. В таком случае проблемы были только с выкачиванием\мерджем более старых веток у других людей, и то это разовая проблема.
kanu Автор
07.01.2019 11:27Проблема с сабмодули, что они ссылаются на конкретный коммит. Таким образом, держать такой репозиторий в актуальном состоянии довольно трудно. Можно, конечно, автоматизировать, чтобы всегда вытягивать, например, свежие релизные версии… Но не очень понятно, какую проблему это решает. Если хочется запускать полностью всю систему локально, то проще залить артифакты в registry/bintray/куда-то ещё и точно также запустить docker-compose
vintage
07.01.2019 11:37git-subtree (https://habr.com/post/429014/) по идее должен решать эту проблему, но я его пока не использовал, так что ничего сказать не могу.
Публиковать артефакты — это тормоза, мусорные коммиты с подъёмом версий зависимостей, конфликты в версиях при мёрже веток и разъезжание кода в репе с тем, что опубликовано. В общем, боль и страдания.
mapron
07.01.2019 11:58subtree сильно хуже, когда есть несколько слабо зависимых команд и надо периодически между ними синхронизировать код.
сабмодули позволяют грубо говоря всегда иметь точно ту же самую версию кода.
subtree в принципе может быть вариантом, если конкретный репозиторий всегда меняет ровно одна команда и никакая другая (так что конфликты исключены). Иначе — не в ту сторону решенный конфликт является багом замедленного действия (который ты не выявишь diff-ом, ибо дифф-то и должен быть в ситуации правок с нескольких источников).
Bakanych
09.01.2019 04:26Есть еще проблема с бранчами, когда в рамках одной фичи нужно отбранчить код в нескольких, связанных сабмодулями репозиториях. Дело в том, что ссылки на сабмодули в фича-бранче родительского репозитория будут указывать на коммиты в мастере. Тогда даже автоматизация через git submodule update не помогает. Есть волшебная пилюля для такого сценария?
kanu Автор
09.01.2019 04:28А как вы собираетесь деплоить эти связанные изменения?
Я бы сказал, что если у вас есть сильная связь между репозитоиями, вы неправильно определили границы.
Если все же границы определены верно, то не нужно писать этот код вместе — добавьте изменения в первый репозиторий, задеплойте его, потом добавьте во второй — задеплойте его.
vintage
09.01.2019 07:07В гите нет веток. Есть только ревизии и указатели на ревизии. Субмодуль может указывать на любую ревизию. Даже если после неё нет ревизии, на которую указывает мастер. А
git submodule update
не смотря на название просто выкачивает эту самую ревизию в рабочую копию. У гита всё плохо с командами, да.
diseaz
07.01.2019 23:59+1А чем это отличается от обычного монорепа? Например, тот же svn уже стопицот лет умеет partial checkout, и каждый подкаталог, и даже файл можно выкачать нужной ревизии. Только, в отличие от git'а, выкачивается не вся история, а только нужная ревизия.
kanu Автор
07.01.2019 11:18+1Это правда, что в проекте из 2-3 человек не нужно несколько репозиториев. Это просто добавить слишком много накладных расходов. Так что это вполне естественно начинать проект с монорепозитория. Проблемы начинаются тогда, когда у вас 200+ разработчиков, 500+ комитетов в день и билд занимает 40 минут.
Главное не пропустить тот момент, когда код будет настолько связан, что разделить его уже не получитсяc0f04
08.01.2019 13:59Это правда, что в проекте из 2-3 человек не нужно несколько репозиториев. Это просто добавить слишком много накладных расходов.
Это зависит не от количества людей, а от сложности проекта.
Одно из преимуществ, которое мне нравится — подмодули заставляют тебя так декомпозировать код, что ты не можешь сделать проекты A и B взаимозависимыми (если всё правильно организовано). А если случайно так получилось, — это требуется исправить, а результат гарантированно можно проверить, просто собрав эти два подмодуля по-отдельности.
Ещё одно преимещуство — это пакетирование. CI/CD автоматически собирает каждый проект, отдельно его тестирует и пакетирует, например, в свой deb- или rpm-пакет под заданные платформы. Но тут сложнее, т. к. требуется автоматизировать зависимую сборку проектов. Зато не требуются такие надстройки как над монорепозиторием.
Ещё одно преимущество — можно делать глобальный рефакторинг не по всему проекту сразу, а частями через подмодули, таким образом распределив задачу по разным людям. При этом нигде ничего не ломается.kanu Автор
09.01.2019 04:38> Это зависит не от количества людей, а от сложности проекта.
И от количества людей тоже (мало людей не смогут нагенерировать поток коммитов и создать проблемный размер для репозитория), так что если 2-3 человека, то основные проблемы монорепозитория не очень проявляют себя.
> Одно из преимуществ, которое мне нравится — подмодули заставляют тебя так декомпозировать код,
Это указано в статье, тоже считаю это преимуществом
> Ещё одно преимещуство — это пакетирование.
вот тут не вижу взаимосвязи с моно- или полирепозиторием, мы в своем монорепозитории собираем отдельные пакеты для каждого микросервиса под разные платформы, работало и на maven, работает и на bazel
> Ещё одно преимущество — можно делать глобальный рефакторинг не по всему проекту сразу, а частями через подмодули
ну прямо скажем, в монорепозитории тоже так можно. Даже более того, (об этом сказано в статье) другого варианта у вас нет даже в монорепозитории из-за неатомарности деплоя.c0f04
09.01.2019 07:45вот тут не вижу взаимосвязи с моно- или полирепозиторием, мы в своем монорепозитории собираем отдельные пакеты для каждого микросервиса под разные платформы, работало и на maven, работает и на bazel
К сожалению Java хоть и знаю, но в продакшене пока ещё не использую (равно как и монорепозитории, которые с самого начала решил не делать), поэтому мне сложно в этом контексте что-либо оценивать.
Идея, которую я пытался донести в том, что каждый подпроект описывается отдельно и подключается там, где это требуется как подмодуль. При этом сам подпроект обрабатывается Gitlab (CI/CD) как самостоятельный проект, который не знает о существовании чего-либо ещё. При этом для каждого проекта создаётся шаблонный yaml-файл для CI/CD в котором по сути меняется лишь название проекта. А сама сборка осуществляется с помощью ещё одного универсального подмодуля, который подключается к каждому проекту. А уже этот подмодуль берёт из проекта файл конфигурации под каждую платформу для текущего проекта.
В результате любой разработчик может быстро склонировать всего лишь один репозиторий (с рекурсивными зависимостями), перейти на любой коммит и собрать пакет, например, для тестов. Или тесты прогнать, дописав новый. Система сборки в этом случае будет самодостаточной.
kanu Автор
07.01.2019 11:23+1Если у вас проект целиком запускается на одной машине, это маленький проект ;)
В моей практике была система, минимальная конфигурация которой содержала 20 виртуалок. Никто даже не пытался запускать ее на локальной машине, т.к. там было несколько инстансов Оракла с кросс-локейшн репликациейslonopotamus
07.01.2019 20:54+3А с меньшим количеством инстансов оракла эта система не способна работать?
kanu Автор
08.01.2019 02:41Не поверите, но нет :)
Это не ноды одного кластера, а независимые подсистемы
В подробности вдаваться не буду, просто поверьте на слово ;)
justboris
07.01.2019 11:39+1А разве суть микросервисов не в том, чтобы их можно было запускать и разрабатывать отдельно?
kanu Автор
07.01.2019 13:44Частично да, но иногда, к сожалению, нужно протестировать интеграционные сценарии. А что того хуже, иногда их нужно ещё и продебажить. Так что хорошо иметь возможность запустить хотя бы часть системы (несколько сервисов, в зависимости от воспроизводимого сценария) локально из IDE в дебаг режиме
justboris
07.01.2019 21:59+3Я в основном отвечал на «docker-compose up и все запущено». Как-то это не микросервисно. Поднимать несколько сервисов можно и иногда нужно, но это не должно быть обязательным требованием.
RouR
07.01.2019 11:52Автор неправ. Аргументы за — какие-то высосанные из пальца. Из серии «сам придумал — сам с собой поспорил».
Потому что, на большом масштабе монорепозиторий будет решать все те же самые проблемы, которые решает и полирепозиторий, но при этом провоцируя вас к сильной связанности вашего кода и требуя невероятных усилий по увеличению масштабируемости вашей системы контроля версий. Таким образом, в среднесрочной и долгосрочной перспективе монорепозиторий не дает никаких организационных преимуществ, в то время как оставляет лучших инженеров компании с пост-травматическим синдромом (проявляется в виде пускания слюней и бессвязного бормотания о производительности git).
что я подразумеваю под «на большом масштабе»? Нет однозначного ответа на этот вопрос, но т.к. я уверен, что вы спросите меня об этом, давайте скажем, что это около 100 разработчиков, пишущих код фул-тайм.
Исходя из того, что разработчик в каждый момент времени будет иметь доступ только к небольшой порции исходного кода, есть ли хоть какая-то разница между загрузкой части монорепозитория или загрузкой нескольких независимых репозиториев? Разницы нет.
Следующий аргумент, приводимый обычно сторонниками монорепозиториев, заключается в том, что хранение всего кода в одном монорепозитории лишает вас необходимости управлять зависимостями, т.к. весь код собирается в одно и то же время. Это ложь! На большом масштабе просто нет возможности пересобирать весь исходный код и прогонять все автоматизированные тесты каждый раз, когда кто-то коммитит изменения в систему контроля версий
На больших проектах монорепозиторий действительно не нужен. Но на малых проектах, или в малых командах (и особенно если всего 1 разработчик), или вообще в начале проекта — монорепозиторий очень удобен. Если проект-прототип получит развитие, то уже далее, в какой-то момент монорепозиторий надо будет разделить.
Проведу некую аналогию — было множество споров, что лучше — монолит или микросервисы. У каждой из архитектур свои + и -. Я считаю что существует промежуточный вариант — микросервисный монолит, который будет жить в монорепозитории. Он хорош на старте проекта, и если проект «взлетит» и получит развитие, то будет рефакторинг в честные микросервисы и разные репозитории.kanu Автор
07.01.2019 12:23-1Ну в статье и говорится не об одном разработчике, а о проекте с 100+ разработчиками.
Могу сказать по своему опыту, про темы совсем невыдуманные. Особенно в части того, что некоторые компании бездумно копируют практики.
В данный момент у нас 200+ инженеров работают над монорепозиторием. Более 100 коммитов в день, билд на CI занимает около 40 минут. Клонирование репозитория с Гитхаба (учитывая поганый Австралийский интернет) занимает больше 10 минут.
Я уж не говорю про индекс в ИдееRouR
07.01.2019 14:06-3Так основной посыл статьи — не используйте монорепо вообще никогда. Это тоже бездумная рекомендация. Мой комментарий про ограничения применимости.
zagayevskiy
07.01.2019 14:38+8Cлушайте, если у вас один разработчик, то у вас не «монорепозиторий», а «репозиторчик». В том смысле, что там мало кода, и вообще пофиг на это. Посыл автора про большие команды. И, в основном, сторонники монорепы эти аргументы и приводят.
Azy
07.01.2019 12:58+1У нас тоже монорепа и two-pizza team. Сначала было разделение на 2 репы — бек на джанго и фронт на реакте. Но это было банально неудобно. В итоге слили в один и довольны.
Про тесты — у нас в несколько потоков:
— бек интеграционные
— бек юнит
— фронт тесты
— фронт тест билда
Добавили маленькую проверку изменений в папке backend и frontend в CI чтобы зря не гонять тесты.
Все довольны :)
anjensan
07.01.2019 13:32+14Надо подчеркнуть, что в статье под «монорепозиторием» понимается не просто «несколько кусков проекта в одной репе» (скажем фронт и бек одного проекта), и даже не «несколько микросервисов в одной репе», а скорее подход trunk-based-development (аля гугл), где:
— одна репа на несколько *продуктов*, с разными командами, циклами релизов и т.п;
— в этой же репе лежат все «общие» компоненты, внутренние фреймворки, сервисы;
— но отсутсвие версий у библиотек/компонентов; по сути версия одна — номер коммита;
— нет (ну или почти нет) внешних зависимостей — вместо этого зависимости включаются в саму репу (подход аля Go).
Поэтому, имхо, немного неуместно говорить «а вот в нашем проекте на 3 человека и 5 микросервисом монорепа и все зашибись». Речь все же идет про разные «монорепы».
olegthelilfix
07.01.2019 13:48Как по мне, отвечая на вопрос полирепозиторий или монорепозиторий — нужно ориентироваться на условия в которых будет выполняется разработка, а также личные предпочтения участников разработки. Остальное же, по моим мироощущениям, не так и важно. Если команде проекта удобно, то какая разница какой подход к организации репозитория они используют?!
kanu Автор
07.01.2019 13:51По сути автор говорит именно об этом, но суть в том, что подход с монорепозиторием на определенном масштабе начинает добавлять проблем с производительностью VCS и не только, которые нужно как-то решать. Гугл может себе позволить иметь сотни разработчиков, работающих годами над этими проблемами. Может ли ваша компания?
vintage
07.01.2019 14:31Примечательно, что старый добрый SVN этих проблем не имеет. Выкачал любую часть репозитория и работай с ней как с отдельным репозиторием. Разве что рано или поздно появляются всякие бранчи в транках и транки в бранчах.
kanu Автор
07.01.2019 14:45Ну говорят, что Яндекс его и использует для своего монорепо в Поиске
TheKnight
07.01.2019 16:41Есть статья про CI в Яндексе, там в комментах обсуждались и VCS.
В статье этого нет, для монорепозитория у нас используется несколько систем контроля версий, которые работают над одним набором данных. На текущий момент это svn, hg и некоторые внутренние разработки.
vsb
07.01.2019 14:52Могу только отметить свой успешный опыт по внедрению монорепозитория. Раньше даже не думал, что так можно, всегда делал кучу репозиториев по дефолту, для меня монорепозиторий оказался просто идеалом.
voidnugget
07.01.2019 18:20Оторвано от контеста.
В случае с тем же JS'ом — есть lerna, хочешь монорепку — делай моно, хочешь полирепку — делай поли через git submodules. Погоды особо не делает.
В разных языках — разные пакетные менеджеры, и очень мало жизнеспособного инструментария для монорепок.
springimport
07.01.2019 20:13В php-проектах использую composer. Очень удобно обозначить зависимости и сохранить lock-файл, на проде просто установка из lock-файла. На случай если очень важно не потерять пакеты, можно сделать свой собственный «proxy» в виде satis. Не представляю что должно произойти чтобы пришлось отказаться от composer и хранить либы в репозитории.
Еще есть вопрос по поводу универсальных либ которые удобно один раз написать и потом только использовать. Проблема в том что отладив либу для проекта, для другого почти наверняка придется сделать изменения которые могут сломать первый. Писать либу так чтобы было супер универсально далеко не всегда получается. Возможно в этом случае и полезны монорепозитории.
justboris
07.01.2019 22:05+1Забавно, а мы сейчас в команде раздумываем над объединением наших пакетов в одно монорепо. Аргументы у нас такие:
- Пакеты собираются, тестируются и релизятся только вместе
- Вся команда работает над всеми пакетами в равной мере, в конечном итоге все равно придется склонировать все репозитории локально
- Протаскивать изменения сквозь цепочку зависимостей тяжко, а это часто бывает нужно.
Размер команды – 10 человек. Если было бы на порядок больше, то там уже пришлось бы как-то разделять зоны отвественности и разъезжаться по отдельным репозиториям.
miga
07.01.2019 22:27-2Половину аргументов можно сходу отметать по той простой причине, что гит — это не про монорепы.
kanu Автор
07.01.2019 23:40Что вы имеете в виду? Все исходники Windows хранятся в Гит, также как и ядро Linux.
miga
07.01.2019 23:45Ну так-то исходники винды лежат не совсем в гите, а линукс — это очень маленький проект по сравнению с кодовой базой фейсбука
justboris
07.01.2019 23:55Тут возникает резонный вопрос: а нужно ли создавать себе проблемы с большими репозиториями, чтобы потом их героически решать? Не проще будет распилить большой проект на отдельные части?
miga
07.01.2019 23:56Очевидно, до тех пор, пока вы не выросли до размеров гугла, фейсбука или даже яндекса — не нужно :)
kanu Автор
08.01.2019 02:33Git Virtual File System en.wikipedia.org/wiki/Git_Virtual_File_System
Это к вопросу о том, где хранятся исходники Windows
roman_kashitsyn
08.01.2019 02:36Когда в команде несколько десятков разработчиков, начинается гонка за коммит в head. В линуксе это работает благодаря организационной структуре: там изменения льются в апстрим по дереву доверия, и в каждом узле небольшое код-во разработчиков.
kanu Автор
08.01.2019 02:39Для решения такой проблемы монорепощиторщики придумали свой тулинг :) называется мерджбот
Например prow от Goggle bentheelder.io/posts/prow или сервис mergify.io
roman_kashitsyn
08.01.2019 02:30Я работал с большими кодовыми базами с использованием обоих подходов.
Монорепы — это на порядок более удобный и продуктивный подход (полирепы вспоминаю как страшный сон), если у компании есть ресурсы для настройки (а возможно, и написания с нуля) инструментов для работы с ними. Facebook и Google выкладывают много полезного для этого кода в открытый доступ (Kythe, Bazel, Hg-experimental), но инвестировать в инфраструктуру придётся много.
Не выйдет просто взять и начать сваливать весь код в одну кучу.
kanu Автор
08.01.2019 02:36Проблема со всеми этими проектами описана в статье.
Все они обладают очень сильной привязкой к специфике кодовой базы разработчика. Их просто невероятно сложно начать использовать. Говорю это, как человек, переведший монорепозиторий с мавена на bazelroman_kashitsyn
08.01.2019 02:42Говорю следующее как разработчик, ежедневно использующий монорепу с миллиардами строк кода. Начать работать с этой монорепой было очень просто, реализовывать кросс-модульные фичи — это просто сказка, навигация по всему коду напрямую из codesearch — это божественно. Видимо, в вашей монорепе нет правильных инструментов.
kanu Автор
08.01.2019 02:55Именно это и говорится в статье — есть ровно 20+- компаний в мире, которые умеют в монорепо. Это гугл, фейсбук, твиттер, яндекс, может быть, кто-то еще.
Остальным не надо даже пытаться, т.к. правильных инструментов у вас нет, как и ресурсов на их создание.
TheKnight
08.01.2019 04:20А в какой компании вы нынче монорепу используете?
И какие основные языки/технологии в вашей деятельности?Gugic
08.01.2019 04:59-1А вы в профиль загляните.
TheKnight
08.01.2019 17:38+1С компанией лоханулся, признаю.
Списка языков и технологий там нет.
Есть определенная разница, использовать монорепу для бекэнда на плюсах, фронтенда на TypeScript или мобильных приложения для iOS и Android.
Если работает первый случай — это круто, но не интересно. Если второй и третий — это уже интересней.Gugic
08.01.2019 20:31Это же гугл. Там в общем и целом один монореп для всего и сразу. C++, Java, Python, Go (который родился в монорепе и поэтому там такой странный для внешнего пользователя менеджмент зависимостей), джаваскрипт, конечно. Под веб, айос, андроид, десктоп и все остальное что может прийти в голову.
Тайпскрипт тоже, да.TheKnight
09.01.2019 09:49Ну и как вам для фронтэнда монорепа? Все ли чудесно и прекрасно?
Gugic
09.01.2019 11:12Нет, не все прекрасно. Но в целом работает.
Пока пользуешься рекомендованным тулингом и зависишь от других внутренних проектов — все в общем даже наверное хорошо.
Интересное начинается когда добираешься до 3p кода (например популярные фреймверки, d3 и прочие antlr'ы).roman_kashitsyn
09.01.2019 14:30Интересное начинается когда добираешься до 3p кода
Ну вообще говоря, c 3p не так уж всё и плохо. Как всегда с монорепами, это — проблема тулинга. Написаны инструменты для импорта сторонних зависимостей из разных источников и синхронизации с открытыми репозиториями. Очень многие популярные вещи уже в third_party.
Зато получаем
- codesearch по всем зависимостям
- очередной leftpad вам ничего не сломает
- вместо двух тысяч копий leftpad у вас будет ровно одна
- версии всех зависимостей всегда консистентные (особенно актуально в каком-нибудь Haskell)
speshuric
10.01.2019 01:43И плюс еще если надо слить/разъединить команды, то на структуре кода это не отражается.
Gugic
08.01.2019 05:04Однако адаптация хороших внешних инструментов под этот чудесный монолит — та еще задача, зачастую просто решаемая в лоб написанием своих собстенных «аналогичных» инструментов.
Я не так давно переводил один фронтенд-проект на 40+ тысяч строк с git/npm/gulp/webpack на эту чудесную монорепу, проклял все. Это еще повезло что он был наполовину на совсместимом наречии написан.roman_kashitsyn
08.01.2019 10:4740+ тысяч строк с git/npm/gulp/webpack на эту чудесную монорепу
На какую чудесную монорепу? В чём заключался перевод? Просто переписали билд на bazel/pants/buck?
Перевод проекта с системы сборки X на систему сборки Y, ? X, Y — это всегда много скучной, изматывающей, неблагодарной работы.
Я переводил ~500.000 строк кода и двумя десятками приложений на Java с кастомной ANT-сборки на Gradle, утомился. При чём тут монорепы?
Gugic
08.01.2019 10:59+2На ТУ САМУЮ чудесну монорепу про которую вы пишете выше.
Билд, это не самое интересное, это, как верно замечено, просто много скучной и изматывающей работы.
Самое интересное — это 3p зависимости, которые раньше подтягивались через стандартный для внешнего мира пакетный менеджер, а теперь должны быть частью собственно монорепы. В которой очевидное правило одной версии (очень далеко не всегда последней), определенные сложности с оформлением и добавлением пакетов из внешнего мира, очень плохая концептуальная совместимость с миром современного фронтенда в целом и еще разные сугубо внутренние нюансы.
Ну хотя это ведь тоже часть билда, если разбираться.
anjensan
08.01.2019 13:37реализовывать кросс-модульные фичи — это просто сказка
Знаем-знаем. А потом без пол-литры не разберешь, а где же у этих систем границы. Где собственно зоны отвественности и все такое… Зла нет :)roman_kashitsyn
08.01.2019 13:53А потом без пол-литры не разберешь, а где же у этих систем границы. Где собственно зоны отвественности и все такое…
Это самый бредовый аргумент из всей статьи. У нас у каждой большой системы есть свой каталог с OWNERS файлами, в которых написано, кто владеет кодом. В нормальном воркфлоу без аппрува этой команды ничего изменить в их коде не получится. Всегда понятно, где границы, и кто за что отвечает. Сервисы взаимодействуют друг с другом через посылку сообщений поверх gRPC. Сложно представить себе более ясные границы.
Полирепы вас от плохого кода точно не спасут.
anjensan
08.01.2019 14:19Ну OWNERS конечно хорошо, но от проблемы не спасает. Там может много кого, и аппрувят люди, которые далеко не всегда имеют досататочный контекст :)
И взаимодействие через gRPC тоже не панацея. Оно может быть как в пределах одной системы (разбитой на куски), так и разных. Я же не про разбивку на бинари говорю.
Вполне возможно что ваш опыт более позитивен в этом плане, но лично мой говорит, что сильная связанность (про которую упоминается в этой статье) таки реальная проблема. Ведь разные не только проекты, но и стеки технологий совсем иные.roman_kashitsyn
08.01.2019 17:14+1Связность зависит от культуры разработки, а не от структуры репозитория. Лично я не вижу никаких подтверждений этого аргумента автора статьи.
В полирепе так же просто сделать неявную зависимость между модулями: предположения о формате данных, структуре хранилища и т.п. Я точно также могу утверждать, что полирепы поощрают вас, к примеру, хранить фронт-енд и бэк-енд в разных репозиториях, и протокол между ними не генерить из общего IDL по соседству, а писать руками дважды (счастливой отладки при апдейте схемы).
И вот такое я как раз видел сплошь и рядом.anjensan
08.01.2019 17:24Речь идет лишь о «стимулирует» или «подталкивает», а не о жестких следствиях.
Я точно также могу утверждать, что полирепы поощрают вас, к примеру, хранить фронт-енд и бэк-енд в разных репозиториях, и протокол между ними не генерить из общего IDL по соседству, а писать руками дважды (счастливой отладки при апдейте схемы).
Вот не совсем. Скорее, если культура разработки высокая, то подталкивает нормально этот протокол версионировать. Что позволяет избежать отладки при апдейтах системы :)
be_a_dancer
08.01.2019 09:28Немного персонального опыта.
Работал и с монорепами в команде 8+ человек, работал и с полирепой в средней по размеру команде.
Имхо. Накладные расходы увеличиваются серьезно в полирепе. У нас есть makefile, который умеет инициализировать репозитории, подбрасывать симлинки на реальные инстансы кода вместо сабмодулей и много чего еще для работы. Кроме того это требует наличия метарепозитория (инфраструктурной репы), который умеет собирать все и деплоить в прод по кнопке. Все это требует вдумчивой проработки и внимания на поддержку.
Мое мнение сложилось примерно тем же, что и с микросервисами: это круто и хорошо, об этом надо задумываться, но только тогда, когда проект реально имеет несколько крупных команд поддержки/разработки и проблемы монорепы начинают мешать
kanu Автор
09.01.2019 04:41Проблема в том, что когда проблемы начинают себя проявлять, разделить монорепозиторий уже скорее всего не получится по техническим, но прежде всего по идеологическим причинам (это будет выглядеть революцией по сравнению с накручиванием нового тулинга вокруг старого подхода). Именно с этой проблемой я столкнулся в своей текущей компании — проблемы видят все, но считают, что бороться надо с помощью инструментов
be_a_dancer
09.01.2019 08:25Одна из задач разработчика — доказать, что такая революция нужна. Особенно, в случае, когда тут команда разработчиков.
А вообще, не могу не согласиться, что чаще происходит именно так.
P. S. Накладные расходы в начале примерно такие же. Особенно, если в команде есть джуны.
hexploy
08.01.2019 10:08Видел я один монореп на 2000+ разработчиков. Разумеется со своими внутренними доработками, странной документацией, занимательными багами и ограничениями при работе из под Windows (большинство сотрудников с маками).
Я дня два матерился прежде чем смог выкачать и собрать проект.
В общем, теперь у меня есть новый вопрос к работодателям на интервью.
kuftachev
08.01.2019 10:24Только вчера вечером задался вопросом, а Хабр вчера утром на него даёт ответ )))
speshuric
10.01.2019 01:35Спасибо за перевод, но кажется, что исходное утверждение излишне категорично (или автор сознательно "вбросил"). Просто каждой задаче своё решение.
Монорепозиторий крупных продуктов (100-1000 разработчиков) вполне распространенное явление: ядра Windows и Linux, .NET Core (прадва, их два 2: CLR и FX, но деление относительно естественное), IntelliJ IDEA Community Edition. Chromium сидит на двух стульях (часть компонент типа V8 отдельно, но и оставшийся булыжник весьма большой). Так посмотреть, то из крупных продуктов, тех кто на git сидит (то есть JDK и Firefox на hg выпадают) — большинство сидят в монорепозиториях.
А почему?
- Продукт единый и нет явных причин где-то строить границу (кроме объёма репы). Ну или граница есть, и это распадается на разные продукты.
- Это позволяет перекраивать верхнеуровневую структуру продукта ("одним коммитом"). Причем важно, что не только структуру кода, но и структуру команд!
- Это позволяет вносить связанные изменения одним коммитом. Да, все стремятся делать коммиты как можно компактнее, но они же должны быть еще и "атомарными". Если одно логическое изменение приходится делать в нескольких репозиториях, то обеспечение атомарности потом нужно дополнительно обеспечить (либо в "надрепозитории", либо в сборке и упровлении зависимостями)
Да, за монорепозиторий приходится платить: производительность, строгие правила, сложная структура. Но альтернативы для целостных продуктов могут оказаться существенно дороже.
НО! Если есть возможность раскидать продукт на слабозависимые репозитории, то, конечно, это надо сделать. В корпоративной среде пример — микросервисы, в OSS — Gnome и KDE. В этих случаях обычно зависимости и связи удобнее разрешать в сборке и поставке (не в subrtee/submodule)
ИМХО про subrtee/submoduleЯ считаю, что механизм subrtee/submodule придаёт совершенно ненужную хрупкость репозиторию. Лучше задачу управления зависимостями отдавать отдельным сервисам: сборке, CI, CD.
Dreyk
очень интересно наблюдать как хайпили по микросервисам против монолита, а сейчас за монорепо против сервис-реп
shurup
О да. Мы недавно публиковали достаточно детальное выступление по «анти-хайпу» микросервисов :-)