API Versiyonlama Stratejileri: URI, Header ve Query Parametresi Yöntemleri

Bir API’yi production’a aldıktan sonra “bunu değiştirmem lazım ama mevcut client’ları kırmadan nasıl yaparım?” sorusuyla karşılaşmayan sysadmin ya da geliştirici tanımıyorum. Bu soru, API versiyonlama stratejilerinin tam kalbinde yatıyor. Yanlış bir strateji seçmek; kırık entegrasyonlar, öfkeli müşteriler ve gece yarısı incident’ları anlamına geliyor. Doğru strateji seçmek ise sisteminizi yıllarca sağlıklı tutabiliyor.

API Versiyonlama Neden Bu Kadar Önemli?

Bir REST API’yi düşündüğünüzde, bu API’yi kullanan onlarca belki yüzlerce farklı client var: mobil uygulamalar, üçüncü parti entegrasyonlar, kendi iç servisleriniz. Bunların hepsi farklı hızlarda güncelleniyor. Mobil uygulama kullanıcısının telefonu belki 2 yıl önce güncellenmiş bir versiyonu çalıştırıyor. Kurumsal bir partner firmanın entegrasyonu ise IT departmanının onayıyla ancak 6 ayda bir güncellenebiliyor.

Siz bu arada API’nizi geliştirmeye, endpoint’lere yeni alanlar eklemeye, eski hatalı tasarımları düzeltmeye devam etmek istiyorsunuz. İşte tam burada versiyonlama devreye giriyor.

Üç ana strateji var: URI versiyonlama, Header versiyonlama ve Query Parameter versiyonlama. Her birinin güçlü ve zayıf yanları var, hangi senaryoda hangisini kullanacağınıza birlikte bakalım.

URI Versiyonlama

En yaygın kullanılan yöntem bu. Versiyon numarasını doğrudan URL’e gömdüğünüz için hem anlaşılması hem de uygulanması en kolay strateji.

# URI versiyonlama örnekleri
GET https://api.sirketiniz.com/v1/users
GET https://api.sirketiniz.com/v2/users
GET https://api.sirketiniz.com/v1/orders/123
GET https://api.sirketiniz.com/v2/orders/123

Nginx ile URI Versiyonlama Yönlendirmesi

Bir sysadmin olarak bu stratejiyi production’da uygulamak için genellikle reverse proxy katmanında yönlendirme yapıyoruz. Nginx ile bunu şöyle kurabilirsiniz:

# /etc/nginx/sites-available/api.conf

server {
    listen 443 ssl;
    server_name api.sirketiniz.com;

    # V1 trafiğini eski servise yönlendir
    location /v1/ {
        proxy_pass http://api-v1-backend:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-API-Version "1";
    }

    # V2 trafiğini yeni servise yönlendir
    location /v2/ {
        proxy_pass http://api-v2-backend:8081/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-API-Version "2";
    }

    # Versiyonsuz istekleri v2'ye yönlendir (varsayılan)
    location /api/ {
        return 301 https://$host/v2$request_uri;
    }
}

Bu yapı sayesinde v1 ve v2 tamamen bağımsız backend servislerinde çalışabiliyor. Birini güncellerken diğerini etkilemiyorsunuz.

URI Versiyonlamanın Güçlü Yanları

  • Görünürlük: URL’e bakınca hangi versiyonu kullandığınızı anında görüyorsunuz. Debug yaparken, log incelerken, dokümantasyon yazarken büyük kolaylık.
  • Cache dostu: CDN ve proxy’ler URL bazlı cache yapıyor. /v1/users ve /v2/users farklı cache entry’leri olduğu için sorun çıkmıyor.
  • Kolay test: Tarayıcınızdan veya curl ile direkt test edebilirsiniz, özel header eklemenize gerek yok.
  • Bookmarkable: API dokümantasyonu linkleri, Postman koleksiyonları, her şey URL tabanlı çalışıyor.

URI Versiyonlamanın Zayıf Yanları

  • REST saflığı sorunu: URI kaynağı tanımlamalı, versiyonu değil. /v2/users aslında “users” kaynağını değil, “v2’deki users” kaynağını tanımlıyor. Akademik tartışma gibi görünse de büyük API tasarımlarında tutarsızlığa yol açabiliyor.
  • URL karmaşası: Zamanla /v1/, /v2/, /v3/ birikmeye başlıyor ve eski versiyonları ne zaman deprecated edeceğiniz sorusu ciddi operasyonel yük oluşturuyor.
  • Kod duplikasyonu riski: Farklı versiyonlar için ayrı route tanımları yazarken kendinizi aynı kodu tekrar eden bir hale getirebilirsiniz.

