Слово монорепо - это комбинация между «моно», как в греческом слове mónos (в переводе сам по себе), и сокращением слова репозиторий. Простая концепция, если ее понимать дословно: одно одинокое хранилище. Это область разработки программного обеспечения, поэтому мы имеем в виду исходный код, мультимедийные ресурсы, двоичные файлы и т. Д. Но это определение - лишь верхушка айсберга, поскольку на практике монорепозиторий - это гораздо больше.

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

Сам термин, как видно на диаграмме выше, выглядит таким же новым, как и в 2017 году. Однако было бы ошибкой думать, что раньше никто не хранил весь свой код в одном месте. Фактически, во время моей первой работы в 2009 году компания, в которой я работал, хранила каждый проект в одном репозитории SVN, по одному каталогу на проект. В самом деле, вы вполне можете проследить эту практику еще дальше. Но как же тогда объяснить недавнюю взрывную популярность?

Реальность такова, что хранение кода в одном месте - не главное преимущество. В последние годы крупные технологические компании - Google, Facebook или Dropbox - демонстрировали массовую совместную работу в одном репозитории. Организации из десятков тысяч инженеров, работающих в одном репозитории, - это потрясающее зрелище. И сложная инженерная задача. На самом деле настолько сложно, что эти компании вкладывают много денег в инструменты и системы, которые позволяют разработчикам работать продуктивно. Эти системы, в свою очередь, решили проблемы, о которых вы, возможно, даже не подозревали. Это то, что очаровывает людей во время технических переговоров. Это то, что движет поисковыми запросами с 2017 года.

Я выделил несколько основных функций, которые предлагает проверенный Google или Facebook монорепозиторий. Это, конечно, не исчерпывающий список, но это отличная отправная точка. Обсуждая каждый из этих пунктов, я принимал во внимание, как выглядит жизнь без них и что именно они решают. Конечно, в нашей сфере работы все идет на компромисс, нет ничего бесплатного. Для каждого профессионала, которого я перечисляю, кто-то найдет варианты использования, которые прямо мне противоречат, но меня это устраивает.

Весь ваш код, независимо от языка, находится в одном репозитории

Первое преимущество хранения всего в одном месте может быть не сразу очевидным, но для разработчика просто возможность свободно просматривать все имеет большое значение. Это помогает укрепить командный дух, а также является очень ценным и дешевым способом распространения информации. Вы когда-нибудь спрашивали себя, какие проекты находятся в разработке в вашей компании? Прошлое и настоящее? Любопытно, чем занимается определенная команда? Как они решили ту или иную инженерную задачу? Как они пишут юнит-тесты?

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

Есть компании, которые перешли от мульти-макета к монорепозиторию, следуя только этой функции из моего списка. Однако такую ​​структуру не следует путать с темой данной статьи. Вместо этого я бы определил его как совмещенное мультирепо. Да, все в одном месте, но остальные функции в этом списке гораздо интереснее.

Вы можете организовывать зависимости между модулями контролируемым и явным образом.

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

Чтобы одна команда зависела от кода другой, все должно проходить через систему хранения управления зависимостями. Примеры: npm, MavenCentral или PyPi. Ранее я сказал, что вы можете легко создать совместное мультирепо, просто храня все в одном месте. Такая система наблюдаема косвенно. Давайте посмотрим, почему это важно.

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

«Действительно, соотношение времени, затрачиваемого на чтение и на запись, намного превышает 10: 1. Мы постоянно читаем старый код в рамках усилий по написанию нового кода. … [Следовательно,] облегчая чтение, легче писать ».

- Роберт К. Мартин, Чистый код: руководство по созданию гибкого программного обеспечения

Однако чаще вы можете полагаться на более старую версию. Так что же теперь делать? Вы пытаетесь использовать VCS для чтения старого кода? Вы пытаетесь прочитать реальный артефакт вместо исходного кода? Что, если он минифицирован, как это обычно бывает с JavaScript?

