← Kursa Dön
📄 Text · 30 min

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.json veya pom.xml

  • Kendi 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şır

Dezavantajları

❌ 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ız

Ne 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ımda

Dezavantajları

❌ 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 tetikleyebilir

Kimler 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.json

turbo.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 TURBO
Build Süresi Karşılaştırma:

Turbo olmadan:     ████████████████████████████  45 dk
İlk Turbo build:   ████████████████             15 dk
Cache hit:         █                             0.5 dk

Nx

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 graph

Nx'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 graph
Dependency 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.json

Submodule 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.git

Submodule 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 --merge

Submodule'ı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şar

Git 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ır

Subtree 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-improvement

Remote 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 main

Submodule 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şilebilir

Ne 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üçükse

npm/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ır

Yarn 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 test

Monorepo 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 dependents

Turborepo 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-repo

Yaygı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=build

2. 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 --recursive

3. 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.