API Tasarımı & Sürüm Yönetimi 6 dk okuma 3 okunma

API Tasarımında Backward Compatibility

API'lerin zaman içinde evrilirken mevcut istemcileri bozmadan değişiklik yapabilmesini sağlayan backward compatibility (geriye dönük uyumluluk) stratejileri; breaking vs. non-breaking değişiklikler, additive change prensibi, tolerant reader pattern, sürümleme stratejileri, deprecation süreci, API sözleşmeleri ve .NET'te uygulama yöntemleri ele alınır.

API Tasarımında Backward Compatibility

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 bir dizi prensip ve tekniktir. Bu yazıda, backward compatibility'nin temel prensiplerini, breaking (bozucu) ve non-breaking (bozmayan) değişiklikleri, sürümleme stratejilerini, deprecation sürecini ve .NET'te uygulama yöntemlerini detaylıca ele alacağız.


1. Breaking vs. Non-Breaking Değişiklikler

Backward compatibility'nin temeli, hangi değişikliklerin istemcileri bozduğunu (breaking) ve hangilerinin bozmadığını (non-breaking) bilmektir.

Değişiklik Türü Breaking mi? Açıklama
Yeni endpoint ekleme ❌ Hayır Mevcut istemciler yeni endpoint'i kullanmak zorunda değil.
Yeni opsiyonel alan ekleme (yanıt) ❌ Hayır Tolerant reader pattern kullanan istemciler yeni alanı görmezden gelir.
Yeni opsiyonel alan ekleme (istek) ❌ Hayır Mevcut istemciler bu alanı göndermek zorunda değil.
Yeni opsiyonel query parametresi ❌ Hayır Mevcut çağrılar varsayılan değeri kullanır.
Alan ekleme (zorunlu, istek) ✅ Evet Mevcut istemciler bu alanı göndermediği için hata alır.
Alan kaldırma (yanıt) ✅ Evet İstemciler bu alanı okumaya çalışırsa undefined/null alır.
Alan tipi değiştirme ✅ Evet int → string dönüşümü istemcileri bozar.
Alan adı değiştirme ✅ Evet İstemciler eski adı arar, bulamaz.
Endpoint kaldırma ✅ Evet İstemciler 404 alır.
HTTP durum kodu değiştirme ✅ Evet 200 OK → 201 Created istemci mantığını bozabilir.
Varsayılan davranışı değiştirme ✅ Evet Örtük davranış değişikliği sürpriz sonuçlar doğurur.
Hata mesajı formatını değiştirme ⚠️ Duruma bağlı İstemci hata mesajını parse ediyorsa bozucudur.

Altın Kural: Emin değilseniz, breaking olduğunu varsayın ve sürümleme stratejinizi buna göre planlayın.


2. Additive Change (Ekleme Prensibi)

Backward compatibility'nin en temel prensibi: Mevcut yapıya ekleme yapın, mevcut yapıdan çıkarma yapmayın.

✅ Non-Breaking (Güvenli) Değişiklikler:

json

// V1 Yanıt
{
  "id": 123,
  "name": "Laptop",
  "price": 1500
}

// V1 + Yeni Alan (Non-Breaking)
{
  "id": 123,
  "name": "Laptop",
  "price": 1500,
  "currency": "TRY"   // Yeni alan eklendi
}

❌ Breaking (Tehlikeli) Değişiklikler:

json

// V1 Yanıt
{
  "id": 123,
  "name": "Laptop",
  "price": 1500
}

// V1 + Alan Kaldırma (Breaking)
{
  "id": 123,
  "name": "Laptop"    // "price" alanı kaldırıldı!
}

3. Tolerant Reader Pattern (Hoşgörülü Okuyucu)

Backward compatibility sadece sunucunun sorumluluğu değildir. İstemciler de Tolerant Reader olmalıdır: Yani, tanımadıkları alanları görmezden gelmeli, eksik alanlara karşı dayanıklı olmalıdır.

❌ Kırılgan İstemci (Fragile Reader):

csharp

