API Tasarımında Backward Compatibility Sağlama
Bir API yayınlandığı anda, onu tüketen istemcilerle bir sözleşme (contract) imzalamış olursunuz. Bu sözleşmeyi bozan değişiklikler; mevcut istemcilerin çökmesine, veri kaybına ve itibar kaybına yol açar. Backward compatibility (geriye dönük uyumluluk), API'nizi geliştirmeye devam ederken eski istemcilerin çalışmaya devam etmesini sağlayan disiplindir.
1. Breaking vs. Non-Breaking Değişiklikler
| Değişiklik | Breaking mi? | Açıklama |
|---|---|---|
| Yeni endpoint ekleme | ❌ | Mevcut istemciler kullanmak zorunda değil |
| Yeni opsiyonel alan (yanıt) | ❌ | Tolerant reader görmezden gelir |
| Yeni opsiyonel query parametresi | ❌ | Varsayılan değer kullanılır |
| Yeni zorunlu alan (istek) | ✅ | Eski istemciler bu alanı göndermez |
| Alan kaldırma (yanıt) | ✅ | İstemciler null/undefined alır |
| Alan tipi değiştirme | ✅ | int → string dönüşümü bozar |
| Alan adı değiştirme | ✅ | İstemciler eski adı arar |
| HTTP durum kodu değiştirme | ✅ | İstemci mantığını bozar |
| Varsayılan davranış değiştirme | ✅ | Sessiz davranış değişikliği |
Altın Kural: Emin değilseniz, breaking olduğunu varsayın ve yeni sürüm planlayın.
2. Additive Change: Ekle, Çıkarma
Backward compatibility'nin temel prensibi: mevcut yapıya ekleme yapın, çıkarma yapmayın.
✅ Non-Breaking:
json
// V1
{ "id": 123, "name": "Laptop", "price": 1500 }
// V1 + Yeni Alan
{ "id": 123, "name": "Laptop", "price": 1500, "currency": "TRY" }
❌ Breaking:
json
// V1
{ "id": 123, "name": "Laptop", "price": 1500 }
// V1 + Alan Kaldırma
{ "id": 123, "name": "Laptop" }
3. Tolerant Reader Pattern
Backward compatibility sadece sunucunun değil, istemcinin de sorumluluğudur. Tolerant Reader, istemcinin tanımadığı alanları görmezden gelmesi ve eksik alanlara karşı dayanıklı olmasıdır.
❌ Kırılgan İstemci:
csharp
// Yanıtın tam olarak beklenen formatta geldiğini varsayar var price = data.price; // "price" yoksa hata!
✅ Hoşgörülü İstemci:
csharp
public class ProductDto
{
public int Id { get; set; }
public string Name { get; set; }
public decimal? Price { get; set; } // Nullable
public string Currency { get; set; } = "TRY"; // Varsayılan
}
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
Tolerant Reader Kuralları: Yeni alanları görmezden gel, eksik alanlara varsayılan değer kullan, alan sırasına bağımlı olma, hata mesajlarını parse etme.
4. Breaking Değişiklikler için Strateji: Expand/Contract
Yeniden adlandırma veya tip değişikliği gibi breaking değişiklikler, üç adımda güvenle yapılabilir:
Adım 1 (Expand): Yeni alanı ekle, eski alanı koru.
json
{ "price": 1500, "priceAmount": 1500, "currency": "TRY" }
Adım 2 (Migrate): Tüm istemciler yeni alana geçene kadar bekle.
Adım 3 (Contract): Eski alanı kaldır.
json
{ "priceAmount": 1500, "currency": "TRY" }
5. Sürümleme ve Deprecation Süreci
Adım 1: Yeni sürümü paralel yayınlayın.
text
/api/v1/products ← Eski (çalışmaya devam ediyor) /api/v2/products ← Yeni
Adım 2: Eski sürümü "deprecated" işaretleyin.
csharp
[ApiVersion("1.0", Deprecated = true)]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ProductsController : ControllerBase { ... }
Adım 3: Deprecation başlıkları ekleyin.
csharp
context.Response.Headers.Append("Deprecation", "true");
context.Response.Headers.Append("Sunset", "Sat, 31 Dec 2026 23:59:59 GMT");
context.Response.Headers.Append("Link", "</api/v2/products>; rel=\"successor-version\"");
Sunset başlığı, eski sürümün ne zaman kapatılacağını bildirir. Link başlığı yeni sürüme işaret eder.
Adım 4: Geçiş süresi tanıyın (6-12 ay).
Adım 5: Eski sürümü kapatın (410 Gone).
6. Otomatik Doğrulama: OpenAPI Diff
Backward compatibility'yi CI/CD'de otomatik doğrulamak, insan hatasını ortadan kaldırır. oasdiff gibi araçlar, iki OpenAPI şemasını karşılaştırarak breaking değişiklikleri tespit eder:
bash
oasdiff breaking api-v1.json api-v2.json
Pipeline'da breaking değişiklik bulunursa, build başarısız olur ve deploy durur. Bu, "fark etmeden bozma" riskini sıfırlar.
7. En İyi Pratikler
| Pratik | Açıklama |
|---|---|
| Tolerant Reader olun | İstemciler yeni alanları görmezden gelsin, eksik alanlara dayanıklı olsun. |
| Additive change yapın | Mevcut yapıya ekleme yapın, çıkarma yapmayın. |
| Breaking değişiklikleri sürümleyin | Yeni sürüm oluşturun; eskiyi koruyun. |
| Deprecation başlıkları kullanın | Sunset ve Deprecation ile istemcileri otomatik bilgilendirin. |
| Kontrat testleri yazın | API sözleşmesini otomatik testlerle koruyun. |
| OpenAPI diff'i CI/CD'ye ekleyin | Breaking değişiklikleri merge öncesi yakalayın. |
| Geçiş süresi tanıyın | İstemcilere yeni sürüme geçmeleri için makul süre verin. |
Sonuç
Backward compatibility, bir API'nin uzun vadeli başarısının anahtarıdır. Breaking değişikliklerden kaçınmak, additive change prensibini benimsemek, tolerant reader pattern'ini uygulamak, sürümleme ve deprecation süreçlerini doğru yönetmek, mevcut istemcilerinizi korurken API'nizi geliştirmeye devam etmenizi sağlar. Unutmayın: Bir API yayınlandığında, onu tüketen istemcilerle bir sözleşme imzalamış olursunuz; bu sözleşmeyi bozan değişiklikler, güven kaybına ve itibar kaybına yol açar.