Header Versiyonlama

Bu yöntemde versiyon bilgisini HTTP header’ında gönderiyorsunuz. URL temiz kalıyor, versiyon bilgisi isteğin metadata’sında yaşıyor.

# Custom header ile versiyonlama
curl -H "X-API-Version: 2" 
     -H "Authorization: Bearer token123" 
     https://api.sirketiniz.com/users

# Accept header ile versiyonlama (Content Negotiation)
curl -H "Accept: application/vnd.sirketiniz.v2+json" 
     -H "Authorization: Bearer token123" 
     https://api.sirketiniz.com/users

# Media type versiyonlama
curl -H "Accept: application/json; version=2" 
     https://api.sirketiniz.com/users

Nginx ile Header Bazlı Yönlendirme

Header versiyonlamayı Nginx’te uygulamak biraz daha dikkat gerektiriyor:

# /etc/nginx/sites-available/api-header-versioning.conf

map $http_x_api_version $api_backend {
    default     "http://api-v2-backend:8081";
    "1"         "http://api-v1-backend:8080";
    "2"         "http://api-v2-backend:8081";
    "3"         "http://api-v3-backend:8082";
}

server {
    listen 443 ssl;
    server_name api.sirketiniz.com;

    location / {
        # Versiyon header yoksa varsayılan v2'ye git
        proxy_pass $api_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-API-Version $http_x_api_version;
        
        # Cache bypass - header bazlı routing cache'i karıştırır
        proxy_cache_bypass $http_x_api_version;
        add_header Vary "X-API-Version" always;
    }
}

Burada dikkat edilmesi gereken kritik nokta: Vary header’ı. Cache’in header değerine göre farklı response’ları saklaması için bunu eklemeniz şart. Aksi halde v1 isteği yapan bir client, cache’deki v2 response’unu alabilir.

Accept Header ile Content Negotiation

GitHub API’sinin kullandığı bu yöntem daha “RESTful” kabul ediliyor:

# GitHub tarzı versiyonlama
curl -H "Accept: application/vnd.github.v3+json" 
     -H "Authorization: token ghp_xxxxxxxxxxxx" 
     https://api.github.com/repos/kullanici/repo

# Kendi API'niz için benzer yapı
curl -H "Accept: application/vnd.sirketiniz.v2+json" 
     -H "Authorization: Bearer eyJhbGc..." 
     https://api.sirketiniz.com/users/profile

Header Versiyonlamanın Güçlü Yanları

  • Temiz URL’ler: https://api.sirketiniz.com/users her zaman aynı. Kaynak URI’si sabit kalıyor.
  • REST uyumluluğu: Kaynağı temsil eden URL değişmiyor, sadece representation versiyonu değişiyor.
  • Esnek geçiş: Client’lar sadece header’ı değiştirerek versiyonu değiştirebiliyor.

Header Versiyonlamanın Zayıf Yanları

  • Görünürlük sorunu: Log’lara baktığınızda hangi versiyonun istek attığını görmek için header’ları parse etmeniz gerekiyor. Nginx log formatınızı buna göre düzenlemeniz şart.
  • Cache karmaşıklığı: CDN ve proxy’lerin header bazlı cache yapılandırması çok daha karmaşık. Yanlış yapılandırmada ciddi güvenlik açıkları çıkabiliyor.
  • Test zorluğu: Tarayıcıdan direkt test edemezsiniz. Her zaman Postman, curl veya benzeri araç gerekiyor.
  • Dokümantasyon zorluğu: Yeni başlayan geliştiriciler için öğrenme eğrisi dik.

Query Parameter Versiyonlama

Üçüncü yöntemde versiyon bilgisi URL’in query string kısmında yer alıyor:

# Query parameter versiyonlama
GET https://api.sirketiniz.com/users?version=2
GET https://api.sirketiniz.com/users?v=1&page=1&limit=20
GET https://api.sirketiniz.com/orders/123?api_version=2

# Gerçek dünya örneği - Stripe tarzı
GET https://api.sirketiniz.com/payments?version=2023-10-15

Nginx ile Query Parameter Yönlendirmesi

# /etc/nginx/conf.d/api-query-versioning.conf

