company_banner

Монорепо: жизнь до и после

    Это история о том, почему в одном из направлений «Юлы» отказались от практики отдельных репозиториев на микросервисы и внутренние библиотеки, перейдя на монорепозиторий, и что из этого вышло. О проблемах, с которыми столкнулись в компании, и тех, которые получилось решить при помощи этого переезда, рассказал на конференции Golang Live 2020 руководитель b2b-разработки «Юлы» Валентин Дубровский.



    В этой статье мы поговорим о:

    1. Проблемах, которые решает монорепо;
    2. Минусах монорепозитория;
    3. GO в команде B2B «Юлы»;
    4. Том, как там вводили монорепозиторий.

    Внимание! Я буду говорить не про монолит. У многих любое «моно-» ассоциируется именно с ним.



    Речь пойдет о монорепозитории.

    Какие проблемы решает монорепо?


    Десять лет назад фронтенд и PHP-код хранились в «Юле» в одном репозитории по 10 Gb (GitLab/GitHub).

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

    Сегодня мы попробуем осмыслить шаг назад и поговорим про монорепозиторий.

    Монорепозиторий – это когда все микросервисы хранятся в одном репозитории, вместо отдельных на каждый сервис. Мы берем несколько репозиториев, в каждом из которых находится его сервис, или библиотека, и переносим их в один.

    Для чего это нужно? Почему бы и дальше не жить по стандартам один сервис – один репозиторий, или одна внутренняя библиотека – один репозиторий? С какими проблемами нам поможет справиться монорепо?

    Ориентирование на местности


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

    А потом приходит его коллега и спрашивает: «Ты делал сервис, который загружает картинку пользователя. Расскажешь, где его найти?». Он не может сам сориентироваться по поиску через GitLab/GitHub в устаревшей документации сервиса. Поэтому обращается к разработчику сервиса напрямую.

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

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

    Приватные репозитории


    Кроме того, монорепозиторий позволяет решить проблему работы с приватными репозиториями.

    Рассмотрим сценарий. В базе данных есть адаптер: Redis, MongoDB — обертка над общей публичной библиотекой. Мы делаем для нее отдельный репозиторий. И осуществляем некий порядок действий: добавляем CI, который прогоняет тесты, навешиваем тэги при релизе master. Дальше начинаются танцы с бубнами. Как притянуть эту библиотеку в наш репозиторий? Ведь через Go.mod это не так просто сделать.

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

    Одна задача — один Pull request


    Еще один интересный плюс монорепозитория связан с самим выполнением задачи.

    Разработчик берет задачу, которая затрагивает несколько сервисов. Например необходимо что-то сделать в GraphQL, или вокруг микросервисов.

    У него два алгоритма.

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

    Второй вариант решения этой проблемы – декомпозиция задачи по сервисам. Там есть общая родительская задача. Ревью проходят по одному, вне контекста общей задачи. Или же можно попробовать сымитировать контекст. Но это все дополнительные действия, которые нужно предпринять, несмотря на то, что задача маленькая. Ведь изменилось условно 15-20-30 строчек кода.

    Если задача небольшая, монорепо позволяет сделать все в рамках одного pull request. Ревью сможет заняться один разработчик, у которого будет полный контекст по задаче. Это удобно и ускоряет поставку задач на production из-за того, что мы избавляемся от лишнего оверхеда.

    Приведу немного надуманный пример, который, тем не менее, позволяет понять проблему глубже.

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

    Улучшаем командный процесс


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

    Эти вопросы можно решить и в полирепозиториях. Но в монорепо это происходит само по себе.

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

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

    Окружение


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

    Есть инструмент docker-compose, который просто идеален для локальной разработки. В нем настроен билд сервиса и запуск. И это прекрасный вариант, не требующий лишних действий.

    Базовые штуки в локальном окружении настроить очень просто. Например, запуск линтера на pre-commit hook. Мы должны закинуть в .git скрипт, который будет запускаться на pre-commit hook. Это позволяет отлаживать простой код на локальном уровне, а не в CI.

    Минусы монорепозитория


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

    Когда у нас есть отдельный репозиторий на каждый сервис, мы можем там использовать совершенно разные мажорные версии библиотек. Например, в одном репозитории драйвер для Mongo шестой версии, а в другом — этот же драйвер пятой версии. И с этим не возникнет никаких проблем.

    В монорепозитории так не получится, и придется следить за breaking changes. Мы не можем использовать разные мажорные версии в разных репозиториях. Кроме того, нужно внимательно следить за внутренними библиотеками.

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

    При использовании монорепа усложняется CI/CD процесс.

    Если мы живем в парадигме один сервис – один репозиторий, мы находимся в 4 джобах: test, build, deploy и что-нибудь еще. Слили master, сбилдили, задеплоили – ОК.

    В монорепозитории это сложнее, потому что при сливе master, нам нужно билдить только то, что изменилось. Монорепозиторий будет потихоньку разрастаться. В нем может быть 30, 40, 50, 100 сервисов. И если мы будем билдить все, ни к чему хорошему это не приведет.

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

    GO в команде B2B «Юлы»




    В «Юле» несколько десятков микросервисов на GO.

    GRPC и Kafka используются для общения между микросервисами, GRPC для синхронных, Kafka для асинхронных. Это важно, потому что GRPC по протофайлу представляет нам генерированный клиент, и в монорепозитории его достаточно просто использовать между микросервисами. Для клиентов мобильных приложений и веба используется GraphQL.

    Gitlab CI используется для билда Docker-образов и для выливки на тестовые стенды. В «Юле» используют Gitlab CI для всех CI-процессов, пишут его сами без DevOps. DevOps-инженеры помогают дать пайплайн, который дергается для того, чтобы вылить сервис на тестовое окружение.

    Сервисы имеют однотипную структуру, которую определили на раннем этапе.
    Это не стандартный Golang package layout, а своя версия.

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

    В каждом сервисе должен быть реализован CI/CD. Базовые штуки вносили в Gitlab CI шаблоны, и инклюдили. Если вы знакомы с Gitlab CI, понимаете, о чем я – назовем это переиспользованием шаблонов.

    Для кода, который можно переиспользовать, заводили свой репозиторий.

    Я уже упоминал, что с приватными репозиториями не все так просто, поэтому в «Юле» сделали небольшой ход конем: подняли Athens. В ней можно указать путь до GitLab и креды до него, это позволяет стягивать приватные репозитории через GOPROXY. Для каждого разработчика, для каждой CI машины это выглядит так: ты делаешь пустой приватный пакет, указываешь Go proxy до Athens, и она упрощает жизнь.

    Кроме того, сделали отдельный репозиторий для docker-compose, где указывали latest до registry каждого сервиса.

    Как в «Юле» вводили монорепо


    Сначала нужно было объединить репозитории.

    Это сложный процесс, и его разбили на шаги:

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

    Первая стратегия выглядит следующим образом: мы все фризим. Для этого подготавливаем инфраструктуру в монорепо. Это самый простой паттерн действия.

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

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

    Дальше мы переходим к локальному окружению.

    Как я уже говорил, docker-compose идеально для этого подходит. Лучшая стратегия: отправить через volume в каждый билд путь до до локальной директории go modules. Тогда, если вы что-то делаете go.mod, оно попадает в GOPATH/pkg/mod. И это ускорит билд.

    И конечно же добавить какие-нибудь вкусняшки. В этом случае речь о UI для GRPC. На каждый сервис сделали свой UI, вывели порт наружу. То есть можно было просто собирать GRPC-запросы в формочки.

    После чего добавили rest proxy для Kafka. Он позволяет продьюсить и консьюмить из Kafka через обычный Postman.

    Теперь поговорим о билдах.

    В «Юле» пошли по такому сценарию: для каждого сервиса использовали директиву Gitlab CI only changes. Записали в only changes Dockerfile, go.mod, внутренние библиотеки и все зависимости от внешних сервисов:



    Это все здорово работает до тех пор, пока не меняется что-то общее, например, go.mod. Когда сливается сервис, в который добавилась новая библиотека, CI начинает все билдить.

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

    Что в компании сделали еще? Раньше там были пухлые Gitlab CI файлы. Но в монорепо можно обращаться к локальным скриптам. И в «Юле» выделили папку script на CI. И тяжелые вещи, которые нужны в CI, пишутся на Go-коде. Например, выбор ревьювера.

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

    На втором этапе развития, в чат скидывался Slack, в нем PR, и автоматически рандомно из чата выбирался ответственный за ревью.

    С монорепо решили перенести это в CI. Когда аттачится pull request, запускают Go-скрипт и выбирают двух случайных ревьюверов. Первого на основе измененных файлов: кто больше всего трогал, тот и выбирается рандомно среди топ-кандидатов. Второго — из всего проекта монорепо.



    Это очень круто работает.

    Естественно, в монорепозитории как по маслу пошли такие вещи, как нотификация при открытии PR, или нотификация, если PR долго не ревьювятся. Каждые 2-3 часа напоминают: «Эй, не забудь про ревью!».



    Это повышает конверсию из pull requests в релиз, работает коллективная ответственность.

    В «Юле» нет Continuous Delivery в production, а релиз происходит так:



    Сейчас в компании почти continuous delivery в production (кстати, он есть на тестовые стенды).

    Выводы


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

    Для «Юлы» переход в монорепозиторий стал успешной практикой, поскольку:

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


    Возможно, в очень большой компании стоит делать монорепозитории по направлениям.

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

    Хотите больше материалов по go-разработке? У вас есть возможность купить видео с Golang Live 2020.

    В этом году на конференции сделали акцент на Go в продуктовой разработке и раскрыли эту тему с трех сторон: как справиться с проблемами Go и какие есть тулзы, почему все-таки стоит выбрать именно Go, и как после этого поддерживать продукт.
    Конференции Олега Бунина (Онтико)
    Конференции Олега Бунина

    Comments 13

      +5
      Пока речь идет о 50-100 сервисах монорепа еще как-то дышит. Но когда их становится несколько тысяч, это превращается в кошмар. Была у нас такая ситуация с монорепой на вебсервисы. Заходишь в вебморду гита (битбакет) и получаешь сообщение «загружены первые 1000 элементов, больше не могу» А тебе нужен 2067-й элемент…

      Клонирование огромной монорепы к себе на локал тоже отдельная песня. Перед уходом ставишь git clone… и можно идти домой. К утру как раз склонируется.

        0
        Я вот тоже думаю, что при росте количества сервисов\либ\истории коммитов начнуться проблемы с монорепой.
        Кроме того, не понятно что делать, если нужен сервис не на ГО.
        Кроме того, отдельные сервисы все-же заставляют писать более изолированные системы. Да, нужно больше движений, когда задача задевает несколько сервисов, но ты будешь обязан подумать: «а как сделать изменения в сервисе А, чтоб не сломать совместимость?». Как результат, сервисы более стабильные.
          0
          Я просто прикинул как бы это выглядело у нас и ужаснулся…

          Проектов в гите десятки (ядровые функции, система расчетов, модуль пластиковых карт, универсальный кассовый модуль и т.д. и т.п. — у каждой команды свой проект и это только бэк, а есть еще фронты, пега, SAP, WBI и черт знает что еще).

          И в каждом проекте сотни репозиториев — на каждую функциональную единицу свой (например, отдельный по техвыписке, отдельный по учету валютных операций, отдельно по карточкам клиентов — их вообще несколько — физики, юрики...). И в каждом репозитории как минимум десяток (а может быть и сотня и более) объектов.

          Свести все это в монорепу? Вы серьезно? Как там потом что-то найти можно будет?
          Тут в пределах одной репы приходится папочную структуру делать чтобы разнести объекты по типам (например — таблицы отдельно, индексы отдельно, функционал отдельно и т.п...)

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

          Ну и интеграционное тестирование никто не отменял. Т.е. сначала компонентное, потом бизнес, потом интеграционное и нагрузочное. И только после этого внедрение на пром.
            0
            Свести все это в монорепу? Вы серьезно? Как там потом что-то найти можно будет?
            Тут в пределах одной репы приходится папочную структуру делать чтобы разнести объекты по типам (например — таблицы отдельно, индексы отдельно, функционал отдельно и т.п...)

            ну, раскидать по каталогам или раскидать по репам — это просто разные способы организации, иерархии, которые могут реализовать один и тот же уровень разделения...

              0
              ну, раскидать по каталогам или раскидать по репам — это просто разные способы организации, иерархии, которые могут реализовать один и тот же уровень разделения...


              Это очень разные способы организации иерархии.

              Когда в одной монорепе будет копошиться 10 человек и каждый со своими коммитами и PR, там быстро наступит ситуация когда сложно что-то разобрать.

              У нас есть строгие правила ведения веток. feature, как только только она прошла компонентное тестирование и ушла на бизнес-тест, мержится в develop. Новые feature делаются только от develop и ниоткуда больше. Т.е. develop — это синхронизационная ветка.

              develop мержится в мастер когда поставка ушла на прод. Т.е. мастер всегда отражает то, что стоит на проде. develop — то, что в бизнес-тесте и далее, но до прода еще не дошло.

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

              А раскидать по каталогам — это просто свое личное удобство, не более.

              Вот сколько у вас всего репозиториев объеденено в монорепу?

              У нас в гите 205 проектов И в каждом проекте… Да черт его знает. В нашем только несколько сотен репозиторииев.

              И если все это в монорепу слить, то получим репу с десятками тысяч объектов, в которой одновременно работает более сотни человек. Как всем этим управлять и кто за всем этим будет следить?

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

                Я не адвокат монорепы, не знаю, с чего Вы это взяли ) Я наоборот согласен с тем, что инструменты — git и gitlab провоцируют скорее на создание все большего и большего количества микрорепозиториев (один репо — один компонент). И туда же концепция с форками. И необходимость тащить ещё интеграционные/инфраструктурные репо


                И на самом деле то, что в монопереход все толкаются и мешают друг другу — ну, наверное, преувеличение. Хотя и вероятность конфликтов ниже, чем в куче мелких реп.

                  0
                  И на самом деле то, что в монопереход все толкаются и мешают друг другу — ну, наверное, преувеличение. Хотя и вероятность конфликтов ниже, чем в куче мелких реп.


                  О каких конфликтах речь?

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

                  С репой >1000 объектов просто неудобно работать. Даже нацти нужный объект из 1000 уже задача та еще.

                  Главное что непонятно — зачем? Все проблемы, описанные в статье решаются на совершенно ином уровне. Это все проблемы роста мелких команд, берущихся за проект больший, чем они могут осмыслить
          +1

          Эти проблемы решаются крупными компаниями — Microsoft доработало git, чтобы скачивать только часть репозитория. Яндекс изобрел свой репозиторий Arc… В общем, нужно помнить, что ты не Гуголь и не обязательно, что механизмы, которые могут позволить крупные технологические компании, могут быть смапплены на мелочь вроде Юлы...

          +5
          Разработчики идут на кухню, разговаривают с другой командой, и та заявляет, что у них уже есть такой сервис.


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

            Спорный момент.
            Проблемы остаются те же, ибо изначально было проведено четкое неравенство между монорепо и монолит, а значит каждый проект вашего монорепо даже если и имеет лишь одну общую ссылку на условный адаптер Redis — то код всех проектов по-отдельности завязан на эту версию. И соответственно при изменении интерфейсов работы с библиотекой придется гулять по всему монорепо в поисках точек взаимодействия и править код коллег, ведь он может даже не скомпилиться, и в таком случае придется вникать в их код, чтобы внести корректную правку, и в итоге ты все равно контактируешь с каждым из них, чтобы вместе правильно обновить версию в конкретно их проекте.
            Или что хуже — обновляешь всё сам, но не учитываешь какие-либо особенности "чужого" проекта и коллеги узнают о твоём "обновлении" сильно позже.

              +5
              Он не может сам сориентироваться по поиску через GitLab/GitHub

              Открываете корень всех проектов и листаете и ищите. Ни чем не отличается от моно репозитория. Зато когда у вас репозиторий на сервис то локально вы можете их группировать или называть как хотите. Плюс не надо тянуть 100500 сервисов над которыми вы вообще не работаете и знать не надо что там.


              Ведь через Go.mod это не так просто сделать.

              Выкиньте Го :) и сразу куча проблем уйдет.


              Если хочется, например, обновить библиотеку, чтобы она обновилась во всех сервисах

              А зачем это? Микросервис это как раз о том, что можно работать и менять только один сервис и остальным будет все равно что там внутри. К примеру, в новом сервиса хорошо заходят клиентская инвалидация с Редис 6, но это новая версия библиотеки в которой есть изменения по АПИ и это значит что фиг я протащу такое изменение в мой сервис и отдельный редис, потому что у остальных есть дела поважнее чем переводить свои сервисы на новую версию библиотеки.


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

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


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

              Я не всегда успеваю понимать что происходит в своей команде, а мониторить всю компанию это надо тратить все время и не писать ни строчки кода.


              Наверное, каждый мечтает о том, чтобы было возможно локально поднимать все окружение по щелчку пальцев.

              А вот это самая большая проблема — если у вас возникает такая ситуация то нет у вас микросервисов, а есть распределенный монолит. При правильной архитектуре, локально вам нужен только один ваш сервис и его состояние. А то потом вы упретесь что надо поднять 100 сервисов, а с ними еще 40 Редисов, 30 Постгресов и тд что не реально на одной локальной машине. И если это возможно все поднять на одной машине и вам такое надо то зачем микросервисы? Сделайте монолит и сразу кучу проблем уйдет :)

                0
                А зачем это? Микросервис это как раз о том, что можно работать и менять только один сервис и остальным будет все равно что там внутри.


                Вот это ключевой момент. Главное — сохранение интерфейса. А внутреннюю логику можно (и иногда приходится) править. И это хорошо и правильно. Так и должно быть. Изменились требования — поправили под них логику работы одного сервиса и вся система стала работать по новым требованиям.

                А вот это самая большая проблема — если у вас возникает такая ситуация то нет у вас микросервисов, а есть распределенный монолит. При правильной архитектуре, локально вам нужен только один ваш сервис и его состояние. А то потом вы упретесь что надо поднять 100 сервисов, а с ними еще 40 Редисов, 30 Постгресов и тд что не реально на одной локальной машине. И если это возможно все поднять на одной машине и вам такое надо то зачем микросервисы? Сделайте монолит и сразу кучу проблем уйдет :)


                Вот этой проблемы у нас нет в силу особенностей работы. Пишем под AS/400 и все поставки тестируются на отдельном тестовом сервере. Т.е. разработка — гит — сборка из гита в артифактори — установка из артифактори на тестовый сервер — тестирвание — продвижение дальше до прома.

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

                Наверное, и в других ситуациях можно организовать что-то подобное. Тогда не потребуется разворачивать всю среду на локале. И тесты будут более объективными т.к. проходить будут в близком к прому окружении — нестыковки с другими патчами там сразу вылазят.
                0

                Очень спорная аргументация. А самое прекрасное — это когда есть разработка на аутсорсе, которая не должна видеть весь монорепо, а эти ребята разрабатывают какой-то изолированный сервис… И приходится изобретать механизмы переливки.
                Я уж не говорю, что Gitlab не любит монорепы от слова совсем…
                Ну, и типичный кейс, когда комменты интереснее статьи

                Only users with full accounts can post comments. Log in, please.