Trunk Based Development: Tek Dal Stratejisi ile Hızlı Yazılım Geliştirme

Yıllarca feature branch modeliyle çalıştım. Her şey güzeldi, ta ki üç haftalık bir branch’i main’e merge etmeye çalışana kadar. O merge konflikti cehennemini yaşayan biri olarak söylüyorum: Trunk Based Development’ı ilk duyduğumda “bu nasıl çalışır ki?” diye sormuştum. Şimdi ise geri dönmem imkansız.

Trunk Based Development (TBD), tüm geliştiricilerin tek bir ana dal üzerinde çalıştığı, uzun ömürlü branch’lerin minimize edildiği bir versiyon kontrol stratejisidir. Ama bu tanım biraz kuru kalıyor. Gerçekte TBD, bir disiplin ve bir kültür meselesidir. Gün içinde birden fazla commit, küçük değişiklikler, sürekli entegrasyon ve herkesin aynı kod tabanı üzerinde çalışması demektir.

Neden Feature Branch Modeli Yorar?

Klasik Gitflow veya feature branch modelinde şu senaryoyu kaç kez yaşadınız: Bir geliştirici iki hafta boyunca kendi branch’inde çalışıyor. Main branch bu sürede 200 commit ilerledi. Merge zamanı geldiğinde ortaya çıkan konflikt sayısı, yapılan işin kendisinden daha uzun sürede çözülüyor.

Buna “merge hell” deniyor. Ama daha sinsi bir problemi var: entegrasyon gecikmesi. Kodunuzu ne kadar uzun süre main’den izole tutarsanız, entegrasyon o kadar pahalıya mal olur. Risk birikir, geri bildirim gecikilir, hatalar geç ortaya çıkar.

Google, Facebook, Microsoft gibi şirketlerin monorepo ve trunk based yaklaşıma geçişi rastlantısal değil. Araştırmalar da bunu destekliyor: DORA metriklerinde yüksek performanslı ekiplerin büyük çoğunluğu TBD uyguluyor.

Trunk Based Development’ın Temel Prensipleri

TBD’yi anlamak için birkaç temel prensibi özümsemek gerekiyor:

  • Tek main branch: “trunk” veya “main” adı verilen tek bir uzun ömürlü dal
  • Kısa ömürlü branch’ler: Eğer branch açılacaksa, maksimum 1-2 günlük ömrü olmalı
  • Sık commit ve push: Gün içinde en az bir kez main’e entegrasyon
  • Feature Flag’ler: Tamamlanmamış özellikler, flag’lerle gizlenerek main’e dahil edilir
  • CI her zaman yeşil: Main branch her zaman deploy edilebilir durumda olmalı

Bu prensiplerin her biri birbiriyle bağlantılı. Sık commit yapabilmek için küçük değişiklikler gerekir. Küçük değişiklikler için feature flag’ler gerekir. Feature flag’ler için solid bir CI/CD pipeline gerekir.

Git Yapılandırması ve İlk Kurulum

TBD’ye geçerken ilk adım repository yapılandırmasını doğru kurmaktır. Main branch’i koruma altına almak, doğrudan push’ları kısıtlamak ve otomatik merge politikaları belirlemek bu aşamada yapılır.

# Main branch'i varsayılan olarak ayarla
git config --global init.defaultBranch main

# Local repository'de main branch oluştur
git checkout -b main
git push -u origin main

# Branch protection kuralı (GitHub CLI ile)
gh api repos/:owner/:repo/branches/main/protection 
  --method PUT 
  --field required_status_checks='{"strict":true,"contexts":["ci/tests","ci/lint"]}' 
  --field enforce_admins=true 
  --field required_pull_request_reviews='{"required_approving_review_count":1}' 
  --field restrictions=null

GitHub veya GitLab kullanmıyorsanız, pre-receive hook ile de koruma sağlayabilirsiniz:

# .git/hooks/pre-receive dosyası
#!/bin/bash
while read oldrev newrev refname; do
  if [ "$refname" = "refs/heads/main" ]; then
    # Main'e doğrudan push'u engelle
    if [ "$oldrev" != "0000000000000000000000000000000000000000" ]; then
      echo "HATA: Main branch'e doğrudan push yapılamaz."
      echo "Lütfen kısa ömürlü bir branch açıp PR/MR kullanın."
      exit 1
    fi
  fi