// Yanıtın tam olarak beklenen formatta geldiğini varsayar
var json = await response.Content.ReadAsStringAsync();
dynamic data = JsonSerializer.Deserialize<dynamic>(json);
var price = data.price; // "price" yoksa exception!

✅ Hoşgörülü İstemci (Tolerant Reader):

csharp

// Eksik alanlara karşı varsayılan değer kullanır
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 değer
}

var options = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
var product = JsonSerializer.Deserialize<ProductDto>(json, options);

Tolerant Reader Prensipleri:

  • Yeni alanları görmezden gel (Ignore unknown properties).

  • Eksik alanlar için varsayılan değer kullan.

  • Null değerlere karşı dayanıklı ol.

  • Alan sırasına bağımlı olma.

  • Hata mesajlarını parse etmeye çalışma.


4. Additive Sürümleme Stratejisi

Breaking değişiklikler kaçınılmaz olduğunda, sürümleme devreye girer. En güvenli yaklaşım, kademeli ve additive (eklemeli) bir yol izlemektir.

Adım 1: Yeni Sürümü Paralel Yayınlayın

text

/api/v1/products    ← Eski sürüm (çalışmaya devam ediyor)
/api/v2/products    ← Yeni sürüm (yeni istemciler için)

Adım 2: Eski Sürümü "Deprecated" Olarak İşaretleyin

csharp

[ApiVersion("1.0", Deprecated = true)]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}")]
    [MapToApiVersion("1.0")]
    public IActionResult GetV1(int id) { ... }

    [HttpGet("{id}")]
    [MapToApiVersion("2.0")]
    public IActionResult GetV2(int id) { ... }
}

Adım 3: Deprecation (Kullanımdan Kaldırma) Başlıkları Ekleyin

csharp

// Sunucu, eski sürümü kullanan istemcilere uyarı döner
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 tamamen kapatılacağını bildirir. Link başlığı ise yeni sürüme işaret eder.

Adım 4: Geçiş Süresi Tanıyın
İstemcilere yeni sürüme geçmeleri için makul bir süre tanıyın (genellikle 6-12 ay). Bu süre boyunca her iki sürüm de aktif kalır.

Adım 5: Eski Sürümü Kapatın
Sunset tarihinde, eski sürümü kapatın ve istemcilere 410 Gone yanıtı dönün.

csharp

[HttpGet("{id}")]
[MapToApiVersion("1.0")]
public IActionResult GetV1(int id)
{
    return StatusCode(410, new ProblemDetails
    {
        Title = "API v1 artık kullanılmıyor",
        Detail = "Lütfen /api/v2/products endpoint'ini kullanın.",
        Status = 410
    });
}

5. Sürümleme Olmadan Uyumluluk Sağlama Teknikleri

Her zaman yeni bir sürüm oluşturmak gerekmez. Bazı durumlarda, aynı sürüm içinde uyumluluğu koruyabilirsiniz:

A. Alan Yeniden Adlandırma (Rename)
Eski alanı koruyup, yenisini ekleyin:

json

{
  "id": 123,
  "name": "Laptop",           // Eski alan (deprecated)
  "productName": "Laptop",    // Yeni alan
  "price": 1500
}

Bir süre sonra eski alanı kaldırın.

B. Tip Değişikliği (Type Change)
Eski ve yeni tipleri bir arada sunun:

json

{
  "price": 1500,              // Eski: sayısal
  "priceFormatted": "1500 TRY" // Yeni: string
}

C. Davranış Değişikliği için Feature Flag
Yeni davranışı bir feature flag arkasına alın:

csharp

[HttpGet]
public IActionResult GetProducts([FromQuery] bool useNewPricing = false)
{
    if (useNewPricing)
        return Ok(GetProductsWithNewPricing());
    return Ok(GetProductsWithLegacyPricing());
}

İstemciler, hazır olduklarında useNewPricing=true gönderir.


6. API Sözleşmeleri ve Kontrat Testleri