# Query parameter'ı okuyup map ile backend seçimi
map $arg_version $qp_backend {
    default     "http://api-v2-backend:8081";
    "1"         "http://api-v1-backend:8080";
    "2"         "http://api-v2-backend:8081";
}

server {
    listen 443 ssl;
    server_name api.sirketiniz.com;

    location /api/ {
        proxy_pass $qp_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        # Versiyon bilgisini log'a yaz
        access_log /var/log/nginx/api_access.log combined;
    }
}

Log Formatını Versiyon Bilgisini İçerecek Şekilde Düzenleme

Hangi stratejiyi kullanırsanız kullanın, versiyon bilgisini log’lara düşürmeniz operasyonel hayatınızı kurtarır:

# /etc/nginx/nginx.conf - log_format bölümü

http {
    log_format api_detailed '$remote_addr - $remote_user [$time_local] '
                            '"$request" $status $body_bytes_sent '
                            '"$http_referer" "$http_user_agent" '
                            'api_version="$http_x_api_version" '
                            'query_version="$arg_version" '
                            'accept="$http_accept" '
                            'response_time="$request_time"';

    server {
        access_log /var/log/nginx/api_access.log api_detailed;
    }
}

Bu log formatıyla grep api_version="1" /var/log/nginx/api_access.log | wc -l komutuyla kaç tane v1 isteği geldiğini görebilir, eski versiyonu ne zaman deprecated edeceğinize dair veri toplayabilirsiniz.

Gerçek Dünya Senaryosu: E-ticaret API Migrasyonu

Diyelim ki bir e-ticaret şirketinde çalışıyorsunuz. V1 API’nizde sipariş response’u şu şekilde dönüyor:

# V1 sipariş response
curl -X GET https://api.magazaniz.com/v1/orders/12345 
     -H "Authorization: Bearer token"

# Dönen response:
# {
#   "order_id": 12345,
#   "customer_name": "Ahmet Yilmaz",
#   "total": 250.00,
#   "status": "shipped"
# }

V2’de müşteri bilgisini ayrı bir objeye taşıdınız, fiyat bilgisi değişti ve yeni alanlar geldi. Bu breaking change. Eski mobile app’ler customer_name alanını okuyarak çalışıyor, bunu customer.full_name yaparsanız tüm eski uygulamalar bozulur.

# V2 sipariş response - breaking change içeriyor
curl -X GET https://api.magazaniz.com/v2/orders/12345 
     -H "Authorization: Bearer token"

# Yeni response:
# {
#   "order_id": 12345,
#   "customer": {
#     "id": 789,
#     "full_name": "Ahmet Yilmaz",
#     "email": "[email protected]"
#   },
#   "pricing": {
#     "subtotal": 220.00,
#     "tax": 30.00,
#     "total": 250.00,
#     "currency": "TRY"
#   },
#   "status": "shipped",
#   "tracking_number": "TR123456789"
# }

Bu senaryoda URI versiyonlama en güvenli tercih. Çünkü hem eski client’lar /v1/orders/12345 çağırmaya devam ediyor, hem siz /v2/ altında istediğiniz değişiklikleri yapabiliyorsunuz. İki backend paralel çalışıyor, geçiş süreci aylarca sürebiliyor.

Hangi Stratejiyi Seçmeli?

Teorik tartışmaları bir kenara bırakıp pratik karar kriterleri üzerinden gidelim.

URI versiyonlamayı tercih edin eğer:

  • Public API yayınlıyorsanız ve üçüncü parti geliştiriciler kullanacaksa
  • CDN veya agressive caching kullanıyorsanız
  • Takımınızda junior geliştiriciler de var ve anlaşılırlık önemliyse
  • Farklı versiyonları tamamen farklı altyapılarda çalıştırma ihtiyacınız varsa
  • API’niz sık sık breaking change içeriyorsa

Header versiyonlamayı tercih edin eğer:

  • Dahili API ve tüm client’larınızı kontrol ediyorsanız
  • REST standartlarına strict uymak önemliyse
  • URL’lerin temiz kalması kritik bir gereksinimse
  • API Gateway altyapınız var ve header routing kolay yapılabiliyorsa

Query parameter versiyonlamayı tercih edin eğer:

  • Geliştirme ve test ortamları için hızlı geçiş yapmak istiyorsanız
  • Webhook endpoint’leri gibi URL’nin sabit kalması gereken senaryolarda
  • Legacy sistemlerle entegrasyon yapıyor ve header manipülasyonu mümkün değilse