done

Günlük TBD Workflow’u

TBD’deki günlük çalışma ritmi feature branch’ten oldukça farklı. Sabah main’i çekiyorsunuz, küçük bir değişiklik yapıyorsunuz, testleri çalıştırıyorsunuz ve push ediyorsunuz. Bu döngü gün içinde birkaç kez tekrar ediyor.

# Güne başlarken: main'i güncelle
git fetch origin
git rebase origin/main

# Küçük bir değişiklik yap, test et
git add -p  # Parça parça staging, toplu değil
git commit -m "feat: kullanici profil endpointi rate limiting eklendi"

# Push etmeden önce tekrar rebase (başkası push etmiş olabilir)
git fetch origin
git rebase origin/main
git push origin main

Burada git add -p kullanımına dikkat edin. TBD’de commit’lerinizin atomik ve anlamlı olması kritik. Toplu git add . yerine değişikliklerinizi gözden geçirerek staging yapın.

Çakışma olduğunda ise:

# Rebase sırasında çakışma çıktığında
git rebase origin/main
# CONFLICT mesajı gelirse:
git status  # hangi dosyalar çakışıyor?
# Çakışmaları düzelt
git add <çakışan_dosya>
git rebase --continue

# Eğer vazgeçmek istersen:
git rebase --abort

Feature Flag Implementasyonu

TBD’nin en kritik parçası feature flag’ler. Tamamlanmamış bir özelliği main’e almak riskli görünür, ama flag’lerle bu riski tamamen ortadan kaldırabilirsiniz.

Basit bir feature flag implementasyonu:

# config/features.env dosyası (production'da tüm flagler kapalı)
FEATURE_NEW_DASHBOARD=false
FEATURE_BETA_API=false
FEATURE_DARK_MODE=true

Python örneği olsa da shell script ile de flag kontrolü yapabilirsiniz:

#!/bin/bash
# feature_check.sh

check_feature() {
  local feature_name="$1"
  local config_file="/etc/app/features.conf"
  
  if [ ! -f "$config_file" ]; then
    echo "false"
    return
  fi
  
  local value=$(grep "^${feature_name}=" "$config_file" | cut -d'=' -f2 | tr -d '[:space:]')
  
  if [ "$value" = "true" ] || [ "$value" = "1" ] || [ "$value" = "enabled" ]; then
    echo "true"
  else
    echo "false"
  fi
}

# Kullanım
if [ "$(check_feature 'NEW_DASHBOARD')" = "true" ]; then
  echo "Yeni dashboard aktif, yeni kodu çalıştır"
  # yeni_dashboard_kodu
else
  echo "Eski dashboard aktif"
  # eski_dashboard_kodu
fi

Feature flag’ler sayesinde geliştirici yeni dashboard kodunu main’e gönderebilir, ama production’da flag kapalı olduğu için kullanıcılar görmez. Test ortamında flag açılarak test edilir, hazır olunca production’da da açılır.

CI/CD Pipeline Entegrasyonu

TBD’nin çalışması için CI/CD pipeline’ı sağlam olmalı. Her commit main’e geldiğinde otomatik testler, lint kontrolü ve deployment pipeline’ı tetiklenmelidir.

# .github/workflows/main.yml (GitHub Actions)
# Bu dosyayı repository'nize ekleyin

