Monorepo Stratejisi: Tek Depoda Çoklu Proje Yönetimi

Yıllar önce bir startup’ta çalışırken, ekibin yarısı “şu servisi neden ben deploy ettim ama UI güncellenmedi” diye şikayet ederdi. Diğer yarısı “ben kendi repomu merge ettim, sen neden benim değişikliklerimi bilmiyorsun” derdi. O zamanlar her projenin ayrı bir Git reposunda yaşadığı klasik polyrepo düzenindeydi her şey. Monorepo kavramını öğrendiğimde sanki eksik bir puzzle parçası yerine oturdu. Ama konuya girmeden önce şunu söyleyeyim: monorepo sihirli bir çözüm değil. Doğru uygulanmazsa polyrepo’dan daha büyük bir kaos yaratabilir.

Monorepo Nedir, Ne Değildir?

Monorepo, birden fazla projeyi, servisi veya paketi tek bir Git deposunda barındırma stratejisidir. Google, Meta, Microsoft gibi devlerin yıllardır kullandığı bu yaklaşım, özellikle mikroservis mimarilerine geçişle birlikte daha fazla ilgi görmeye başladı.

Yaygın bir yanlış anlama var: monorepo demek monolitik uygulama demek değildir. Tek bir depoda onlarca bağımsız servis, kütüphane ve uygulama barındırabilirsiniz. Bağımsızlık mantıksal seviyede devam eder, fiziksel olarak aynı repoda yaşarlar.

Bir diğer yanlış anlama da “her şeyi tek bir klasöre koy, bitti” düşüncesi. Monorepo’nun işe yaraması için ciddi bir klasör yapısı, CI/CD disiplini ve takım anlaşması gerekir.

Klasör Yapısı: Temeli Doğru Atmak

Monorepo’nun kalbi klasör yapısıdır. Burada yapılan hatalar ileride geri dönüşü olmayan acılar yaratır. Genel olarak iki popüler yaklaşım var:

Uygulama odaklı yapı:

mycompany-monorepo/
├── apps/
│   ├── web-frontend/
│   ├── admin-panel/
│   ├── mobile-api/
│   └── worker-service/
├── packages/
│   ├── shared-ui/
│   ├── auth-lib/
│   ├── logger/
│   └── config/
├── infra/
│   ├── terraform/
│   ├── k8s/
│   └── docker/
├── scripts/
│   ├── build-all.sh
│   ├── lint-changed.sh
│   └── deploy.sh
└── .github/
    └── workflows/

Servis odaklı yapı (mikroservis ağırlıklı ekipler için):

mycompany-monorepo/
├── services/
│   ├── auth-service/
│   │   ├── src/
│   │   ├── Dockerfile
│   │   └── package.json
│   ├── payment-service/
│   └── notification-service/
├── libs/
│   ├── common-utils/
│   └── database-helpers/
└── tools/
    └── generators/

Ben genellikle ikinci yaklaşımı tercih ediyorum çünkü her servisin kendi Dockerfile ve bağımlılık yönetimi dosyasına sahip olması, CI/CD pipeline’larını çok daha temiz tutuyor.

Branch Stratejisi: Monorepo’nun En Kritik Noktası

İşte burada çoğu ekibin tökezlediği yer. Polyrepo’da her proje kendi branch stratejisini uygulayabilirdi. Monorepo’da tek bir strateji herkese uygulanır, bu da dikkatli düşünmeyi gerektirir.

Trunk-Based Development (TBD) monorepo ile en uyumlu strateji olarak öne çıkıyor. Ana fikir şu: herkes doğrudan main branch’e küçük, sık commit’ler atar ya da çok kısa ömürlü feature branch’ler kullanır.

# Yeni özellik için kısa ömürlü branch oluşturma
git checkout -b feat/payment-service-retry-logic

# Değişiklikler yapılır
git add services/payment-service/src/retry.ts
git commit -m "feat(payment-service): add exponential backoff for failed payments"

# Ana branch güncellendi mi kontrol et
git fetch origin main
git rebase origin/main

# Push ve PR aç - 24-48 saat içinde merge edilmeli
git push origin feat/payment-service-retry-logic

Burada kritik bir nokta var: commit mesajlarına hangi servisi etkilediğini belirtmek. Bu, hem kod incelemesini kolaylaştırır hem de değişikliği otomatik olarak tespit eden CI araçları için gereklidir.