Stripe’ın yaklaşımı aslında hibrit ve çok akıllıca: API anahtarı ile hesap oluşturulduğunda o günün tarihsel versiyonu “sabitlenir”. Siz /v1/ tarzı URI kullanırsınız ama arka planda her API anahtarının bir “created_at” versiyonu vardır. Breaking change yaparsanız eski key’ler otomatik eski davranışı almaya devam eder.

Versiyonlama Politikası ve Deprecation

Strateji seçmekten daha zor olan kısım: eski versiyonları nasıl ve ne zaman kaldıracaksınız? Birkaç pratik kural:

  • Deprecation süresi minimum 6 ay olmalı: Kurumsal client’lar için bu bile bazen kısa. 12 ay daha gerçekçi.
  • Deprecation header ekleyin: Response’lara Deprecation: true ve Sunset: Sat, 01 Jun 2025 00:00:00 GMT header’ları ekleyin. Client geliştiriciler bunu okuyabilir.
  • Usage tracking zorunlu: Log’lardan hangi client’ın hangi versiyonu kullandığını takip etmeden deprecation yapamazsınız.
  • Direkt iletişim: Analitiklerinizden eski versiyon kullanan partner’ları tespit edip direkt e-posta gönderin.
# Deprecation header ekleme - Nginx
location /v1/ {
    proxy_pass http://api-v1-backend:8080/;
    
    # Deprecation uyarısı ekle
    add_header Deprecation "true" always;
    add_header Sunset "Sat, 01 Jun 2025 00:00:00 GMT" always;
    add_header Link '<https://api.sirketiniz.com/v2/>; rel="successor-version"' always;
    
    # Eski versiyon kullanımını özel log'a yaz
    access_log /var/log/nginx/v1_deprecated_usage.log api_detailed;
}

Bu log dosyasını düzenli olarak analiz ederek henüz geçiş yapmamış client’ları tespit edebilirsiniz.

API Gateway ile Gelişmiş Versiyonlama

Eğer Kong, AWS API Gateway veya benzer bir API Gateway kullanıyorsanız, versiyonlama politikalarını merkezi olarak yönetebilirsiniz:

# Kong ile rate limiting ve versiyonlama örneği
# V1 için daha düşük rate limit - geçişi teşvik etmek için
curl -X POST http://localhost:8001/services 
  --data name=api-v1 
  --data url=http://api-v1-backend:8080

curl -X POST http://localhost:8001/services/api-v1/plugins 
  --data name=rate-limiting 
  --data config.minute=100 
  --data config.policy=local

# V2 için daha yüksek rate limit - geçişi teşvik
curl -X POST http://localhost:8001/services 
  --data name=api-v2 
  --data url=http://api-v2-backend:8081

curl -X POST http://localhost:8001/services/api-v2/plugins 
  --data name=rate-limiting 
  --data config.minute=1000 
  --data config.policy=local

Bu yaklaşımla eski versiyonu kullanmayı ekonomik olarak caydırıcı hale getirebilirsiniz. V1 kullanıcıları 100 istek/dakika limiti ile çalışırken, V2’ye geçenler 1000 istek/dakika alıyor. Teşvik mekanizması olarak oldukça etkili.

Sonuç

API versiyonlama kararı, sadece teknik bir tercih değil; aynı zamanda operasyonel bir taahhüt. Hangi stratejiyi seçerseniz seçin, tutarlı olun. Bir API’de URI, başka bir API’de header kullanmak, client geliştiricileri çıldırtır.

Yıllar içinde edindiğim pratik sonuç şu: Public API’ler için URI versiyonlama, dahili servisler için header versiyonlama çoğu senaryoda iyi çalışıyor. Query parameter ise özel durumlar için, özellikle webhook’lar ve legacy entegrasyonlar için saklayın.

En önemli nokta ise stratejiden bağımsız: versiyonlamanın operasyonel kısmını ciddiye alın. Log’larınızı düzgün tutun, deprecation header’larını ekleyin, kullanım metriklerini takip edin. Teknik altyapıyı kurmak bir günlük iş, eski versiyonları yönetmek ise aylarca süren bir operasyonel süreç.

API’nizi bir kez tasarlayıp bırakmıyorsunuz; onunla birlikte yaşıyorsunuz. Bu gerçeği kabul eden bir versiyonlama stratejisi, gece yarısı incident’larını minimize eden stratejidir.

Bir yanıt yazın

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