Сравните это, например, с системой Google: поскольку зависимости кода являются прямыми, т.е. практически нигде нет версий, можно сказать, что система непосредственно наблюдаема. Код, на который вы смотрите, - это почти весь ваш мир. Я говорю в основном потому, что, конечно, всегда будут незначительные исключения из этого правила, такие как внешние зависимости, которые не позволят вам разместить себя. Но это не должно отвлекать от обсуждения.

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

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

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

Программные модули повторно используют общую инфраструктуру

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

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

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

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

Я знаю, что некоторые компании идут на все, чтобы минимизировать влияние этого. У них будут свои конфигурации в виде скаффолдинга (a la create-react-app или yeoman), и они будут использоваться для настройки новых репозиториев. Но, как мы видели в разделе перед этим, невозможно обеспечить, чтобы все использовали последнюю версию этих шаблонных зависимостей! Время, затрачиваемое на обновление каждого репозитория по отдельности, линейно увеличивается в больших базах кода. При достаточном масштабе практически все опубликованные версии внутреннего пакета будут зависеть одновременно!

Вот цитата, которая мне очень нравится, которая относится к этой загадке:

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

- Анна Кюри

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

Изменения всегда отражаются во всем репозитории

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

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

Но люди в Facebook публикуют последнюю версию React, и мы понимаем, что обновление до нее нетривиально. Чтобы быть более продуктивными, мы создали библиотеку многократно используемых компонентов, которая находится в виде отдельного модуля. От этого зависят все проекты. Эта новая версия React содержит множество критических изменений, которые влияют на нее. Какие у нас есть варианты обновления?

Как правило, именно здесь злоумышленники-монорепозиторы сбивают всю концепцию. Легко сказать, что мы загнали себя в угол и что структура мультирепо была бы лучшим выбором с учетом обстоятельств. Действительно, в последнем случае мы бы просто постепенно внедряли новую версию React в наши проекты один за другим, которому предшествовало бы обновление основной версии нашего модуля основных компонентов.

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

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

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

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

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

Для решения этой последней проблемы крупные компании обратились к codemods - программным преобразованиям исходного кода, которые могут выполняться в очень большом масштабе. Если вам интересно, существует множество руководств, но суть в том, что вы пишете код, который сначала обнаруживает определенные шаблоны в исходном коде, а затем применяет к нему определенные изменения. Чтобы продолжить наш пример React, вы можете написать codemod, который заменяет устаревший API на новый и даже при необходимости вносит изменения в логику. Действительно, именно так Facebook рекомендует вам перейти с одной версии своей библиотеки на другую. Вот как они это делают внутри компании. Ознакомьтесь с их примерами с открытым исходным кодом.

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

Недостатки

Старая поговорка «бесплатного обеда не бывает», безусловно, применима и здесь. Я говорил о многих плюсах, но есть некоторые минусы, о которых нужно подумать.

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

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

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

Наконец, давайте рассмотрим непрерывную интеграцию. Вы можете использовать такую ​​систему, как Jenkins, Travis или CircleCI, для управления тем, как ваш код тестируется и доставляется клиентам. Если у вас более одного репозитория, вы обычно настраиваете по одному конвейеру для каждого. Некоторые команды идут еще дальше и имеют по одному выделенному экземпляру CI для каждого проекта. Это гибкая система, которая может адаптироваться к потребностям каждой команды. Ваша группа по выставлению счетов может развертываться в производственной среде один раз в неделю, в то время как ваша веб-группа будет работать быстрее и выполнять развертывание несколько раз в день.

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

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

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

Аналогичным образом, системы CI с открытым исходным кодом намного мощнее, чем вы думаете. Jenkins, например, может масштабироваться до сотен рабочих мест, десятков сотрудников и с легкостью может обслуживать потребности большой команды. Может ли он масштабировать Google? Точно нет! Но есть ли у вас десятки тысяч инженеров, которые ежедневно работают над производством? Плато, на котором эти инструменты перестают работать, настолько велико, что не стоит думать, пока вы не приблизитесь к нему. И, скорее всего, вы узнаете, когда подойдете ближе.

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

В заключение

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