Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Monorepo vs Polyrepo - переваги та недоліки?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Monorepo vs polyrepo**: monorepo тримає всі проекти в одному Git-репозиторії; polyrepo дає кожному проекту власне репо. Monorepo дозволяє атомарні коміти між застосунками і спільними бібліотеками без version drift. Polyrepo підходить незалежним командам з окремими каденсами деплою. **Ключове:** спільний код тягне до monorepo, автономія команд - до polyrepo.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Monorepo vs polyrepo** - monorepo тримає всі проекти в одному Git-репозиторії; polyrepo дає кожному проекту власне репо. ## Теорія ### TL;DR - Monorepo = одна спільна кухня (спільні інгредієнти і інструменти); polyrepo = окремі кухні для кожного шефа, де кожна спеція дублюється - Головна різниця: monorepo дозволяє зафіксувати зміну в `shared-ui` і оновити всі залежні застосунки одним комітом; polyrepo потребує publish-циклу і ручного бампу версій у кожному споживачі - Більше 5 взаємозалежних пакетів - вибирай monorepo; незалежні команди з окремими сервісами - вибирай polyrepo - Основні інструменти: Nx, Turborepo, Bazel для monorepo; Lerna і git submodules для polyrepo - Реальні цифри: Google тримає один Bazel-монорепо на 86TB; Netflix розбиває все на 700+ окремих репо через Spinnaker ### Швидкий приклад Структура monorepo (Nx workspace): ``` my-company/ apps/ web/ # React-застосунок - імпортує shared-ui напряму api/ # Node.js бекенд libs/ shared-ui/ # UI-компоненти, без кроку публікації nx.json # Будує тільки змінені проекти ``` Polyrepo: ``` my-company-web/ # Git repo #1 - бере shared-ui через npm my-company-api/ # Git repo #2 my-company-ui/ # Git repo #3 - публікується в npm registry ``` В monorepo один `git commit` оновлює `web` і `shared-ui` разом. В polyrepo та ж зміна потребує трьох комітів, npm publish і бампу версій. ### Ключова різниця Справжня різниця між monorepo і polyrepo проявляється при зміні спільної бібліотеки. В monorepo ти оновлюєш `shared-ui`, і граф залежностей (Nx будує його з `tsconfig.json`) автоматично позначає `web` для перебудови. Один PR, одне ревʼю, нульовий version mismatch. В polyrepo та ж зміна потребує публікації нової npm-версії, оновлення version pin у кожному споживачі, очікування CI в кожному репо і координації мерджів між командами. Дрейф версій (version drift) - не теоретична проблема. Це стандартний результат для будь-якої нетривіальної спільної бібліотеки в polyrepo. ### Коли що використовувати **Вибирай monorepo якщо:** - Кілька застосунків поділяють UI-компоненти, утиліти або типи - Потрібні атомарні зміни по всьому стеку (оновити API-контракт і фронтенд одним PR) - Команда невелика або середня (до 50 розробників) і власнить весь продукт - Ти будуєш design system, який використовують 3+ застосунки **Вибирай polyrepo якщо:** - Команди власнять повністю незалежні сервіси без спільного коду - Потрібен суворий контроль доступу на рівні репозиторію (compliance, security isolation) - Сервіси деплояться на абсолютно різних каденсах без coupling - Ти запускаєш [мікросервіси](/questions/microservices-architecture), де кожен сервіс є black box ### Порівняльна таблиця | Аспект | Monorepo | Polyrepo | |---|---|---| | Шерінг коду | Прямі імпорти, без публікації | npm-публікація + version pinning | | Коміти | Атомарні по всіх проектах | Окремі PR, синхронізація через теги | | Швидкість збірки | Кешована/інкрементальна (Turborepo кешує 90%+) | Повна перебудова кожного репо | | Розмір репо | Росте до 100GB+ | Невеликий (1-5GB кожне) | | Контроль доступу | Шлях-базований через CODEOWNERS | Дозволи на рівні репо | | CI/CD | Складна оркестрація (Nx affected) | Прості пайплайни на кожне репо | | Інструменти | Nx, Turborepo, Bazel | Lerna, git submodules | | Хто використовує | Google, Meta, Microsoft | Netflix, AWS | | Коли використовувати | Спільний код, єдиний продукт | Автономія команд, незалежні сервіси | ### Як це працює всередині Git в monorepo обробляє весь репозиторій як єдине дерево. `git diff` охоплює всі проекти. Nx парсить `project.json` і `tsconfig.paths`, будує граф залежностей і запускає `nx affected:build` тільки для того, що змінилось. Якщо ти торкнувся `shared-ui`, Nx відстежує, які застосунки його імпортують, і перебудовує лише їх. Google доводить це до крайнощів: Bazel хешує кожен вхідний файл і перевіряє remote cache спочатку. При 95% hit rate збірка 100,000 файлів займає стільки ж часу, що й збірка 1,000. Polyrepo не має ніякої крос-репозиторної свідомості за замовчуванням. CI запускається незалежно для кожного репо. Коли `shared-ui` публікує v2.1.0, жодне з залежних репо не отримує автоматичного сповіщення. Команди дізнаються про breaking changes тільки коли їхній CI ламається після оновлення version pin. ### Типові помилки **Помилка: запускати повний CI на кожен push в monorepo** Без affected-фільтрації monorepo збирає всі проекти на кожен push. З 10 застосунками це 10-хвилинний [CI-пайплайн](/questions/ci-cd-pipelines) на однорядкове виправлення. ```bash # Неправильно: перебудовує все npm run build:all # Правильно: перебудовує тільки залежні проекти npx nx affected:build --base=main ``` **Помилка: вважати, що monorepo не підтримує контроль доступу** Це хибне уявлення. GitHub і GitLab підтримують path-based branch protection. Файл CODEOWNERS задає гранулярні вимоги до ревʼю: ``` # .github/CODEOWNERS /libs/shared-ui/ @ui-team /apps/api/ @backend-team /infra/ @platform-team ``` Pull request, що торкається `libs/shared-ui`, автоматично запитує ревʼю від `@ui-team`. Ніхто поза командою не може замерджити ці зміни без ревʼю. Модель доступу більш гранулярна, ніж дозволи на рівні репо в polyrepo. **Помилка: polyrepo без автоматичного версіонування** Ручні version bumps призводять до зламаних деплоїв. Якщо `shared-ui` публікує major version, але downstream-команда пропускає бамп, їхня збірка ламається без попередження. Рішення: автоматизуй через conventional commits і `npm version` в CI. **Помилка: міграція в monorepo через злиття git-історій** Злиття повних історій з 5 репо створює нечитабельний git log з 50,000 комітів непов'язаних проектів. Використовуй `git filter-repo` для squash або імпортуй кожен проект з чистим початковим комітом. **Помилка: ігнорувати remote caching** Monorepo без remote cache сповільнюється по мірі росту і врешті-решт стає не швидшим за polyrepo. Turborepo Remote Cache або Nx Cloud розподіляють кешовані артефакти між усіма розробниками і CI-машинами. Збірка одного інженера наповнює кеш для наступних десяти. ### Де зустрічається на практиці - **Google** використовує Bazel на одному монорепо з 2 мільярдами рядків коду і 120,000 інженерів; зміни в Android і Chrome деплояться одним атомарним комітом - **Meta** запускає Buck на монорепо, що містить React Native і всі вебзастосунки - **Microsoft** використовує Nx для розробки VS Code і TypeScript - **Netflix** керує 700+ окремими репо через Spinnaker для деплой-оркестрації - **Uber** тримає монорепо з 1,000+ пакетами для координації мобільних і вебплатформ ### Можливі питання на співбесіді **Q:** Як Nx визначає, які проекти зачеплені змінами? **A:** Nx парсить `project.json` і `tsconfig.paths` і будує граф залежностей. При пуші порівнює поточний коміт з base SHA і відстежує, які проекти імпортують змінені файли. **Q:** В чому різниця між Bazel і Turborepo? **A:** Bazel є hermetic: збірки відтворювані на будь-якій машині, бо кожен вхід хешується і ізолюється. Turborepo налаштовується набагато швидше і добре підходить для JS-проектів. Bazel має сенс при 100,000+ файлів, Turborepo покриває більшість команд нижче цього порогу. **Q:** Як примусово застосовувати межі модулів (module boundaries) в monorepo? **A:** Nx надає `enforceModuleBoundaries` в конфігурації eslint. Це блокує невалідні імпорти між пакетами на основі тегів, що ти призначаєш кожному проекту. Наприклад, `web` може імпортувати `shared-ui`, але не `api`. **Q:** Як команди в polyrepo можуть шерити код без переходу на monorepo? **A:** Через приватний npm registry: публікуй пакети і бери їх як версійні залежності. Компроміс - publish-update-merge цикл на кожну спільну зміну. Git submodules є альтернативою, але вони повільні і схильні до проблем зі станом. **Q:** Як масштабувати monorepo до тисяч розробників? **A:** Google вирішує це через Piper (внутрішня VCS, що обробляє 1 мільйон комітів на день), path-based ACL і Bazel remote execution (RBE), який розподіляє кроки збірки по кластеру. Для менших масштабів Nx Cloud або Turborepo remote caching покривають основне вузьке місце. ## Приклади ### Шерінг утиліти через Turborepo ```json // apps/web/package.json { "dependencies": { "@my-company/shared-utils": "workspace:*" } } ``` ```ts // apps/web/src/api.ts import { formatDate } from '@my-company/shared-utils'; const display = formatDate(new Date()); // "2024-01-15T00:00:00.000Z" ``` ```ts // packages/shared-utils/src/date.ts export const formatDate = (d: Date): string => d.toISOString(); ``` Коли `formatDate` змінюється, Turborepo виявляє зміну, пропускає `apps/api` (не зачеплений) і перебудовує тільки `apps/web`. Cache hit для `api` економить 40-60 секунд на типовій збірці. Без npm publish, без бампу версій, без координації. ### Координація в polyrepo через semantic versioning Та ж зміна `formatDate` в polyrepo виглядає так: ```bash # В репо my-company-ui git commit -m "fix: formatDate returns ISO string" npm version patch # бамп до 1.0.1 npm publish # публікація в npm registry # В репо my-company-web (окремий PR, окремий CI-запуск) npm install @my-company/shared-utils@1.0.1 git commit -m "chore: bump shared-utils to 1.0.1" ``` Два репо, два PR, два CI-запуски. Якщо `web` і `mobile` обидва споживають `shared-utils`, це три скоординовані зміни заради одного виправлення. Цей coordination cost зростає лінійно з кількістю споживачів. ### Контроль доступу в monorepo через CODEOWNERS ``` # .github/CODEOWNERS # UI-команда ревʼює всі зміни спільних компонентів /libs/shared-ui/ @ui-team # Бекенд-команда ревʼює API /apps/api/ @backend-team # Platform-команда ревʼює інфраструктуру /infra/ @platform-team ``` PR, що торкається `libs/shared-ui`, автоматично запитує ревʼю від `@ui-team`. Ніхто поза командою не може замерджити ці зміни без ревʼю. Модель більш гранулярна, ніж дозволи на рівні репо в polyrepo. Побоювання що "monorepo = всі бачать все і можуть все" приходить від команд, які просто не налаштували CODEOWNERS.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.