API versioning: Eski istemcileri kırmadan API'yi geliştirmek
API'yi zamanla geliştirirken eski istemcileri nasıl kırmayacağımızı işliyoruz: kırıcı değişiklik nedir, versiyonlama neden gerekli, URL/header/query yöntemleri ve .NET'te versiyonlamayı kurmak.
Bir API canlıya çıktığı andan itibaren, artık senin değil, onu kullananların da malı olur. Mobil uygulamalar, başka servisler, üçüncü taraf entegrasyonlar ona bağlanır. Ve bir gün o API'yi değiştirmen gerekir; yeni bir alan eklersin, bir cevabın yapısını düzeltirsin. İşte tam burada tehlike başlar: yaptığın değişiklik, sana bağlı çalışan onlarca uygulamayı bir anda kırabilir. Bugün bu sorunu çözen yaklaşımı, API versioning'i konuşuyoruz.
Kırıcı Değişiklik Nedir?
Her değişiklik tehlikeli değildir. Bazı değişiklikler geriye dönük uyumludur, yani eski istemcileri etkilemez. Örneğin bir cevaba yeni bir alan eklemek genelde güvenlidir; eski uygulama o yeni alanı görmezden gelir, işine devam eder. Ama bazı değişiklikler kırıcıdır (breaking change): bir alanın adını değiştirmek, bir alanı kaldırmak, bir cevabın yapısını baştan kurgulamak. Bunlar, o alana güvenerek yazılmış eski kodu bir anda çalışmaz hale getirir.
Bunu bir apartmanın posta kutuları gibi düşün. Kutulara yeni bir tane eklersen kimse etkilenmez. Ama mevcut kutuların numaralarını değiştirirsen, o adrese mektup gönderen herkesin mektubu yanlış yere gider. API'deki kırıcı değişiklik de tam olarak budur: senin için küçük bir düzeltme, sana bağlı biri için tam bir felaket.
Peki Neden Sadece Eski Halini Korumuyoruz?
Akla ilk gelen çözüm şu olabilir: "hiç kırıcı değişiklik yapmayayım, API'yi hep geriye uyumlu tutayım." Kulağa hoş gelse de bu bir noktadan sonra imkânsız hale gelir. Uygulamalar gelişir, gereksinimler değişir, bazen eski tasarım kararlarının yanlış olduğu ortaya çıkar. API'yi sonsuza dek dondurmak, onu gelişemez kılar. İşte versiyonlama tam da bu çıkmaza çözüm sunar: eskiyi kırmadan yeniyi yayınlamanın yolu.
Versiyonlamanın Temel Fikri
Versiyonlamanın mantığı basit: aynı API'nin birden fazla sürümünü aynı anda ayakta tutarsın. Eski istemciler v1'i kullanmaya devam eder, yeni istemciler v2'ye geçer. Sen v2'de istediğin kırıcı değişikliği yaparsın ama v1'e dokunmadığın için eski uygulamalar hiçbir şey fark etmez. Herkes kendi sürümünde huzur içinde çalışır. İstemciler kendi zamanlarında, hazır olduklarında yeni sürüme geçer; kimse zorla taşınmaz.
Versiyonu Nereye Koyalım? Üç Yöntem
Bir isteğin hangi sürümü istediğini belirtmesinin birkaç yolu var. Üçü yaygındır ve her birinin kendine göre artısı vardır.
URL içinde
En yaygın ve en görünür yöntem. Sürüm doğrudan adresin içinde yer alır:
/api/v1/users
/api/v2/users
Bu yöntemin en büyük avantajı açıklık. Adrese bakan herkes hangi sürümü kullandığını anında görür; test etmek, dokümante etmek ve tarayıcıdan denemek çok kolaydır. Bu netliği yüzünden pratikte en sık tercih edilen yöntemdir.
Header içinde
Sürüm, adreste değil, isteğin bir başlığında taşınır. Örneğin özel bir X-Api-Version: 2 başlığıyla. Bu yaklaşımda adres sürümden bağımsız, temiz kalır. Ama görünürlüğü düşüktür; hangi sürümün kullanıldığını anlamak için isteğin başlıklarına bakman gerekir, adrese bakmak yetmez.
Query string içinde
Sürüm, adresin sonuna bir parametre olarak eklenir: /api/users?api-version=2. Kullanımı kolaydır ama URL yöntemi kadar temiz durmaz; sürüm bilgisi, filtreleme parametreleri gibi diğer query değerlerinin arasında kaybolabilir.
.NET'te Versiyonlamayı Kurmak
.NET, versiyonlamayı bir paket üzerinden çözüyor. Önce paketi ekliyorsun:
dotnet add package Asp.Versioning.Mvc
Sonra Program.cs'te versiyonlama servisini kaydediyorsun:
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
});
Bu ayar üç şey söylüyor. Varsayılan sürüm 1.0 olsun; istemci bir sürüm belirtmezse otomatik olarak varsayılan kullanılsın; ve cevaplarda hangi sürümlerin mevcut olduğu bilgisi de dönsün. Bu son ayar, API'ni kullananların hangi sürümlerin var olduğunu keşfetmesini kolaylaştırır.
Ardından controller'larını sürümlere işaretliyorsun. URL tabanlı yöntemi seçtiğimizi varsayarsak şöyle görünür:
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/users")]
public class UsersV1Controller : ControllerBase
{
[HttpGet]
public IActionResult GetAll()
{
return Ok("v1 kullanıcı listesi");
}
}
[ApiVersion("1.0")] bu controller'ın hangi sürüme ait olduğunu, [Route(...)] içindeki v{version:apiVersion} ise sürümün adresin neresinde yer alacağını belirtir. Yeni bir sürüm çıkardığında, [ApiVersion("2.0")] ile işaretlenmiş ayrı bir controller yazarsın ve eskisine hiç dokunmazsın. İki sürüm yan yana, birbirini etkilemeden yaşar.
Ne Zaman Yeni Sürüm Çıkarmalı?
Versiyonlamayı kurmak kolay ama asıl mesele onu ne zaman kullanacağını bilmek. Basit kural: sadece kırıcı bir değişiklik yapman gerektiğinde yeni sürüm çıkar. Bir alan eklemek gibi geriye uyumlu değişiklikler için yeni sürüme gerek yok; mevcut sürümde yapabilirsin. Ama bir alanı kaldırıyor, adını değiştiriyor ya da cevabın yapısını baştan kuruyorsan, işte o zaman v2'nin zamanı gelmiştir. Gereksiz yere sürüm çoğaltmak da bakım yükünü artırır; her yeni sürüm, ayakta tutman gereken bir sözleşme daha demektir.
Küçük Bir Deneme
Versiyonlama paketini projene ekle ve iki controller oluştur: biri v1, diğeri v2 sürümüne işaretli, ama aynı kaynağı (mesela kullanıcıları) yöneten. v2'de cevabın yapısını bilerek farklı kur. Sonra tarayıcıdan hem /api/v1/users hem de /api/v2/users adresine git ve iki farklı cevabı gör. İkisinin de aynı anda, birbirini etkilemeden çalıştığını gözlemlemek, versiyonlamanın neden bu kadar rahatlatıcı olduğunu net biçimde gösterir.
Bir sonraki yazıda güvenlik ve kimlik bloğuna geçiyoruz. İlk konu, çoğu geliştiricinin karıştırdığı iki kavramı ayırmak olacak: authentication (kimlik doğrulama) ile authorization (yetkilendirme) arasındaki fark. Bugün sürümlerini ayakta tuttuğun API'yi, artık kimlerin ve nasıl kullanabileceğini kontrol etmenin zamanı geliyor.