Gitflow gibi karmaşık branch stratejileri monorepo’da kaos yaratır. On servis, beş ekip ve her birinin ayrı develop, release, hotfix branch’leri olduğunu hayal edin. Bu senaryoyu yaşayan bir müşteride danışmanlık yapıyordum; repo’da 200’den fazla aktif branch vardı ve kimse neyin nerede olduğunu bilmiyordu.

Değişiklik Tespiti: Sadece Etkilenen Servisi Build Et

Monorepo’nun en büyük avantajlarından biri, her commit’te sadece değişen parçaları tespit edip onları build etme imkanı. Bunu yapmadan monorepo korkunç bir deneyime dönüşür; her küçük değişiklikte 15 servisin tamamını build etmek zorunda kalırsınız.

#!/bin/bash
# scripts/detect-changes.sh
# Son commit veya PR'da hangi servisler değişti?

CHANGED_SERVICES=()
BASE_BRANCH=${BASE_BRANCH:-"origin/main"}

# Değişen dosyaları al
CHANGED_FILES=$(git diff --name-only $BASE_BRANCH...HEAD)

# Her servis için değişiklik var mı kontrol et
for service_dir in services/*/; do
    service_name=$(basename "$service_dir")
    if echo "$CHANGED_FILES" | grep -q "^services/$service_name/"; then
        CHANGED_SERVICES+=("$service_name")
        echo "Değişiklik tespit edildi: $service_name"
    fi
done

# Paylaşılan kütüphaneler değiştiyse tüm servisleri işaretle
if echo "$CHANGED_FILES" | grep -q "^libs/"; then
    echo "Paylaşılan kütüphane değişti - tüm servisler etkilendi"
    CHANGED_SERVICES=("all")
fi

export CHANGED_SERVICES

Bu script’i GitHub Actions, GitLab CI veya Jenkins’te kullanarak gereksiz build’lerin önüne geçebilirsiniz.

CI/CD Pipeline: Servis Bazlı Akıllı Tetikleme

GitHub Actions ile monorepo’ya özel bir pipeline örneği verelim:

# .github/workflows/ci.yml
name: Monorepo CI

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      auth-service: ${{ steps.changes.outputs.auth-service }}
      payment-service: ${{ steps.changes.outputs.payment-service }}
      shared-libs: ${{ steps.changes.outputs.shared-libs }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      
      - uses: dorny/paths-filter@v2
        id: changes
        with:
          filters: |
            auth-service:
              - 'services/auth-service/**'
              - 'libs/**'
            payment-service:
              - 'services/payment-service/**'
              - 'libs/**'
            shared-libs:
              - 'libs/**'

  build-auth-service:
    needs: detect-changes
    if: needs.detect-changes.outputs.auth-service == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build Auth Service
        working-directory: services/auth-service
        run: |
          npm ci
          npm run build
          npm test

  build-payment-service:
    needs: detect-changes
    if: needs.detect-changes.outputs.payment-service == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build Payment Service
        working-directory: services/payment-service
        run: |
          npm ci
          npm run build
          npm test

dorny/paths-filter action’ı bu iş için mükemmel. Hangi dizinlerde değişiklik olduğunu tespit ediyor ve buna göre sadece ilgili job’ları çalıştırıyor.

Paylaşılan Kod Yönetimi ve Bağımlılık Grafiği

Monorepo’nun en güçlü tarafı paylaşılan kod yönetimi. Ama bu aynı zamanda en dikkatli olunması gereken alan. Bir shared-utils kütüphanesinde yaptığınız breaking change, bağlı olan tüm servisleri etkiler.

# Bağımlılık grafiğini görselleştirme (Node.js ekosistemi için)
# package.json'da workspace kullanıyorsanız

# libs/auth-lib/package.json
{
  "name": "@mycompany/auth-lib",
  "version": "1.2.0",
  "main": "dist/index.js"
}

# services/payment-service/package.json
{
  "name": "payment-service",
  "dependencies": {
    "@mycompany/auth-lib": "workspace:*",
    "@mycompany/logger": "workspace:*"
  }
}
# Hangi servisler auth-lib'i kullanıyor?
grep -r "auth-lib" services/*/package.json