Backward compatibility'yi garanti altına almak için kontrat testleri (contract tests) kritik öneme sahiptir.

A. OpenAPI Şemasını Sürümleyin
Her API sürümü için OpenAPI şemasını saklayın ve yeni sürümün eskisiyle uyumlu olup olmadığını kontrol edin.

B. Otomatik Uyumluluk Kontrolü
CI/CD pipeline'ında, yeni API sürümünün eski sürümle uyumlu olup olmadığını otomatik kontrol eden araçlar kullanın:

bash

# OpenAPI diff araçları
oasdiff breaking api-v1.json api-v2.json

C. Kontrat Testleri
İstemci sözleşmesini doğrulayan testler yazın:

csharp

[Fact]
public async Task GetProduct_ReturnsExpectedFields()
{
    var response = await _client.GetAsync("/api/v1/products/123");
    var json = await response.Content.ReadAsStringAsync();
    var product = JsonSerializer.Deserialize<ProductDto>(json);

    // Kritik alanların varlığını doğrula
    Assert.NotNull(product.Id);
    Assert.NotNull(product.Name);
    Assert.True(product.Price > 0);
}

7. Deprecation Süreci (Kullanımdan Kaldırma)

Bir API öğesini kullanımdan kaldırmak, dikkatli bir süreç gerektirir.

Aşamalar:

Aşama Süre Yapılacaklar
1. Duyuru Anında Deprecation politikasını yayınlayın, istemcileri bilgilendirin.
2. Uyarı 3-6 ay Deprecation ve Sunset başlıklarını ekleyin, log'larda uyarı verin.
3. Geçiş Desteği 6-12 ay Migration rehberi, örnek kodlar ve destek sağlayın.
4. Kapatma Sunset tarihi Eski endpoint'i kapatın, 410 Gone dönün.

Örnek Deprecation Politikası:

text

API v1, 1 Ocak 2025'te kullanımdan kaldırılmıştır.
31 Aralık 2026'ya kadar desteklenecektir.
1 Ocak 2027'de kapatılacaktır.
Lütfen /api/v2/products endpoint'ine geçin.

8. .NET'te Backward Compatibility için Araçlar

Araç Amaç
Microsoft.AspNetCore.Mvc.Versioning API sürümleme (URL, header, query)
Asp.Versioning.Mvc.ApiExplorer Swagger/OpenAPI ile sürümleme entegrasyonu
oasdiff OpenAPI şemaları arası breaking change tespiti
Swashbuckle.AspNetCore Her sürüm için ayrı Swagger dokümantasyonu
Verify Snapshot testleri ile API yanıtlarını doğrulama

9. En İyi Pratikler

  1. Tolerant Reader Olun: İstemcileriniz yeni alanları görmezden gelsin, eksik alanlara dayanıklı olsun.

  2. Additive Change Yapın: Mevcut yapıya ekleme yapın, çıkarma yapmayın.

  3. Breaking Değişiklikleri Sürümleyin: Breaking değişiklikler için mutlaka yeni sürüm oluşturun.

  4. Deprecation Politikası Belirleyin: Net bir deprecation takvimi ve iletişim planı oluşturun.

  5. Sunset ve Deprecation Başlıklarını Kullanın: İstemcileri otomatik olarak bilgilendirin.

  6. Kontrat Testleri Yazın: API sözleşmesini otomatik testlerle koruyun.

  7. OpenAPI Şemasını Sürümleyin: Her sürümün şemasını saklayın ve karşılaştırın.

  8. Dokümantasyonu Güncel Tutun: Her sürüm için ayrı dokümantasyon sağlayın.

  9. Geçiş Süresi Tanıyın: İstemcilere yeni sürüme geçmeleri için makul süre verin.

  10. İletişim Kurun: Deprecation kararlarını zamanında ve açıkça duyurun.

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 stratejilerini doğru kullanmak ve net bir deprecation süreci 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. İyi bir API, zaman içinde evrilirken eski dostlarını da yanında taşır.

Tüm yazılar Soru ya da yorumun mu var? Yaz