REST API Versiyonlama ve Geriye Dönük Uyumluluk Rehberi
'Bir alan ekledik, mobil uygulama çöktü.' API bir sözleşmedir ve tüketicilerini kontrol edemezsiniz. Hangi değişikliklerin kırıcı olduğunu, toleranslı okuyucu ilkesini, URI, başlık ve tarih bazlı versiyonlama stratejilerini, Spring Boot ile uygulamayı, Deprecation ve Sunset başlıklarıyla emekliye ayırma sürecini, sözleşme testlerini ve CI'da kırıcı değişiklik tespitini anlatıyoruz.
"Yanıta bir alan ekledik, bayinin entegrasyonu durdu." "Bir enum değeri ekledik, mobil uygulama çöktü." "Alan adını düzelttik, üç müşteri aynı gün aradı." API bir sözleşmedir ve sözleşmenin diğer tarafını — mobil uygulamalar, bayiler, iş ortakları, eski bir sürümde takılı kalan iç ekipler — kontrol edemezsiniz. Sürüm yönetimi bu yüzden bir URL biçimi tartışması değil, bir uyumluluk disiplinidir: neyi kırmadan değiştirebileceğinizi bilmek, kırmak zorunda kaldığınızda tüketiciye zaman ve yol vermek.
Bu yazıda kırıcı ve kırıcı olmayan değişiklikleri, toleranslı okuyucu ilkesini, URI, başlık ve tarih bazlı versiyonlama stratejilerini, Spring Boot ile uygulamayı, Deprecation ve Sunset başlıklarıyla emekliye ayırma sürecini, sözleşme testlerini ve CI'da kırıcı değişiklik tespitini anlatıyoruz.
Önce uyumluluk: ne kırar, ne kırmaz?
Değişiklik | Kırar mı? | Not |
|---|---|---|
Yanıta yeni (isteğe bağlı) alan eklemek | Hayır* | *Tüketici bilinmeyen alanı yok sayıyorsa (toleranslı okuyucu) |
İsteğe yeni isteğe bağlı parametre eklemek | Hayır | Varsayılan davranış değişmemeli |
Yeni uç nokta eklemek | Hayır | |
Alanı kaldırmak / yeniden adlandırmak | Evet | Yeni ad ekle, eskisini bir süre çift tut, sonra emekliye ayır |
Alan tipini/biçimini değiştirmek (string → number, tarih biçimi) | Evet | Yeni alan olarak ekle |
Enum'a yeni değer eklemek | Genellikle evet | Katı istemciler bilinmeyen değerde çöker; sözleşmede "yeni değerler gelebilir" yazılmalı ve istemciler hazırlanmalı |
Zorunlu istek alanı eklemek | Evet | İsteğe bağlı + varsayılan ile ekle |
Doğrulamayı sıkılaştırmak (uzunluk, biçim) | Evet | Dün geçen istek bugün 400 alıyorsa kırılmadır |
Hata kodlarını / durum kodlarını değiştirmek | Evet | İstemciler koda göre dallanır |
Varsayılan davranışı değiştirmek (sıralama, sayfa boyutu) | Evet | En sinsi kırılma: şema aynı, davranış farklı |
URL yapısını, kimlik doğrulamayı değiştirmek | Evet | Yeni ana sürüm |
İki ilke bu tabloyu yönetir. Toleranslı okuyucu: tüketici bilmediği alanları yok sayar, bilmediği enum değerine mantıklı bir varsayılanla davranır; sağlayıcı bunu sözleşmeye yazar. Spring tüketicisinde spring.jackson.deserialization.fail-on-unknown-properties=false (istemci tarafında) ve enum'lar için @JsonEnumDefaultValue. Yalnızca ekleyerek evrim: kaldırma ve yeniden adlandırma yerine ekleme; kaldırılacak olan önce "deprecated" işaretlenir, tarihle emekliye ayrılır. Bu iki ilkeyle çoğu API yıllarca tek ana sürümle yaşar.
Versiyonlama stratejileri
Strateji | Örnek | Artı | Eksi | Uygun |
|---|---|---|---|---|
URI yolu |
| Görünür, önbelleklenebilir, geçit yönlendirmesi kolay, tarayıcıda test edilebilir | URL "kaynağın kimliği" ilkesini bükler; tüm API'nin sürümü birlikte gider | Açık/ortak API'ler; varsayılan seçim |
Özel başlık |
| URL temiz; uç nokta bazında sürüm mümkün | Görünmez; önbellek ve geçit ayarı ister; tarayıcıdan test zor | İç API'ler, sık evrilen servisler |
İçerik pazarlığı |
| REST'e en uygun; temsil bazında sürüm | Karmaşık; istemci kütüphaneleri zorlanır | Hypermedia ağırlıklı API'ler |
Tarih bazlı |
| Sürekli küçük değişiklikler; istemci tarihini sabitler (Stripe modeli) | Sunucuda dönüşüm katmanları birikir; ciddi altyapı ister | Çok tüketicili, sürekli evrilen platform API'leri |
Sorgu parametresi |
| Basit | Kolay unutulur, önbellek anahtarına girer, kirli | Önerilmez |
Pratik öneri: dışa açık API'de URI'de yalnızca ana sürüm (v1, v2); ara sürüm yok — kırıcı olmayan değişiklikler sürüm numarası değiştirmeden eklenir. Yeni ana sürüm istisnadır, yılda bir bile fazladır; her ana sürüm, iki sürümü aynı anda yaşatma maliyetidir.
Spring Boot ile uygulama
Spring Framework 7 (Spring Boot 4) API sürüm desteğini çerçeveye aldı: sürüm, istek eşlemesinin bir parçasıdır ve yol parçası, başlık ya da sorgu parametresinden çözülür.
@Configuration
public class ApiSurumAyari implements WebMvcConfigurer {
@Override
public void configureApiVersioning(ApiVersionConfigurer c) {
c.useRequestHeader("API-Version") // ya da usePathSegment(1) → /api/v2/...
.addSupportedVersions("1", "2")
.setDefaultVersion("1") // başlık yoksa en eski desteklenen sürüm: eski istemciler kırılmaz
.setVersionRequired(false);
}
}
@RestController
@RequestMapping("/api/siparisler")
public class SiparisController {
@GetMapping(path = "/{id}", version = "1")
SiparisV1 getirV1(@PathVariable long id) { return SiparisV1.from(servis.getir(id)); } // eski şekil: musteriAdi düz string
@GetMapping(path = "/{id}", version = "2+") // 2 ve sonrası
SiparisV2 getirV2(@PathVariable long id) { return SiparisV2.from(servis.getir(id)); } // yeni şekil: musteri nesnesi, para birimi ayrı
}Spring Boot 3'te aynı düzen, sürüm başına ayrı controller paketi (api.v1, api.v2) ve yol öneki ile kurulur; fark yalnızca eşleme mekanizmasındadır. Her iki durumda da kural aynıdır: sürüm farkı DTO ve controller katmanında yaşar, domain modelinde değil. SiparisV1 ve SiparisV2 aynı domain nesnesinden üretilir; iş kuralı tek yerdedir. Sürüm bazlı davranış farkı (örneğin v1'de sayfa boyutu 20, v2'de 50) da bu katmanda tutulur.
Emekliye ayırma başlıkları
@Component
public class EskiSurumBasliklari implements HandlerInterceptor {
@Override
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object h, ModelAndView mv) {
if ("1".equals(req.getHeader("API-Version")) || req.getRequestURI().startsWith("/api/v1/")) {
res.setHeader("Deprecation", "@1767225600"); // RFC 9745: 2026-01-01'den beri deprecated
res.setHeader("Sunset", "Tue, 30 Jun 2026 00:00:00 GMT"); // RFC 8594: bu tarihte kapanır
res.setHeader("Link", "<https://developer.acme.com/api/v2/gecis>; rel=\"deprecation\"; type=\"text/html\"");
}
}
}Başlıklar makine tarafından okunabilir uyarıdır; istemci kütüphaneleri ve API geçitleri bunları loglar ve uyarı üretir. Sunset tarihinden sonra eski sürüm 410 Gone ve geçiş rehberine bağlantı döner; sessizce kapatmak yerine.
Sürüm yaşam döngüsü
Destek politikası yazılı: "Aynı anda en fazla iki ana sürüm (N ve N−1); eski sürüm, yenisinin yayınından en az 12 ay sonra kapanır." Bu cümle geliştirici portalında durur.
Sürüm ve tüketici bazlı kullanım metrikleri: Hangi API anahtarı hangi sürümü ne kadar çağırıyor? Kapanış kararı bu panoya bakılarak verilir; "kimse kullanmıyor sanıyorduk" cümlesi böyle önlenir.
Duyuru: Değişiklik günlüğü, e-posta, portal uyarısı, Deprecation başlığı; kalan tüketicilere doğrudan ulaşım (API anahtarı → iletişim).
Geçiş rehberi: Alan eşleme tablosu, örnek istek/yanıt, sık hatalar; mümkünse geçici bir uyumluluk katmanı (v1 isteğini v2'ye çeviren geçit filtresi).
Kapanış: Sunset tarihinde 410; bir süre sonra yol tamamen kaldırılır.
Sözleşme ve test: kırılmayı üretimden önce yakalamak
OpenAPI tek gerçek kaynak: Koddan üretilen (springdoc) ya da önce yazılıp koda uygulanan; her sürümün şeması depoda.
Kırıcı değişiklik tespiti CI'da: Önceki sürümün OpenAPI'siyle yeni şema karşılaştırılır (oasdiff gibi araçlar); alan kaldırma, tip değişikliği, zorunlu alan ekleme derlemeyi kırar. "Yanlışlıkla kırma" bu adımla biter.
Tüketici güdümlü sözleşme testleri: Pact ya da Spring Cloud Contract ile tüketici "ben bu alanları bu şekilde kullanıyorum" der; sağlayıcı her sürümde bu sözleşmeleri koşar. İç ekipler arasında en etkili yöntem.
Kayıtlı yanıt testleri: Eski sürüm için "altın" yanıtlar; refactor sonrası bayt bayt aynı mı?
İç API ve dış API farkı
Kontrol ettiğiniz tüketicilerle (aynı şirketin ekipleri) sürüm sayısını düşük tutun: toleranslı okuyucu + ekleyerek evrim + sözleşme testleri ile çoğu zaman tek sürüm yeter; kırıcı değişiklik, tüketici dağıtımıyla koordine edilir. Dış tüketicilerde (bayi, iş ortağı, mobil uygulama mağazası onayı) tam disiplin: ana sürüm, politika, başlıklar, metrikler, rehber. Mobil uygulamalar özellikle önemlidir: eski sürüm aylarca sahada kalır; API'nin N−2'yi bile bir süre taşıması gerekebilir.
Sahada sık yapılan hatalar
Her değişiklikte yeni sürüm: v7'ye gelen API'nin altı sürümü bakımdadır; ekleyerek evrim kullanılsaydı v1'de olurdu.
Uç nokta bazında bağımsız sürümler: "Siparişler v3, müşteriler v1" — tüketici hangi kombinasyonun çalıştığını bilemez.
Enum'a sessizce değer eklemek: Katı istemciler çöker; sözleşmede uyarı ve istemci hazırlığı.
Varsayılanı değiştirmek: Şema aynı, davranış farklı; en zor bulunan kırılma.
Sürümü domain modeline sızdırmak:
if (surum == 1)serviste; sürüm farkı DTO katmanında kalmalı.Sunset'siz deprecation: "Bir gün kapanacak" hiçbir tüketiciyi harekete geçirmez; tarih verin.
Kullanım metriği olmadan kapatmak: Kapatılan sürümü kullanan bayiden aynı gün telefon.
Sık sorulan sorular
GraphQL ve gRPC'de sürüm nasıl yönetilir?
GraphQL sürümsüz evrimi tasarım ilkesi yapar: alan ekle, eskisini @deprecated işaretle, kullanım metriğiyle kaldır — burada anlattığımız ekleyerek evrimin dile gömülmüş hâli. gRPC/Protobuf'ta alan numaraları korunur, kaldırılan numaralar reserved yapılır, paket adında ana sürüm (acme.siparis.v2) taşınır.
Semantik sürümleme (1.4.2) API'ye uygulanır mı?
İstemci kütüphanesi (SDK) için evet; HTTP API'nin kendisi için yalnızca ana sürüm anlamlıdır. Ara ve yama sürümleri değişiklik günlüğünde yaşar, URL'de değil.
API geçidi sürüm yönetimine ne katar?
Yönlendirme (v1 → eski dağıtım, v2 → yeni), kullanım metrikleri, Deprecation başlıklarını merkezi ekleme, basit dönüşüm (v1 → v2 alan eşleme) ve kapanışta 410 döndürme; uygulamaya dokunmadan yaşam döngüsünün büyük kısmı geçitte yönetilebilir.
Sonuç
İyi API sürüm yönetimi, az sürümle yaşamaktır: toleranslı okuyucu ve ekleyerek evrimle kırıcı değişiklikten kaçınmak; kaçınılamadığında URI'de ana sürüm, sürüm farkını DTO katmanında tutmak; Deprecation ve Sunset başlıkları, yazılı politika, kullanım metrikleri ve geçiş rehberiyle tüketiciye yol vermek; OpenAPI karşılaştırması ve sözleşme testleriyle kırılmayı CI'da yakalamak. Sürüm numarası bir araçtır; sözleşmeye saygı ilkedir.
API'lerinizi tasarlamak, sürüm politikanızı kurmak ya da kırıcı değişiklik tespitini CI'a eklemek için yazılım tasarımı ve mimari ile entegrasyon ve otomasyon hizmetlerimize göz atabilirsiniz.