# Bu kütüphaneyi değiştirmeden önce tüm bağımlı servisleri test et
#!/bin/bash
CHANGED_LIB="auth-lib"
echo "$CHANGED_LIB bağımlı servisler:"
for service in services/*/; do
    if grep -q "@mycompany/$CHANGED_LIB" "$service/package.json" 2>/dev/null; then
        echo "  - $(basename $service)"
        # Bu servisi test et
        (cd "$service" && npm test)
    fi
done

Git Hooks ile Kalite Kontrolü

Monorepo’da herkesin aynı kalite standartlarına uyması için Git hook’ları vazgeçilmez. Commit mesajı formatını zorunlu kılmak, en basit ama etkili önlem:

#!/bin/bash
# .git/hooks/commit-msg
# Conventional Commits formatını zorunlu kıl

COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")

# Format: type(scope): description
# Scope: hangi servis veya kütüphane etkileniyor
PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf)(([a-z-]+)): .{10,}"

if ! echo "$COMMIT_MSG" | grep -qP "$PATTERN"; then
    echo "Hata: Commit mesajı formatı yanlış!"
    echo "Beklenen format: type(scope): açıklama"
    echo "Örnek: feat(auth-service): add OAuth2 support"
    echo "Örnek: fix(payment-service): handle timeout errors"
    exit 1
fi

echo "Commit mesajı formatı geçerli."

Bu hook’u ekip genelinde uygulamak için husky (Node.js) veya pre-commit (Python tabanlı, dil bağımsız) araçlarını kullanabilirsiniz.

Gerçek Dünya Senaryosu: E-ticaret Platformu

Somut bir örnek üzerinden gidelim. 8 mikroservisten oluşan bir e-ticaret platformunu monorepo’ya taşıdık. İlk hafta kaotikti, ama üç ay sonra şunları fark ettik:

Önce mevcut polyrepo durumunu analiz ettik:

# Eski polyrepo yapısı - her repo ayrıydı
# ecommerce-auth (repo 1)
# ecommerce-product (repo 2)  
# ecommerce-order (repo 3)
# ecommerce-payment (repo 4)
# ecommerce-frontend (repo 5)
# ecommerce-admin (repo 6)
# shared-types (repo 7) - burası KABUSTU

# Monorepo'ya taşıma script'i
#!/bin/bash
mkdir ecommerce-monorepo
cd ecommerce-monorepo
git init

# Her eski repo'yu kendi subdirectory'siyle import et
git subtree add --prefix=services/auth 
    [email protected]:company/ecommerce-auth.git main

git subtree add --prefix=services/product 
    [email protected]:company/ecommerce-product.git main

git subtree add --prefix=services/order 
    [email protected]:company/ecommerce-order.git main

# Git history korundu mu kontrol et
git log --oneline services/auth/ | head -5

git subtree komutu, eski repo’ların Git history’sini koruyarak yeni repoya ekler. Bu kritik; “bunu kim ne zaman yazdı” sorusuna cevap verebilmelisiniz.

Büyük Dosya Yönetimi ve .gitignore Stratejisi

Monorepo büyüdükçe repo boyutu sorun haline gelebilir. Özellikle node_modules, build ve dist dizinlerinin yanlışlıkla commit edilmesi felakettir:

# .gitignore - Monorepo için kapsamlı örnek

# Build çıktıları (tüm servisler için)
**/dist/
**/build/
**/.next/
**/__pycache__/
**/*.pyc

# Bağımlılıklar
**/node_modules/
**/.venv/
**/vendor/

# Servis bazlı environment dosyaları
services/**/.env
services/**/.env.local
!services/**/.env.example

# IDE
.idea/
.vscode/*
!.vscode/extensions.json
!.vscode/settings.json

# OS
.DS_Store
Thumbs.db

# Test coverage
**/coverage/
**/.nyc_output/

# Docker
**/docker-compose.override.yml

Büyük binary dosyalar için Git LFS kullanmak şart:

# Git LFS kurulumu
git lfs install

# Büyük dosya tiplerini LFS'e yönlendir
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "infra/terraform/**.tfstate"
git lfs track "assets/**/*.mp4"

# .gitattributes otomatik güncellendi, commit et
git add .gitattributes
git commit -m "chore: configure git lfs for large files"

Merge Stratejisi ve Code Review Süreci

