Monorepo vs Multi-repo
Giriş — Bir Şehir Planlaması Problemi
Bir şehir düşünün. Bu şehirde birçok farklı bina var: hastane, okul, alışveriş merkezi, belediye binası. Şimdi iki farklı planlama yaklaşımı hayal edin:
Yaklaşım 1 (Multi-repo): Her bina kendi arsasında, kendi bahçesiyle, kendi güvenlik sistemiyle bağımsız duruyor. Binalar arası iletişim yollarla sağlanıyor. Her binanın kendi yöneticisi, kendi kuralları var.
Yaklaşım 2 (Monorepo): Tüm binalar tek bir büyük kampüs içinde. Ortak altyapı (elektrik, su, internet) merkezi. İç yollarla bağlantılı. Tek bir yönetim kurulu.
Yazılım dünyasında da kodunuzu organize etmenin bu iki temel yolu var: Monorepo ve Multi-repo. Her ikisinin de güçlü yanları ve zorlukları var. Bu derste, her iki yaklaşımı derinlemesine inceleyeceğiz, ayrıca Git'in bu yapıları destekleyen araçlarını (submodules, subtree) ve modern monorepo araçlarını (Turborepo, Nx) öğreneceğiz.
Multi-Repo: Geleneksel Yaklaşım
Yapı
Multi-repo yaklaşımında her proje, servis veya paket kendi bağımsız Git repository'sine sahiptir:
GitHub Organization: acme-corp
├── acme-frontend (React app)
├── acme-backend (Node.js API)
├── acme-mobile (React Native)
├── acme-shared-ui (Ortak UI components)
├── acme-auth-service (Authentication microservice)
├── acme-payment-service (Payment microservice)
├── acme-infrastructure (Terraform configs)
└── acme-docs (Documentation)Her repo bağımsız:
Kendi
package.jsonveyapom.xmlKendi CI/CD pipeline'ı
Kendi versiyonlama (semver)
Kendi contributor'ları ve izinleri
Avantajları
✅ Bağımsız deploy — Her servis kendi hızında ilerler
✅ Temiz izinler — Frontend ekibi backend'e erişmek zorunda değil
✅ Küçük repo boyutu — Clone hızlı, CI hızlı
✅ Bağımsız versiyonlama — Her paket kendi semver'ini takip eder
✅ Teknoloji özgürlüğü — Her repo farklı dil/framework kullanabilir
✅ Basit Git geçmişi — Her repo sadece kendi geçmişini taşırDezavantajları
❌ Kod paylaşımı zor — Ortak kod ayrı repo'da publish edilmeli
❌ Cross-repo değişiklik — Bir API değişikliği 5 repo'yu etkiler
❌ Dependency yönetimi — Versiyon uyumsuzlukları (diamond dependency)
❌ Tutarsız tooling — Her repo farklı linter, formatter kullanabilir
❌ Keşfedilebilirlik — Yeni gelen "bu kod nerede?" diye kaybolur
❌ Atomic commit yok — İlişkili değişiklikleri tek commit'te yapamazsınızNe Zaman Tercih Edilir?
Microservice mimarisi (bağımsız deploy önemli)
Farklı ekipler farklı teknolojiler kullanıyor
Açık kaynak projeler (her paket bağımsız kullanılabilir)
Çok büyük organizasyonlar (100+ geliştirici)
Monorepo: Modern Yaklaşım
Yapı
Monorepo'da tüm projeler tek bir Git repository'sinde yaşar:
acme-corp/
├── apps/
│ ├── frontend/ (React app)
│ ├── backend/ (Node.js API)
│ ├── mobile/ (React Native)
│ └── admin-panel/ (Admin dashboard)
├── packages/
│ ├── ui/ (Ortak UI components)
│ ├── utils/ (Utility functions)
│ ├── config/ (Shared configs)
│ └── types/ (TypeScript type definitions)
├── services/
│ ├── auth/ (Auth microservice)
│ └── payment/ (Payment microservice)
├── infrastructure/
│ ├── terraform/
│ └── docker/
├── package.json (Root workspace)
├── turbo.json (Turborepo config)
└── nx.json (veya Nx config)Avantajları
✅ Kod paylaşımı kolay — import '../packages/ui' ve bitti
✅ Atomic commit — İlişkili değişiklikler tek commit'te
✅ Tutarlı tooling — Herkes aynı linter, formatter, test framework
✅ Tek CI/CD — Merkezi pipeline, tutarlı deploy
✅ Keşfedilebilirlik — Her şey tek yerde, grep ile bulursunuz
✅ Dependency yönetimi — Tek node_modules, tek versiyon
✅ Refactoring kolaylığı — Cross-project rename/refactor tek adımdaDezavantajları
❌ Büyük repo boyutu — Clone yavaşlayabilir (ama sparse checkout var)
❌ Karmaşık CI — Her PR için tüm testleri çalıştırmak verimsiz
❌ İzin kontrolü zor — GitHub repo seviyesinde izin verir, klasör seviyesinde değil
❌ Git performansı — 100K+ commit, 10K+ dosya olunca yavaşlar
❌ Öğrenme eğrisi — Monorepo araçlarını öğrenmek gerekir
❌ Bağımlılık zinciri — Bir paketin değişimi hepsini tetikleyebilirKimler Kullanıyor?
Google → Tek monorepo, 2 milyar+ satır kod, 86TB
Meta → Monorepo (Mercurial-based, Sapling)
Microsoft → Windows monorepo (Git VFS)
Uber → Go monorepo
Airbnb → JavaScript monorepo
Vercel → Turborepo (kendi ürünleri)💡 İpucu: Google'ın monorepo'su o kadar büyük ki, standart Git ile yönetilemez. Kendi geliştirdikleri Piper isimli özel bir versiyon kontrol sistemi kullanırlar. Ancak orta ölçekli projeler (birkaç yüz geliştirici) için Git + monorepo araçları gayet iyi çalışır.
Monorepo Araçları
Turborepo
Vercel'in geliştirdiği Turborepo, JavaScript/TypeScript monorepo'ları için hız odaklı bir build sistemidir. Temel prensibi: aynı işi iki kez yapma.
# Kurulum
npx create-turbo@latest my-monorepo
# Oluşan yapı
my-monorepo/
├── apps/
│ ├── web/ (Next.js app)
│ └── docs/ (Documentation)
├── packages/
│ ├── ui/ (Shared components)
│ ├── eslint-config/ (Shared ESLint)
│ └── typescript-config/ (Shared tsconfig)
├── turbo.json
└── package.jsonturbo.json konfigürasyonu:
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"test": {
"dependsOn": ["build"]
},
"lint": {},
"dev": {
"cache": false,
"persistent": true
}
}
}Turborepo'nun süper gücü: Remote Caching
# İlk çalıştırma — her şeyi build eder
$ npx turbo build
• Packages in scope: @acme/web, @acme/docs, @acme/ui
• Running build in 3 packages
✓ @acme/ui build 2.1s
✓ @acme/docs build 4.3s
✓ @acme/web build 8.7s
# İkinci çalıştırma — cache'den gelir (0.1s!)
$ npx turbo build
• Packages in scope: @acme/web, @acme/docs, @acme/ui
✓ @acme/ui build 200ms >>> FULL TURBO
✓ @acme/docs build 200ms >>> FULL TURBO
✓ @acme/web build 200ms >>> FULL TURBOBuild Süresi Karşılaştırma:
Turbo olmadan: ████████████████████████████ 45 dk
İlk Turbo build: ████████████████ 15 dk
Cache hit: █ 0.5 dkNx
Nrwl'ın geliştirdiği Nx, daha kapsamlı bir monorepo çözümüdür. Angular ekosisteminden doğmuştur ama artık her framework'ü destekler:
# Nx workspace oluştur
npx create-nx-workspace@latest my-org
# Proje ekle
nx generate @nx/react:application frontend
nx generate @nx/node:application backend
nx generate @nx/react:library shared-ui
# Build (sadece etkilenen projeleri build eder)
nx affected --target=build
# Dependency graph görselleştirme
nx graphNx'in farkı: Akıllı Task Çalıştırma
# Sadece değişen projeleri test et
nx affected --target=test
# Proje bağımlılık grafiğini göster
nx graphDependency Graph (nx graph):
┌──────────┐
│ frontend │
└────┬─────┘
│ depends on
┌────▼─────┐ ┌──────────┐
│ shared-ui│◄────│ mobile │
└────┬─────┘ └────┬─────┘
│ │
┌────▼────────────────▼──┐
│ utils │
└────────────────────────┘Turborepo vs Nx Karşılaştırması
Özellik │ Turborepo │ Nx
─────────────────┼──────────────────┼──────────────────
Kurulum │ Çok kolay │ Orta
Öğrenme eğrisi │ Düşük │ Yüksek
Hız │ Çok hızlı │ Hızlı
Code generation │ Yok │ Güçlü
Plugin ekosistemi│ Minimal │ Zengin
Dil desteği │ JS/TS │ Çoklu dil
Konfigürasyon │ Minimal │ Kapsamlı
CI entegrasyonu │ İyi │ Çok iyi (Nx Cloud)⚠️ Dikkat: Monorepo aracı seçerken "en popüler" yerine "ekibinize en uygun" olanı seçin. Küçük bir ekip ve basit bir proje için Turborepo'nun minimal yaklaşımı idealdir. Büyük, çok dilli bir organizasyon için Nx'in kapsamlı tooling'i daha mantıklıdır.
Git Submodules — Repo İçinde Repo
Konsept
Git submodules, bir Git repository'sinin içinde başka bir Git repository'sini referans olarak tutmanıza olanak tanır. Bunu bir kitaptaki dipnot gibi düşünün: ana metin sizin, ama başka bir kaynağa referans veriyorsunuz.
my-project/ (ana repo)
├── src/
├── lib/
│ └── shared-utils/ (submodule → ayrı repo'yu gösterir)
├── vendor/
│ └── payment-sdk/ (submodule → ayrı repo'yu gösterir)
├── .gitmodules (submodule konfigürasyonu)
└── package.jsonSubmodule Ekleme
# Submodule ekle
git submodule add https://github.com/acme/shared-utils.git lib/shared-utils
# Ne oldu?
# 1. lib/shared-utils/ dizinine repo klonlandı
# 2. .gitmodules dosyası oluşturuldu
# 3. .git/config'e submodule kaydedildi
# .gitmodules içeriği:
cat .gitmodules[submodule "lib/shared-utils"]
path = lib/shared-utils
url = https://github.com/acme/shared-utils.gitSubmodule ile Çalışma
# Repo'yu submodule'larıyla birlikte klonla
git clone --recurse-submodules https://github.com/acme/my-project.git
# Zaten klonladıysanız submodule'ları ayrıca çekin
git submodule init
git submodule update
# veya tek komutla
git submodule update --init --recursive
# Submodule'ı güncelle (en son versiyona)
cd lib/shared-utils
git checkout main
git pull
cd ../..
git add lib/shared-utils
git commit -m "chore: update shared-utils submodule"
# Tüm submodule'ları güncelle
git submodule update --remote --mergeSubmodule'ın Kritik Detayı
Submodule, bir commit SHA'ya "pin"lenir. Yani submodule'un ana repo'sundaki son commit'i değil, sizin belirttiğiniz commit'i kullanır:
# Ana repo'daki submodule referansını göster
git submodule status
# Çıktı:
# abc1234def567890 lib/shared-utils (v2.3.1)
# ↑ Bu specific commit'e pin'lenmiş
# Submodule'u yeni bir commit'e güncellemek explicit action gerektirir
cd lib/shared-utils
git pull origin main
cd ..
git add lib/shared-utils
git commit -m "chore: bump shared-utils to latest"Submodule'ın Zorlukları
⚠️ Dikkat Edilmesi Gerekenler:
1. git clone → Submodule'lar otomatik gelmez (--recurse-submodules gerekli)
2. Branch değiştirme → Submodule stale kalabilir
3. Merge conflict → Submodule conflict'leri çözmek karmaşık
4. CI/CD → Pipeline'da submodule init adımı gerekli
5. Yeni geliştiriciler → Sıkça "boş klasör" sorunu yaşarGit Subtree — Daha Basit Alternatif
Konsept
Git subtree, başka bir repo'nun kodunu kendi repo'nuzun bir parçası olarak içe aktarır. Submodule'dan farkı: kod gerçekten sizin repo'nuza kopyalanır, ayrı bir repo referansı değildir.
Şöyle düşünün: Submodule bir "kısayol" iken, subtree bir "kopyala-yapıştır ama bağlantıyı koru" yaklaşımıdır.
# Subtree ekle
git subtree add --prefix=lib/shared-utils \
https://github.com/acme/shared-utils.git main --squash
# Bu komut:
# 1. shared-utils repo'sunun main branch'ini çeker
# 2. lib/shared-utils/ altına kopyalar
# 3. --squash: tüm geçmişi tek commit'e sıkıştırırSubtree Güncelleme
# Upstream'den güncelleme çek
git subtree pull --prefix=lib/shared-utils \
https://github.com/acme/shared-utils.git main --squash
# Kendi değişikliklerini upstream'e gönder
git subtree push --prefix=lib/shared-utils \
https://github.com/acme/shared-utils.git feature/my-improvementRemote Ekleyerek Kullanım (Tavsiye Edilen)
Her seferinde URL yazmak yerine remote ekleyin:
# Remote ekle
git remote add shared-utils https://github.com/acme/shared-utils.git
# Artık daha kısa komutlar
git subtree add --prefix=lib/shared-utils shared-utils main --squash
git subtree pull --prefix=lib/shared-utils shared-utils main --squash
git subtree push --prefix=lib/shared-utils shared-utils mainSubmodule vs Subtree Karşılaştırması
Özellik │ Submodule │ Subtree
───────────────────┼────────────────────────┼────────────────────────
Kod nerede? │ Ayrı repo (referans) │ Ana repo'da (kopyalanmış)
Clone │ --recurse-submodules │ Normal clone yeter
.gitmodules │ Gerekli │ Gerekli değil
Güncelleme │ Manuel (explicit) │ git subtree pull
Contributor deneyim│ Karmaşık │ Basit (fark görmez)
Bağımlılık boyutu │ Küçük (referans) │ Büyük (kod dahil)
Offline çalışma │ Submodule init gerekir │ Her zaman erişilebilirNe Zaman Hangisi?
# Submodule tercih edin:
# - Bağımlılık çok büyükse (tüm kodu klonlamak gereksiz)
# - Bağımlılık sık güncelleniyorsa ve specific versiyona pin'lemek istiyorsanız
# - Bağımlılık birden fazla projede kullanılıyorsa
# Subtree tercih edin:
# - Takım submodule ile tecrübesizse
# - Bağımlılığı modifiye edip upstream'e geri göndermek istiyorsanız
# - Basitlik öncelikse
# - Bağımlılık küçüksenpm/pnpm/yarn Workspaces
Modern JavaScript ekosisteminde, monorepo genellikle paket yöneticisi workspace'leri ile yapılır:
pnpm Workspaces (Önerilen)
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'// package.json (root)
{
"name": "acme-monorepo",
"private": true,
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"lint": "turbo lint"
}
}// apps/web/package.json
{
"name": "@acme/web",
"dependencies": {
"@acme/ui": "workspace:*",
"@acme/utils": "workspace:*"
}
}# Workspace'leri kullanma
pnpm install # Tüm workspace'ler için bağımlılıkları kur
pnpm --filter @acme/web dev # Sadece web app'i çalıştır
pnpm --filter @acme/ui build # Sadece UI paketini build et
pnpm -r run test # Tüm workspace'lerde test çalıştırYarn Workspaces
// package.json (root)
{
"private": true,
"workspaces": ["apps/*", "packages/*"]
}npm Workspaces
// package.json (root)
{
"private": true,
"workspaces": ["apps/*", "packages/*"]
}npm run build --workspaces # Tüm workspace'lerde build
npm run test --workspace=@acme/ui # Belirli workspace'te testMonorepo CI/CD Stratejisi
Monorepo'da CI/CD'nin en büyük zorluğu: her PR için tüm projeleri build/test etmek verimsizdir. Çözüm: etkilenen projeleri tespit etmek.
GitHub Actions ile Etkilenen Projeleri Tespit
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
web: ${{ steps.changes.outputs.web }}
api: ${{ steps.changes.outputs.api }}
ui: ${{ steps.changes.outputs.ui }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
web:
- 'apps/web/**'
api:
- 'apps/api/**'
ui:
- 'packages/ui/**'
build-web:
needs: detect-changes
if: needs.detect-changes.outputs.web == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pnpm --filter @acme/web build
build-api:
needs: detect-changes
if: needs.detect-changes.outputs.api == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pnpm --filter @acme/api build
# Shared package değiştiyse, onu kullanan tüm projeler etkilenir
build-dependents:
needs: detect-changes
if: needs.detect-changes.outputs.ui == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pnpm --filter ...@acme/ui build # ui ve tüm dependentsTurborepo ile CI
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Turbo'nun diff yapabilmesi için tam geçmiş
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
cache: 'pnpm'
# Turbo remote cache
- run: pnpm turbo build test lint --filter=...[origin/main]
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}💡 İpucu:
--filter=...[origin/main]ifadesi "main branch'ten bu yana değişen paketleri ve onlara bağımlı olan paketleri" anlamına gelir. Bu, CI süresini dramatik olarak kısaltır.
Gerçek Dünya Karar Matrisi
Proje durumunuza göre hangi yaklaşımı seçmelisiniz?
Durum │ Önerilen Yaklaşım
─────────────────────────────────────────┼──────────────────────
Startup, 3-5 kişi, tek ürün │ Monorepo (Turborepo)
Startup, farklı teknolojiler │ Multi-repo
Orta şirket, 10-30 dev, paylaşılan UI │ Monorepo (Nx)
Büyük şirket, 100+ dev, microservices │ Multi-repo + ortak lib'ler
Açık kaynak kütüphane ailesi │ Monorepo (Lerna/Turborepo)
Freelancer, bağımsız projeler │ Multi-repo
Tek ürün, frontend + backend + mobile │ Monorepo
Farklı müşteriler, farklı projeler │ Multi-repoYaygın Hatalar
1. Monorepo'da Her Şeyi Build Etmek
# ❌ Yanlış — her PR'da her şeyi build/test et
npm run build # 45 dakika...
# ✅ Doğru — sadece etkilenen projeleri build et
npx turbo build --filter=...[HEAD~1]
# veya
nx affected --target=build2. Submodule'ları Unutmak
# ❌ Clone sonrası "boş klasör var!" panikleme
git clone my-repo
ls lib/shared-utils/ # Boş!
# ✅ Submodule'ları da çek
git clone --recurse-submodules my-repo
# veya
git submodule update --init --recursive3. Monorepo'da İzin Karmaşası
# GitHub repo seviyesinde izin verir, klasör seviyesinde değil
# ❌ "Frontend ekibi backend klasörünü değiştiremesin" → GitHub'da yapılamaz
# ✅ Çözüm: CODEOWNERS dosyası
# .github/CODEOWNERS
/apps/frontend/ @frontend-team
/apps/backend/ @backend-team
/packages/ui/ @design-system-team
# Bu sayede ilgili ekip review zorunluluğu getirirÖzet
Bu derste proje organizasyonunun iki temel yaklaşımını karşılaştırdık:
📂 Multi-repo — bağımsız deploy, temiz izinler ama kod paylaşımı zor
📦 Monorepo — kolay paylaşım, tutarlı tooling ama karmaşık CI/CD
⚡ Turborepo — JavaScript monorepo'lar için hızlı, minimal, cache odaklı
🏗️ Nx — kapsamlı monorepo çözümü, code generation, plugin ekosistemi
🔗 Git Submodules — repo içinde repo referansı, specific commit'e pin
🌳 Git Subtree — kodu repo'ya kopyalar, daha basit ama daha büyük repo
🎯 Karar kriteri — ekip büyüklüğü, teknoloji çeşitliliği ve deploy stratejisi belirler
Bir sonraki derste, ileri düzey merge conflict stratejilerini ve conflict'leri tamamen önleme tekniklerini öğreneceğiz.
AI Asistan
Sorularını yanıtlamaya hazır