name: TBD CI Pipeline

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

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Lint kontrolü
        run: |
          # Shell scriptler için shellcheck
          find . -name "*.sh" -exec shellcheck {} ;
          
      - name: Unit testler
        run: |
          make test-unit
          
      - name: Integration testler
        run: |
          make test-integration
          
      - name: Deployment (sadece main branch'te)
        if: github.ref == 'refs/heads/main'
        run: |
          make deploy-staging

GitLab kullanıyorsanız:

# .gitlab-ci.yml
stages:
  - lint
  - test
  - deploy

variables:
  TRUNK_BRANCH: "main"

lint-job:
  stage: lint
  script:
    - shellcheck scripts/*.sh
    - yamllint .
  rules:
    - if: '$CI_COMMIT_BRANCH == $TRUNK_BRANCH'
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

test-job:
  stage: test
  script:
    - make test
  coverage: '/Coverage: d+.d+/'

deploy-staging:
  stage: deploy
  script:
    - ./deploy.sh staging
  rules:
    - if: '$CI_COMMIT_BRANCH == $TRUNK_BRANCH'
      when: on_success

Kısa Ömürlü Branch Yönetimi

Evet, TBD’de de branch açabilirsiniz. Ama bu branch’ler maksimum bir iki gün yaşamalı. Uzayan branch’ler için uyarı sistemi kurmak iyi bir pratik.

#!/bin/bash
# stale_branch_checker.sh
# Eski branch'leri tespit et ve raporla

REPO_DIR="${1:-.}"
MAX_DAYS="${2:-2}"
WARNING_THRESHOLD=$((MAX_DAYS * 24 * 3600))

cd "$REPO_DIR" || exit 1

echo "=== Stale Branch Raporu (${MAX_DAYS} günden eski) ==="
echo ""

git fetch --all --quiet

git for-each-ref --format='%(refname:short) %(authordate:unix) %(authorname)' 
  refs/remotes/origin/ | 
while read -r branch_name commit_time author_name; do
  # main ve HEAD'i atla
  [[ "$branch_name" =~ "origin/main" ]] && continue
  [[ "$branch_name" =~ "HEAD" ]] && continue
  
  current_time=$(date +%s)
  age=$((current_time - commit_time))
  age_days=$((age / 86400))
  
  if [ "$age" -gt "$WARNING_THRESHOLD" ]; then
    branch_clean="${branch_name#origin/}"
    echo "UYARI: '$branch_clean' branch'i $age_days gündür güncellenmedi (Yazar: $author_name)"
  fi
done

Bu script’i cron job olarak çalıştırın ve Slack/Teams’e bildirim gönderin:

# crontab -e
# Her gün sabah 9'da stale branch kontrolü
0 9 * * 1-5 /opt/scripts/stale_branch_checker.sh /var/repos/myproject 2 | 
  /opt/scripts/slack_notify.sh "#dev-alerts"

Gerçek Dünya Senaryosu: E-ticaret Projesi Geçişi

Bir e-ticaret projesinde TBD’ye geçiş sürecini anlatarayım. Ekip 8 geliştirici, Gitflow kullanıyordu. Haftalık release döngüsü vardı ve her release öncesi birleştirme süreci iki günü alıyordu.

TBD’ye geçişte önce küçük başladık:

# Eski feature branch'leri main ile sync et
git checkout feature/payment-refactor
git fetch origin
git rebase origin/main

# Değişiklikleri küçük parçalara böl
# git log ile commit'leri incele
git log --oneline feature/payment-refactor ^origin/main

# Her commit'i ayrı ayrı main'e al (cherry-pick veya rebase)
git checkout main
git cherry-pick abc123  # sadece hazır olan kısımlar

İlk iki haftada en büyük direnç “ama özellik yarım kaldı, nasıl main’e alacağız?” sorusundan geldi. Cevap feature flag’lerdeydi. Ödeme refactor’ünün yeni kodu flag arkasına alındı:

# deploy script'inde feature flag kontrolü
#!/bin/bash
deploy_payment_module() {
  local env="$1"
  
  if [ "$env" = "production" ]; then
    PAYMENT_FLAG=$(aws ssm get-parameter 
      --name "/myapp/features/new_payment" 
      --query 'Parameter.Value' 
      --output text 2>/dev/null || echo "false")
    
    if [ "$PAYMENT_FLAG" = "true" ]; then
      echo "Yeni ödeme modülü deploy ediliyor..."
      deploy_new_payment
    else
      echo "Eski ödeme modülü deploy ediliyor..."
      deploy_legacy_payment
    fi
  fi
}

Üçüncü haftada ekip ritmi oturdı. Altıncı haftada release öncesi birleştirme süreci sıfıra düştü. Çünkü main her zaman deploy edilebilir durumdaydı.

Commit Mesajı Standartları

TBD’de commit mesajları daha da kritik çünkü her commit doğrudan main history’sine gidiyor. Conventional Commits standardını zorunlu kılmak iyi bir pratik.

# commit-msg hook'u
# .git/hooks/commit-msg
#!/bin/bash

commit_msg=$(cat "$1")
pattern="^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(([a-z-]+))?: .{1,100}$"

if ! echo "$commit_msg" | grep -qE "$pattern"; then
  echo ""
  echo "HATA: Commit mesajı formatı yanlış!"
  echo ""
  echo "Format: <tip>(<kapsam>): <açıklama>"
  echo ""
  echo "Geçerli tipler: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert"
  echo ""
  echo "Örnek: feat(auth): JWT token yenileme mekanizması eklendi"
  echo "Örnek: fix(payment): null pointer exception düzeltildi"
  echo ""
  exit 1
fi

Bu hook’u tüm ekibe dağıtmak için:

#!/bin/bash
# setup_hooks.sh - Yeni developer onboarding script'i

HOOKS_DIR=".git/hooks"
SCRIPTS_DIR="scripts/git-hooks"

echo "Git hook'ları yapılandırılıyor..."

for hook in "$SCRIPTS_DIR"/*; do
  hook_name=$(basename "$hook")
  cp "$hook" "$HOOKS_DIR/$hook_name"
  chmod +x "$HOOKS_DIR/$hook_name"
  echo "Hook kuruldu: $hook_name"
done

echo ""
echo "TBD workflow kurulumu tamamlandı."
echo "Lütfen docs/tbd-guide.md dosyasını okuyun."

Yaygın Sorunlar ve Çözümleri

TBD’ye geçişte karşılaşılan sorunların başında “incomplete feature” korkusu geliyor. Geliştiriciler yarım kalan kodu main’e almaktan çekiniyor. Bunun çözümü feature flag olmakla birlikte, kod tasarımının da bu yaklaşımı desteklemesi gerekiyor.

İkinci büyük sorun CI süresinin uzaması. Her commit’te tetiklenen CI, eğer 20-30 dakika sürüyorsa iş akışını yavaşlatır. Çözüm:

# Paralel test çalıştırma
# Makefile örneği
test-parallel:
	@echo "Testler paralel çalıştırılıyor..."
	@mkdir -p test-results
	
	# Unit testleri arka planda başlat
	make test-unit > test-results/unit.log 2>&1 &
	UNIT_PID=$$!
	
	# Integration testleri arka planda başlat  
	make test-integration > test-results/integration.log 2>&1 &
	INT_PID=$$!
	
	# Her iki işlemin bitmesini bekle
	wait $$UNIT_PID
	UNIT_EXIT=$$?
	
	wait $$INT_PID
	INT_EXIT=$$?
	
	# Sonuçları değerlendir
	if [ $$UNIT_EXIT -ne 0 ] || [ $$INT_EXIT -ne 0 ]; then
		echo "HATA: Testler başarısız!"
		cat test-results/unit.log
		cat test-results/integration.log
		exit 1
	fi
	
	echo "Tüm testler başarılı."

Üçüncü yaygın sorun ise hotfix senaryoları. TBD’de hotfix nasıl yapılır? Ana branch her zaman deploy edilebilir durumda olduğu için, hotfix doğrudan main’e yapılır ve hemen deploy edilir. Release branch’i tutanlar için ise cherry-pick kullanılır.

Sonuç

Trunk Based Development, teoride basit görünür. Pratikte ise ekip disiplini, sağlam CI/CD altyapısı ve feature flag kültürü gerektirir. Geçiş acı verici olabilir, özellikle uzun süredir feature branch modeline alışmış ekiplerde.

Ama şunu söyleyeyim: TBD’ye geçtikten sonra merge konflikti cehenneminden kurtuldunuz, release süreçleri sadeleşti ve deploy sıklığınız arttıysa, bu acıya değdi demektir. Yüksek frekanslı entegrasyon, hataları erken yakalıyor. Küçük değişiklikler, rollback’i kolaylaştırıyor. Main’in her zaman yeşil olması, ekibe güven veriyor.

Başlamak için büyük bir dönüşüme gerek yok. Şu an üzerinde çalıştığınız projeye küçük adımlarla başlayın: commit boyutlarını küçültün, CI sürenizi kısaltın ve bir sonraki feature için flag implementasyonunu deneyin. Bir ay sonra farkı kendiniz göreceksiniz.

Bir yanıt yazın

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