Monorepo’da code review süreci dikkatli yapılandırılmazsa bottleneck yaratır. Her PR’a tüm ekibin bakması hem verimsiz hem pratik değil. Çözüm: CODEOWNERS dosyası.

# .github/CODEOWNERS

# Genel kurallar - varsayılan reviewer'lar
* @company/platform-team

# Servis bazlı sahiplik
services/auth-service/ @company/identity-team
services/payment-service/ @company/payments-team
services/order-service/ @company/orders-team

# Paylaşılan kütüphaneler - platform ekibi onayı şart
libs/ @company/platform-team

# Infrastructure değişiklikleri - senior onayı
infra/ @company/platform-team @senior-devops

# CI/CD değişiklikleri
.github/ @company/platform-team

Bu yapıyla payment-service‘e yapılan bir PR, otomatik olarak sadece payments ekibine assign edilir. Platform ekibini gereksiz yere rahatsız etmezsiniz.

Monorepo Araçları: Nx, Turborepo, Lerna

Eğer Node.js ekosistemindeyseniz bu araçlar hayat kurtarır:

# Nx kurulumu ve workspace başlatma
npx create-nx-workspace@latest mycompany --preset=empty

# Mevcut projeye Nx ekle
npx nx@latest init

# Sadece etkilenen projeleri test et (Nx'in güçlü yanı)
npx nx affected:test --base=origin/main

# Sadece etkilenen projeleri build et
npx nx affected:build --base=origin/main

# Bağımlılık grafiğini görselleştir
npx nx graph
# Turborepo ile monorepo yönetimi
# turbo.json
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": []
    },
    "lint": {
      "outputs": []
    }
  }
}

# Sadece değişen paketleri build et (cache kullanarak)
npx turbo run build --filter=[origin/main]

Turborepo’nun remote cache özelliği özellikle büyük ekiplerde inanılmaz fark yaratıyor. Bir developer’ın yaptığı build, diğerlerine cache’lenmiş olarak geliyor.

Sık Yapılan Hatalar

Gerçek projelerden derlediğim en sık karşılaşılan hatalar:

  • Her şeyi tek bir servis gibi geliştirmek: Monorepo fiziksel bir birleşim, mantıksal bir birleşim değil. Servisler birbirinin iç API’larına doğrudan erişmemeli.
  • Branch stratejisi belirlememek: “Herkes main’e atıyor” demek ile trunk-based development uygulamak farklı şeyler. Feature flag’ler olmadan TBD yapamazsınız.
  • CI/CD’yi optimize etmemek: Monorepo kurulur, her push’ta tüm sistem build edilir. İki saatlik pipeline süresi ekibi bunaltır.
  • CODEOWNERS kurmadan büyümek: 20 kişilik ekip olduktan sonra kurmaya çalışmak, her PR’ı kimin review edeceği tartışmasına dönüşür.
  • Paylaşılan kütüphane versiyonlamasını atlamak: workspace:* kullanmak esneklik sağlar ama breaking change’lerde tüm servisler birlikte güncellenmek zorunda kalır. Çözüm: semantic versioning ve changelog tutmak.

Sonuç

Monorepo, doğru uygulandığında takımlar arası koordinasyonu köklü biçimde iyileştiriyor. Paylaşılan kodun tek noktadan yönetilmesi, cross-service değişikliklerin tek bir PR’da görünmesi, bağımlılıkların şeffaf hale gelmesi gerçek avantajlar. Ama bu avantajları elde etmek için ödemeniz gereken bedel var: iyi bir klasör yapısı, disiplinli bir branch stratejisi ve akıllı CI/CD pipeline’ları.

Önerim şu: Eğer 3-5 kişilik bir ekipseniz ve 2-3 birbiriyle ilişkili servisiniz varsa, monorepo’yu bugün deneyebilirsiniz. 10 kişinin üzerindeyseniz ve ciddi bir büyüme beklentiniz varsa, Nx veya Turborepo gibi araçlara baştan yatırım yapın. Onlarca servisi olan büyük bir organizasyonsanız, önce pilot bir ekiple deneyin ve araçları olgunlaştırdıktan sonra geniş çapta yayın.

Monorepo’nun başarısı teknik kurulumdan çok takım disiplini ve anlaşmasına bağlı. Teknik kısmı zor değil; en zor olan kısım herkesin aynı kurallara uymasını sağlamak.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir