что такое репозиторий проекта
Как организовать собственный репозиторий модулей Node.js с блэкджеком и версионностью
В ISPsystem на текущий момент три front-end команды разрабатывают три крупных проекта: ISPmanager для управления веб-серверами, VMmanager для работы с виртуализацией и BILLmanager для автоматизации бизнеса хостеров. Команды работают одновременно, в режиме сжатых сроков, поэтому без оптимизации не обойтись. Чтобы сэкономить время, мы применяем единые решения и выносим общие компоненты в отдельные проекты. Такие проекты имеют собственные репозитории, которые поддерживают участники всех команд. Об устройстве этих репозиториев, а также работе с ними и будет эта статья.
Как устроены репозитории общих проектов
Мы используем собственный сервер с GitLab для хранения удаленных репозиториев. Для нас было важно сохранить привычное рабочее окружение и иметь возможность работать с общими модулями в процессе их разработки. Поэтому мы отказались от публикации в приватных репозиториях npmjs.com. Благо модули Node.js можно устанавливать не только с NPM, но и из других источников, включая git-репозитории.
Мы пишем на TypeScript, который впоследствии компилируется в JavaScript для последующего использования. Но в наше время разве что ленивый фронтендер не компилирует свой JavaScript. А потому нужны разные хранилища для исходного кода и скомпилированного проекта.
Пройдя через тернии долгих обсуждений, мы выработали следующую концепцию. Должно быть два отдельных репозитория для исходников и для скомпилированной версии модуля. Причем второй репозиторий должен являться зеркалом первого.
Это значит, что при разработке какая-либо фича должна публиковаться до момента релиза в ветке с точно таким же именем, что и у ветки, в которой ведется разработка. Таким образом у нас есть возможность использовать экспериментальную версию модуля, устанавливая его из определенной ветки. Той самой, в которой мы ведем разработку — это очень удобно для проверки его в действии.
Плюс ко всему на каждую публикацию мы создаем метку, сохраняющую состояние проекта. Имя метки соответствует версии, прописанной в package.json. При установке из git-репозитория метка указывается после решетки, например:
Таким образом мы можем зафиксировать используемую версию модуля и не волноваться, что кто-то что-то поменяет.
Для нестабильных версий также создаются метки, однако к ним добавляется сокращенный хэш коммита в репозитории исходников, из которого и произведена публикация. Вот пример такой метки:
Такой подход позволяет добиться уникальности меток, а также связать их с репозиторием исходников.
Раз мы заговорили о стабильных и нестабильных версиях модуля, то вот как мы их различаем: если публикация выполняется из ветки master или develop, версия стабильная, в противном случае — нет.
Как организована работа с общими проектами
Все наши договоренности не имели бы смысла, если бы мы не могли их автоматизировать. В частности, автоматизировать процесс публикации. Ниже я покажу, как организована работа с одним из общих модулей — утилитой для тестирования пользовательских сценариев.
Эта утилита с помощью библиотеки puppeteer подготавливает браузер Chromium к использованию в докер-контейнерах и запускает тесты посредством Mocha. Участники всех команд могут модифицировать утилиту, не боясь при этом что-то сломать друг у друга.
В файле package.json утилиты для тестирования прописана следующая команда:
Она запускает рядом лежащий скрипт:
В свою очередь этот код через модуль Node.js child_process выполняет все необходимые команды.
Вот основные этапы его работы:
1. Проверка на наличие незакоммиченных изменений
Здесь мы проверяем результат команды git diff. Нехорошо, если в публикацию попадут изменения, которые отсутствуют в исходниках. К тому же это нарушит связь нестабильных версий с коммитами.
В константу build попадает результат выполнения сборки. Если все прошло хорошо, параметр status будет равен 0. В противном случае ничего опубликовано не будет.
3. Разворачивание репозитория скомпилированных версий
Весь процесс публикации — это ни что иное, как отправка изменений в определенный репозиторий. Поэтому скрипт создает в нашем проекте временную директорию, в которой инициализирует git-репозиторий и связывает его с удаленным репозиторием сборок.
Это стандартный процесс, использующий git init и git remote.
4. Генерация имени метки
Для начала мы выясняем имя ветки, из которой выполняем публикацию, при помощи команды git symbolic-ref. И задаем имя ветки, в которую будут залиты изменения (в репозитории сборок отсутствует ветка develop).
Используя команду git rev-parse, получаем сокращенный хеш последнего коммита в ветке, в которой находимся. Он может понадобиться для генерации имени метки нестабильной версии.
Ну и собственно составляем имя метки.
5. Проверка отсутствия точно такой же метки в удаленном репозитории
Если подобная метка была создана ранее, результат команды git ls-remote не будет пустым. Одна и та же версия должна быть опубликована лишь единожды.
6. Создание соответствующей ветки в репозитории сборок
Как я и говорил ранее, репозиторий скомпилированных версий утилиты представляет из себя зеркало репозитория с исходниками. А потому, если публикация происходит не из ветки master или develop, мы должны создать соответствующую ветку в репозитории сборок. Ну или по крайней мере убедиться в ее существовании
7. Подготовка файлов
Для начала необходимо удалить все, что могло оказаться в развернутом репозитории. Ведь, если мы используем ранее существовавшую ветку, она содержит в себе предыдущую версию утилиты.
Далее переносим обновленные файлы, необходимые для публикации, и добавляем их в индекс репозитория.
После подобной манипуляции git хорошо распознает произведенные изменения по строкам файлов. Таким образом мы получаем консистентную историю изменений даже в репозитории скомпилированных версий.
8. Коммит и отправка изменений
В качестве сообщения коммита в репозитории сборок мы используем имя метки для стабильных версий. А для нестабильных — сообщение коммита из репозитория исходников. Поддерживая таким образом нашу идею хранилища-зеркала.
9. Удаление временной директории
Ревью обновлений в общих проектах
Одним из важнейших процессов после внесения изменений в общие проекты становится ревью. Несмотря на то, что выработанная технология позволяет создавать абсолютно изолированные версии модулей, никому не хочется иметь десятки разных версий одной и той же утилиты. А потому каждый из общих проектов должен следовать единому пути развития. Об этом стоит договариваться между командами.
Ревью обновлений в общих проектах проводится членами всех команд по мере возможности. Это сложный процесс, так как каждая команда живет по собственному спринту и имеет разную загруженность. Иногда переход на новую версию может затянуться.
Тут лишь можно порекомендовать не пренебрегать и не затягивать с данным процессом.
Единый репозиторий для управления Enterprise Architecture
Моя история не для всех. В том смысле, что тема не хайповая. Но тем, кто в теме, надеюсь, будет интересно. Она (история) основана на реальном опыте последних лет. Я расскажу об одном из вариантов — с моей точки зрения, эффективном, — управления сложным архитектурным ландшафтом.
Что я подразумеваю под «сложным»: это несколько сотен бизнес-приложений с довольно внушительной дисперсией атрибутов — технологии, разнородность функциональности, связанность с другим приложениями, критичность, возраст, размер и так далее. Добавьте сюда динамику, поскольку ландшафт неустанно меняют несколько десятков внутренних и внешних команд. Иными словами — самый отпетый, или, на устойчивом жаргоне, «кровавый» энтерпрайз.
Очевидно, что в этом бурлящем котле каждая новая инициатива по изменению начиналась с поисков: где и что нужно делать, как определить достаточность и необходимость изменений. То есть проводился анализ. И поскольку котел большой и градус изменений высокий, то анализ, а он должен быть качественным, длился месяцами. Но тщательность не гарантировала 100%-го качества, поскольку на той же поляне, что и ваша инициатива, могли толкаться другие, внося непредвиденные вами изменения.
Кто-то скажет, что это обычная картина для «кровавого» энтерпрайза. То ли дело Agile-команды с единственным владельцем продукта. Всё учтено, и команда знает всё. Не буду спорить. Во многом это правда. Но на рваном лоскутном ландшафте независимых команд не построишь. А действительно крупные задачи одной командой не решишь. Да и в любой методологии должен быть разумный уровень порядка.
Единый архитектурный репозиторий
Именно с наведения порядка мы и начали. Это вылилось в создание единого архитектурного репозитория. Начнем с его мета-модели. По моему мнению, подобные изменения всегда надо начинать с разработки мета-модели. Это лучший способ объяснить заинтересованным сторонам и, самое главное, самим себе, что сможет дать новый репозиторий. Мета-модель покажет, как ваши цели ложатся на возможности систем, где репозиторий будет реализовываться. При её разработке проще всего посмотреть на документы, которые уже есть в вашей компании. Изучите имеющийся опыт. Посмотрите на ванильные модели различных вендоров. Если уж совсем по-серьезному, то почитайте ГОСТы, например, ГОСТ 57100 (ISO 42010).
Для начала включайте в мета-модель только самое необходимое, поскольку начинать лучше с простого. Потом, если окажется, что такой модели недостаточно, то развить её не составит труда. Причем развитие будет осознанным. То есть здесь самый правильный подход — итеративный. Начинайте с небольшого участка архитектуры. Посмотрите, как с ней справляется ваша модель. Достаточно ли в ней элементов, связей и атрибутов для поставленных целей и тогда принимайте решение о ее развитии.
Мы подошли к мета-модели сугубо утилитарно. В качестве перспективных были определены три цели:
Solution в основном повторяет структуру “Current”, но имеет некоторые особенности.
Хотя и сейчас модель остается довольно простой, но первый вариант был значительно проще. Это был только слой Application. Потом репозиторий пополнился компонентами, потом бизнес-объектами и бизнес-слоем.
Время от времени мы ощущаем последствия лаконичности модели, но для нас гораздо важнее не её усложнение, а зона покрытой репозиторием архитектуры и актуальность информации в репозитории. Так что, похоже, мы нашли ту золотую серединку, когда хватает сил на поддержание актуальной информации в репозитории и в тоже время уровень детализации полезен и достаточен для анализа архитектуры и создания решений для инициатив.
В основе нашего репозитория лежит Sparx Enterprise Architect: были использованы почти все возможности, предоставляемые этой системой по кастомизации решения — используется своя мета-модель (технология MDG), поддерживаемая тысячами строк кода на Java и Javascript. Конечно, кастомизация ведет к необходимости самостоятельного развития и поддержки, но мы были к этому готовы.
Current Architecture
Теперь немного подробнее о том, что из себя представляет текущая состояние репозитория.
На уровне бизнес-слоя основными элементами являются бизнес-сервисы и сценарии использования:
И сценарии использования:
В нашем случае сценарии использования детализируют бизнес-сервис в конкретных условиях его применения. Но всё же сценарий — это довольно крупно-гранулярное представление бизнес-функциональности.
Сценарии использования автоматизируются приложениями — это уже уровень Application Architecture, где приложения связаны между собой информационными потоками:
Потоки содержат бизнес-объекты из слоя Data Architrecture:
Основой для Application Architecture является Component Architecture, где каждое приложение имеет представление своей структуры в виде компонентов, а потоки детализируются в виде взаимодействия компонентов через интерфейсы:
Теперь посмотрите еще раз на упомянутые элементы и их взаимоотношения. Всё очень просто, но позволяет на достаточном уровне описать архитектуру крупного банка. После нескольких лет работы в репозитории накопилось почти десять тысяч архитектурных элементов, между которыми сформировалось несколько десятков тысяч связей. И это только Current Architecture.
Solution Architecture
Перейдем ко второму пункту обозначенных выше целей. Нам требуется создавать Solution Architecture для инициатив различных размеров, реализуемых по различным методологиям, включая Agile.
Решение описывается для каждого слоя архитектуры. Мета-модель Solution части репозитория в основном повторяет структуру Current, но имеет отличия. Например, набор атрибутов пересекается лишь частично. Плюс элементы и связи из части Solution имеют набор атрибутов, необходимых для описания решения.
Пройдемся по всем четырем слоям решения.
Бизнес архитектура содержит два вида элементов. Это крупные Use Cases, которые реализуются в проекте, и более детальные Requirements для «водопадных» проектов или User Stories в случае Agile-инициатив. Причем Use Cases обязательно имеют своё зеркало в части Current. При этом Requirements и User Stories — это элементы исключительно части Solution и не имеют представления в части Current. Еще одна важная деталь: Sparx-репозиторий не является для них мастер-системой. Они экспортируются из других систем.
Requirements и User Stories сопоставляются с ответственными за них приложениями.
Оставшиеся слои Solution Architecture имеют диаграммы, похожие на Current Architecture:
Цветовая гамма элементов и связей Solution Architecture передает вид архитектурных изменений. Описание изменений можно заносить как в соответствующие атрибуты элементов и связей, так и в прикрепленные к элементам Notes.
На основе этих данных генерируются соответствующие части архитектурного документа.
Хотя, как показывает практика, наиболее востребованными являются диаграммы. Именно они используются во время обсуждений с Enterprise-архитекторами, командами разработчиков, вендорами и на Архитектурном комитете.
Что самое замечательное, в силу «единственности» репозитория все элементы и связи, используемые на диаграммах, задокументированы и единообразно понимаются всеми участниками дискуссий. Поэтому, изначально все участники находятся на одной волне.
При анализе больших инициатив Solution-архитектор не тратит время на поиск информации | Software-архитектор при изучении Solution-архитектуры видит хорошо знакомые ему элементы. Он понимает, о чем эта архитектура |
Еще один выдающийся момент. Описание Solution-архитектуры достаточно для актуализации текущего ландшафта. Таким образом, по факту выпуска решения в production, архитектурные изменения с помощью скриптов переносятся из части Solution в часть Current.
Репозиторий благодаря такой взаимосвязи остается актуальным несмотря на постоянные инициативы по изменениям ландшафта.
Поддержка репозитория
Репозиторий с таким значительным количеством элементов и связей, в котором работают десятки пользователей, надо содержать в адекватном состоянии. Например, все обязательные атрибуты должны быть заполнены; связи между элементами должны быть определенного вида. Более того, состояние архитектур Application и Component должно соответствовать одно другому, поскольку они представляют одни и те же приложения, но с разной степенью детализации.
Конечно, обучение пользователей играет важную роль. Но этого крайне мало. Людям свойственно ошибаться. Мы смягчили эту проблему с помощью кода на Java и JavaScript. Каждую ночь по расписанию запускаются скрипты, которые значительно облегчают нашу жизнь:
Выводы
Сравнивая два состояния — сегодняшний день и тот «доисторический» период, — однозначно можно отметить, что требования к архитекторам возросли. Вырос и порог входа для любого человека, которому по тем или иным причинам необходим анализ архитектуры.
Крайне важным является то, что люди, ответственные за ведение репозитория, тратят на это достаточно много времени и, в нашем случае, должны уметь разрабатывать код.
Возвращаясь к мета-модели репозитория, хочу отметить, что в рамках простой модели очень сложно свести воедино мнение многих заинтересованных сторон, и еще сложнее оставаться с нею продолжительное время. И любые изменения в мета-модели должны в том или ином виде отразиться в скриптах автоматизации.
С другой стороны, анализ архитектуры и дизайн решения стали проще и значительно короче. Качество Solution-архитектуры возросло на порядок. Решение стало намного детальнее и всегда остаётся консистентным. В работе архитектора теперь минимизированы рутинные операции, связанные с оформлением документации, с необходимостью поддержки её в актуальном состоянии. Архитектор действительно занимается творчеством.
И в заключении несколько слов об инструменте, который мы использовали для реализации репозитория, о Sparx Enterprise Architect. С моей точки зрения, у него выдающееся соотношение цена/качество. Конечно, в основном это обусловлено его низкой ценой, но присутствует практически вся необходимая нам функциональность.
Что нам не хватает, так это разнообразных интерактивных вьюшек на данные в архитектурном репозитории. Ведь качественный анализ без них крайне затруднен. Начинали мы с простых SQL-запросов напрямую к базе данных Sparx. Но потом решили эту проблему самостоятельной разработкой. Дополнительно к самому Sparx мы смотрим на репозиторий через кастомное веб-приложение, где наиболее популярные вьюшки представлены в табличном виде с возможностью фильтрации, сортировки и трассировки между ними по выбранным значениям:
Монолитные репозитории в Git
Многие выбрали Git за его гибкость: в частности, модель веток и слияний позволяют эффективно децентрализовать разработку. В большинстве случаев эта гибкость является плюсом, однако некоторые сценарии поддержаны не так элегантно. Один из них — это использование Git для больших монолитных репозиториев — монорепозиториев. Эта статья исследует проблемы монорепозиториев в Git и предлагает способы их смягчения.
Скала Улуру в Австралии как пример монолита — КДПВ, не более
Что такое монорепозиторий?
В каких случаях монорепозитории удобны?
С тысячами коммитов в неделю и сотнями тысяч файлов, главный репозиторий исходного когда Facebook громаден — во много раз больше,
чем даже ядро Linux, в котором, по состоянию на 2013 год, находилось 17 миллионов строк кода в 44 тысячах файлов.
Концептуальные проблемы
С хранением несвязанных проектов в монорепозитории Git возникает много концептуальных проблем.
Тег в Git — это именованный указатель на определённый коммит, который, в свою очередь, ссылается на целое дерево. Однако польза тегов уменьшается в контексте монорепозитория. Посудите сами: если вы работаете над веб-приложением, которое постоянно развёртывается из монорепозитория (Continuous Deployment), какое отношение релизный тег будет иметь к версионированному клиенту под iOS?
Проблемы с производительностью
Наряду с этими концептуальными проблемами существует целый ряд аспектов производительности, влияющих на монорепозиторий.
Количество коммитов
Хранение несвязанных проектов в едином большом репозитории может оказаться хлопотным на уровне коммитов. С течением времени такая стратегия может привести к большому числу коммитов и значительному темпу роста (из описания Facebook — «тысячи коммитов в неделю»). Это становится особенно накладно, поскольку Git использует направленный ациклический граф (directed acyclic grap — DAG) для хранения истории проекта. При большом числе коммитов любая команда, обходящая граф, становится медленнее с ростом истории.
Количество указателей (refs)
Если указатели хранятся не в сжатом виде, перечисление веток будет работать медленно. После выполнения команды git gc указатели будут упакованы в единый файл, и тогда перечисление даже 20.000 указателей станет быстрым (около 0.06 секунды).
Количество учитываемых файлов
Большие файлы
Большие файлы в одном поддереве/проекте влияют на производительность всего репозитория. Например, большие медиа-файлы, добавленные в проект iOS-клиента в монорепозитории, будут клонироваться даже разработчикам, работающим над совершенно другими проектами.
Комбинированные эффекты
Что насчёт Bitbucket?
Стратегии смягчения последствий
Конечно, было бы здорово, если бы Git специально поддержал вариант использования с монолитными репозиториями. Хорошая новость для подавляющего большинства пользователей заключается в том, что на самом деле, действительно большие монолитные репозитории — это скорее исключение, чем правило, поэтому даже если эта статья оказалась интересной (на что хочется надеяться), она вряд ли относится к тем ситуациям, с которыми вы сталкивались.
Есть целый ряд методов снижения вышеописанных негативных эффектов, которые могут помочь в работе с большими репозиториями. Для репозиториев с большой историей или большими бинарными файлами мой коллега Никола Паолуччи описал несколько обходных путей.
Удалите указатели
Если количество указателей в вашем репозитории исчисляется десятками тысяч, вам стоит попробовать удалить те указатели, которые стали ненужными. Граф коммитов сохраняет историю эволюции изменений, и поскольку коммиты слияния содержат ссылки на всех своих родителей, работу, которая велась в ветках, можно отследить даже если сами эти ветки уже не существуют. К тому же, коммит слияния зачастую содержит название ветки, что позволит восстановить эту информацию, если понадобится.
В процессе разработки, основанном на ветках, количество долгоживущих веток, которые следует сохранять, должно быть небольшим. Не бойтесь удалять кратковременные feature-ветки после того, как слили их в основную ветку. Рассмотрите возможность удаления всех веток, которые уже слиты в основную ветку (например, в master или production ).
Обращение с большим количеством файлов
Если в вашем репозитории много файлов (их число достигает десятков и сотен тысяч штук), поможет быстрый локальный диск и достаточный объём памяти, которая может быть использована для кеширования. Эта область потребует более значительных изменений на клиентской стороне, подобных тем, которые Facebook реализовал для Mercurial.
Их подход заключается в использовании событий файловой системы для отслеживания изменённых файлов вместо итерирования по всем файлам в поисках таковых. Подобное решение, также с использованием демона, мониторящего файловую систему, обсуждалось и для Git, однако на данный момент так и не привело к результату.
Используйте Git LFS (Large File Storage — хранилище для больших файлов)
Bitbucket Server 4.3 полностью поддерживает Git LFS v1.0+, а кроме того, позволяет просматривать и сравнивать большие графические файлы, хранящиеся в LFS.
Мой коллега Стив Стритинг активно участвует в разработке проекта LFS и не так давно написал о нём статью.
Определите границы и разделите ваш репозиторий
Наиболее радикальное решение — это разделение монорепозитория на меньшие, более сфокусированные репозитории. Попробуйте не отслеживать каждое изменения в едином репозитории, а идентифицировать границы компонентов, например, выделяя модули или компоненты, имеющие общий цикл выпуска версий. Хорошим признаком компонентов может быть использование тегов в репозитории и то, насколько они имеют смысл для других частей дерева исходного кода.
Хоть концепт монорепозитория и расходится с решениями, сделавшими Git чрезвычайно успешным и популярным, это не означает, что стóит отказываться от возможностей Git только потому, что ваш репозиторий монолитный: в большинстве случаев, для возникающих проблем есть работающие решения.
Штефан Заазен — архитектор Atlassian Bitbucket. Страсть к DVCS привела его к миграции команды Confluence с Subversion на Git и, в конечном итоге, к главной роли в разработке того, что сейчас известно под названием Bitbucket Server. Штефана можно найти в Twitter под псевдонимом @stefansaasen.