API tasarımında sürümleme (versioning) kararları
İstemcileri kırmadan bir API'yi nasıl evrimleştirirsiniz? Sürümleme stratejilerini trade-off'larıyla karşılaştırıyor, gerçek deneyimlerimi paylaşıyorum.
Bir API yayınladıktan sonra dünyaya “bu sözleşme değişmeyecek” demiş gibi oluyorsunuz. Değiştirirseniz, sizi kullanan istemciler kırılıyor. Değiştirmezseniz, tasarım hatalarıyla sonsuza dek yaşıyorsunuz. API versioning, bu gerilimi yönetmenin yolu — ama “doğru” bir yolu yok; trade-off’lardan birini seçmek var.
Neden sürümleme gerekli?
Bir kaynağın yanıt yapısını değiştirmeniz gerektiğini düşünelim. Kullanıcı objesi şu an şöyle dönüyor:
{
"id": 1,
"name": "Muhammet Şafak",
"created_at": "2019-01-15T10:00:00Z"
}
Yeni versiyonda name yerine first_name ve last_name ayrı alanlara bölünecek. Bunu tek sürümde değiştirirseniz, eski name alanını kullanan her istemci anında kırılır. Sürümleme, eski ve yeni yapının bir süre yan yana yaşamasına olanak tanır.
Üç yaygın strateji
URI sürümlemesi
GET /api/v1/users
GET /api/v2/users
En yaygın yaklaşım. İstemcinin isteğe bakarak hangi sürümde olduğunu anlamak çok kolay; log’larda, proxy kurallarında, dokümantasyonda net görünüyor. Dezavantajı: URL’leri çoğaltıyor ve “her kaynak için v1/v2 mi ayırmalıyım?” sorusunu doğuruyor.
Header sürümlemesi
GET /api/users
Accept: application/vnd.myapi.v2+json
veya özel bir header:
X-API-Version: 2
URL temiz kalıyor. Ama istemci bu header’ı eksik gönderirse ne olur? Varsayılan sürüm döner mi? Hata mı verilir? Bu belirsizlik, sözleşmenin bir parçası haline geliyor.
Query parametresi
GET /api/users?version=2
Pratik bir başlangıç noktası ama uzun vadede bakımı zor. Cache davranışı tahmin edilmesi zor; proxy’ler ve CDN’ler query parametresini görmeyebilir.
Ben ne tercih ediyorum?
Birkaç projede URI sürümlemesine yerleştim: /api/v1/, /api/v2/. Gerekçelerim net:
-
Okunabilirlik. Hangi sürümü çağırdığınız URL’de açıkça görünüyor. Hata ayıklarken log satırına bakıp anlamak kolay.
-
Dokümantasyon sınırı. Her sürümün kendi dokümantasyonu ayrı tutulabilir. “Bu endpoint v2’de nasıl davranıyor?” sorusunu yanıtlamak için sürümü bulmak gerekmez.
-
Dağıtım esnekliği. v1 ve v2’yi ayrı controller’lara ya da rota gruplarına yönlendirebilirsiniz. Kod tabanını iç içe geçirmek zorunda kalmıyorsunuz.
Header tabanlı sürümlemenin teorik zarafeti var ama pratikte istemcileri yanlış yapılandırılmış header ile karşılaştığımda sorunu bulmak daha uzun sürüyor.
Geriye dönük uyumlu değişiklikler
Her değişiklik sürüm gerektirmez. Backward-compatible değişiklikler mevcut sürümde yapılabilir:
- Yanıta yeni bir alan eklemek.
- İsteğe bağlı bir parametre eklemek.
- Hata mesajlarını zenginleştirmek.
Breaking change’ler ise sürüm gerektirir:
- Var olan bir alanı kaldırmak veya yeniden adlandırmak.
- Zorunlu bir parametre eklemek.
- Yanıt yapısını temel düzeyde değiştirmek.
- Durum kodunu değiştirmek.
Eski sürümleri ne zaman kaldırırsınız?
Bu sorunun yanıtı istemci tabanınıza göre değişiyor. İç kullanım için bir API’de v1’i bırakmak ve v2’ye geçişi hızlıca zorunlu kılmak mümkün. Dış istemcilere açık bir API’de en az 6-12 aylık bir deprecation süreci makul.
Deprecation sürecini iletmek için HTTP başlıklarını kullanabilirsiniz:
Deprecation: true
Sunset: Sun, 31 Jan 2021 23:59:59 GMT
Sunset başlığı RFC 8594 ile tanımlanıyor (Mayıs 2019, Informational): bir kaynağın gelecekte belirli bir anda yanıt vermez hâle geleceğini bildiriyor. Tarayıcılar veya istemci kütüphaneleri bunu kendiliğinden işlemese de, istemci geliştiricilere bildirim olarak anlamlı.
Yukarıdaki Deprecation: true satırı için bugünden bir not düşmek gerekiyor. Bu yazıyı yazdığım sırada başlık yalnız bir IETF taslağıydı ve taslak Deprecation: true biçimini örnek olarak veriyordu. Taslak Mart 2025’te RFC 9745 olarak yayımlandı ve değer tipi değişti: Deprecation artık bir Item Structured Header ve değeri bir Date olmak zorunda. Yani bugün doğru yazımı şöyle:
Deprecation: @1780272000
Sunset: Sat, 31 Oct 2026 23:59:59 GMT
Link: <https://api.example.com/docs/migrate-v2>; rel="deprecation"; type="text/html"
true değeri artık geçersiz. İki başlık iki ayrı şeyi söylüyor: Deprecation ne zaman kullanımdan kaldırıldığını, Sunset ne zaman yanıt vermeyeceğini.
API sürümlemesinde mükemmel bir strateji yok. Kendi projelerimde URL tabanlı sürümlemeyi tercih ediyorum; çünkü hem istemci geliştirici için en görünür yöntem hem de bir isteğe bakıp hangi sürümde olduğunu anlamak kolay. Header tabanlı sürümleme daha zarif görünse de, hata ayıklarken görünmez kalması beni birkaç kez yordu. Hangi yöntemi seçerseniz seçin, önemli olan onu tutarlı uygulamak ve istemcilerinizle net iletişim kurmak — bir sürüm stratejisi ancak öngörülebilir olduğunda işe yarar.
Kaynaklar
Bu konuda sorulanlar
Public API'mde bir endpoint'i kullanımdan kaldırırken sunset sürecini nasıl yönetmeliyim?
Kullanımı partner bazında ölçün, her yanıta Sunset ve Deprecation başlığı koyun, tarihli sunset duyurup brownout yapın, kullanım sıfıra yaklaşınca kesin.
Bu konudaki deneyler
Swipenor: skoru istemci hesaplarsa sunucu neye güvenir?
Kaydırmalı doğru/yanlış bilgi yarışması; asıl mesele oyun değil, istemcinin ürettiği skoru imzayla doğrulanabilir kılmak.
Şu an ne yapıyor
Kaydırmalı doğru/yanlış bilgi yarışması bir mobil uygulama olarak çekirdek döngüsüyle uçtan uca çalışıyor; skoru istemci üretse de server_nonce ve HMAC imzasıyla sunucu tarafında doğrulanıyor. swipenor.com uygulamanın tanıtım sayfasıdır, oyunun kendisi değil.
Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.