Bulut Altyapı

.NET 10’da API Versioning ve OpenAPI: Pratik Entegrasyon

Açık konuşayım: API versiyonlama, yıllardır bana göre.NET tarafında en az heyecan veren ama en çok baş ağrıtan konulardan biri (evet, doğru duydunuz). Yeni bir endpoint yazmak keyifli, evet — ama eski client’ları kırmadan o endpoint’i nasıl büyütürsün? Asıl düğüm orada.

Geçen hafta İstanbul’da bir sigorta şirketinde tam da bunu konuşuyorduk. Ekip.NET 8’den.NET 10’a geçiş planlıyor, mobil uygulamanın eski sürümlerini kullanan yüz binlerce kullanıcı var ve “biz şimdi bu API’yi nasıl versiyonlayacağız?” sorusu masaya geldi. Hâlâ Asp.Versioning mi, yoksa Microsoft’un yeni Microsoft.AspNetCore.OpenApi paketi mi? İkisini birden mi? Şey, iş biraz karışıyor gıbı duruyor.

İnanın, İşin aslı şu:.NET 10 ile tablo biraz değişti. Hem Microsoft.AspNetCore.OpenApi resmî olarak olgunlaştı, hem de Asp.Versioning v10 çıktı — ki bu sürüm ilk kez.NET 10’un yeni built-in OpenAPI desteğini resmî olarak kapsıyor. Yanı artık iki dünyayı birbirine yamamak için sayfalarca custom kod yazmanız gerekmiyor (buna dikkat edin). Ben de burada kendi notlarımı ve müşterilerde uyguladığım yapıyı paylaşayım dedim — valla güzel iş çıkarmışlar —

Önce şu “neden versiyonlama?” sorusunu kısa keselim

Bunu zaten biliyorsanız geçin. Ama deneyimime göre çoğu junior geliştirici “v1, v2, hadi yapıyoruz” noktasında kalıyor; asıl “neden” kısmında takılıyor. Cevap basit: API’nız canlıya çıktığı an artık sadece sizin değil — önü tüketenlerin de oluyor.

Bir dakika — bununla bitmedi.

Bir mobil uygulama düşünün. App Store onay süreci — kendi adıma konuşayım — 3 gün, kullanıcının uygulamayı güncellemesi işe bazen 3 ay sürüyor; siz API’yi değiştirdiğiniz anda sahada eski sürüm hâlâ çalışıyor. Kırarsanız itibar gidiyor, hatta bazen fark etmeden destek masasına yük bindiriyorsunuz. Versiyonlama dediğimiz şey aslında “geriye dönük uyumluluk” sözleşmesi gıbı bir şey.

Üç ana strateji var:

  • URL Path: /api/v1/orders — En yaygın, okunması kolay, cache tarafında da fena değil — bunu es geçmeyin
  • Query String: /api/orders?api-version=2.0 — Esnek ama URL’i biraz çirkinleştiriyor
  • Header: X-Api-Version: 2 — Pürist’lerin sevdiği türden, ama debug ederken insanı yorabiliyor

Bence URL Path en az kafa karıştıranı. Türkiye’de gördüğüm kurumsal projelerin de büyük kısmı bunu seçiyor. Sebebi basit: bir junior developer Postman’i açtığında URL’e bakar bakmaz “tamam bu v2” diyebiliyor. Mühendislik biraz da böyle bir şey; gelecekteki ekibinin işini kolaylaştırmak gerekiyor.

Asp.Versioning v10 ile başlayalım

Önce sadece versiyonlamayı kuralım, OpenAPI’yi sonra ekleriz. Adım adım gitmeyi seviyorum çünkü her şeyi aynı anda bağladığınızda bir yer patlarsa nereye bakacağınızı şaşırıyorsunuz.

Paketleri kuralım

dotnet add package Asp.Versioning.Http --version 10.0.0
dotnet add package Asp.Versioning.Mvc.ApiExplorer --version 10.0.0
dotnet add package Microsoft.AspNetCore.OpenApi

İlk paket Minimal API’lar için, ikincisi controller’lar ve OpenAPI keşfi için. Üçüncüsü zaten.NET 10 ile geliyor ama yine de sürümü kontrol etmekte fayda var; hani bazen küçük detaylar can sıkıyor ya, aynen öyle.

Program.cs’de temel kurulum

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
})
.AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
var app = builder.Build();
var versionSet = app.NewApiVersionSet()
.HasApiVersion(new ApiVersion(1, 0))
.HasApiVersion(new ApiVersion(2, 0))
.ReportApiVersions()
.Build();
app.MapGet("/api/v{version:apiVersion}/orders", () => "v1 orders")
.WithApiVersionSet(versionSet)
.MapToApiVersion(1, 0);
app.MapGet("/api/v{version:apiVersion}/orders", () => new { items = new[] { "v2 orders" }, total = 1 })
.WithApiVersionSet(versionSet)
.MapToApiVersion(2, 0);
app.Run();

Bu kadar. Çalıştırın; /api/v1/orders ve /api/v2/orders farklı yanıtlar dönüyor. Güzel. Ama OpenAPI yok daha — işte asıl uğraştıran kısım şimdi geliyor.

OpenAPI’yi versiyon başına ayırmak

Şahsen, Tartışmalı nokta şu: tek bir /openapi/v1.json dosyası neredeyse tüm versiyonları mı içersin, yoksa her versiyonun kendi dokümanı mı olsun? Microsoft’un önerdiği ve benim de mantıklı bulduğum yaklaşım ikinci seçenek: her versiyonun ayrı OpenAPI dökümanı olsun.

Sebebi pratik. Bir client geliştiricisi v1 kullanıyorsa v2 endpoint’lerini görmesinin pek anlamı yok. OpenAPI dökümanını client SDK üretmek için kullanıyorsanız (çoğu kurumda durum bu), karmaşa çok hızlı büyüyor; sonra kim hangı endpoint’i niye gördü diye gereksiz tartışmalar başlıyor.

builder.Services.AddOpenApi("v1", options =>
{
options.AddDocumentTransformer((document, context, ct) =>
{
document.Info.Version = "1.0";
document.Info.Title = "Sipariş API v1";
return Task.CompletedTask;
});
});
builder.Services.AddOpenApi("v2", options =>
{
options.AddDocumentTransformer((document, context, ct) =>
{
document.Info.Version = "2.0";
document.Info.Title = "Sipariş API v2";
return Task.CompletedTask;
});
});
//...
app.MapOpenApi(); // /openapi/v1.json ve /openapi/v2.json otomatik olarak servis edilir

Yanı, Buradaki kritik nokta şu: AddOpenApi çağrısındaki döküman adı, GroupNameFormat‘taki “v1”, “v2” ile eşleşmek zorunda. Bu eşleşme olmazsa endpoint’ler OpenAPI dökümanına düşmüyor — ben ilk denemede tam buna takıldım açıkçası. Yarım saat boş bir paths: {} ekranına baktım durdum; sebep meğer basit bir işim uyuşmazlığıymış.

“OpenAPI doküman adı = ApiExplorer group adı.” Bu cümleyi post-it’e yazıp monitörünüze yapıştırın..NET 10’da OpenAPI + versiyonlama setup’ında en sık yapılan hata bu.

Controller’lı dünyada işler nasıl?

Minimal API’lardan bahsettik ama Türkiye’deki kurumsal kodun büyük kısmı hâlâ controller tabanlı gidiyor. Bir bankacılık projesinde 200+ controller’ı versiyonlamak zorunda kaldım; orada baya ter döktük doğrusu.

Çok konuştum, örnekle göstereyim. Bu konuyla ilgili Copilot Student’tan GPT-5.3-Codex Çıktı: Ne Anlama Geliyor? yazımıza da göz atmanızı tavsiye ederim.

[ApiController]
[ApiVersion(1.0)]
[ApiVersion(2.0)]
[Route("api/v{version:apiVersion}/[controller]")]
public class OrdersController : ControllerBase
{
[HttpGet, MapToApiVersion(1.0)]
public IActionResult GetV1() => Ok("v1");
[HttpGet, MapToApiVersion(2.0)]
public IActionResult GetV2() => Ok(new { items = Array.Empty<object>() });
}

Bunun güzel tarafı şu: aynı controller içinde iki versiyon birlikte yaşayabiliyor. Ama dikkat edin — bunu fazla abartırsanız controller’larınız yavaş yavaş 1500 satırlık spagettiye döner. Benim pratik tavsiyem şu olurdu: küçük breaking change’lerde aynı controller yeterli, büyük yapısal değişikliklerde işe OrdersV2Controller gıbı ayrı dosya açın. Azure DevOps MCP Server Nişan Güncellemesi: Neler Değişti? yazısında da benzer bir mimarı karar konuşmuştum — kod organizasyonunda dogma olmaz, proje ne istiyorsa ona göre hareket edersiniz.

Türkiye perspektifi: kurumsal mı startup mı?

Araya gireyim: Şimdi biraz yana kayalım ama konudan kopmayalım. Bu dökümantasyon Microsoft’un global blog kafasında yazılmış olabilir ama Türkiye’de işin rengi değişiyor; müşterilerde gördüğüm kadarıyla iki uçta iki ayrı problem var.

Aslında, Startup tarafı:“Versiyonlama mı? Daha ürün-pazar uyumumuz bile oturmadı kardeşim.” Haklı oldukları yer var tabi ki. MVP aşamasında tek bir /api‘yle başlayabilirsiniz ama ben yine de URL’e şimdiden /v1 ekleyin derim. Maliyeti yok denecek kadar az, gelecekteki size bıraktığı rahatlık işe ciddi fark yaratıyor.Bunu yapmayan iki startup gördüm; ikisi de ikinci yılda refactor borcuyla boğuştu resmen.

Yanı, Kurumsal taraf:Burası bambaşka bir dünya.Mesela bankalarda, sigortalarda ve telkolarda her API çağrısı neredeyse sözleşme gıbı ele alınıyor.Geçen sene bir GSM operatöründe gördüm: kullanılmayan v1 endpoint’leri dört yıldır canlıdaydı.Neden? Çünkü hiçbir entegrasyon partner’i dönüp “tamam ben artık v1 kullanmıyorum” demeye yanaşmıyor.Bu yüzden kurumsalda şunu öneriyorum:

Senaryo Nasıl Strateji? Sundown Politikası
Tartup / İç tüketim değilse bile küçük ekip (URL Path) tek aktif versiyon gıbı düşünün) Epey esnek, takım karar veriyor)
B2B Public API si varsa URL Path + Header opsiyonu En az 12 ay deprecation
Bankacılık / Telkom URL Path, sıkı sözleşme 24+ ay, kontrata bağlı
Mobil backend URL Path, agresif sunset App store telemetrisine bağlı

Swa ggerUI ve Scalar arasında hangisi?

.OpenAPI dökümanı üretmek ayrı mesele; önü insan gözüne düzgün göstermek ayrı mesele.Tarihi olarak SwaggerUI standart öldü ama son bir yılda Scalar’a da bayağı bakan var.Denediniz mi hiç?

// Swashbuckle.AspNetCore.SwaggerUI
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/openapi/v1.json", "v1");
options.SwaggerEndpoint("/openapi/v2.json", "v2");
});
// veya Scalar.AspNetCore
app.MapScalarAp iReference(options =>
{
options.AddDocument("v1");
options.AddDocument("v2");
});Açık konuşayım; ben hâlâ SwaggerUI'da kalıyorum ama sebebim teknik değil, alışkanlık.Cihazlarda ekipler "Swagger" diye biliyor bu işi,"Scalar" deyince yüzlerine kısa süreli bir boşluk geliyor.Yine de yeni başlayan projelerde Scalar'ı denemenizi öneririm; arayüzü baya temiz duruyor.
💡 Bilgi: Scalar, OpenAPI 3.1 desteği ve dark mode konusunda SwaggerUI'dan ilerde.Ama tarayıcı içi "try it out" tecrübesinde Swagger hâlâ daha olgun.Gösterim için Scalar, partner-facing sandbox için Swagger derim.

Pratik göç rehberi : üç adımda planlama

.NET 8/9 projeniz var ve bu yapıya geçmek istiyorsunuzsa ilk hafta panik yapmayın ; sıralama kabaca şöyle : (ben de ilk duyduğumda şaşırmıştım) Bu konuyla ilgili Azure Data Studio Emekli Oldu: VS Code'a 10 Dakikada Geçiş yazımıza da göz atmanızı tavsiye ederim.

  1. Önce envanter : Hangi endpoint 'lerinizin kaç client 'ı var ? Application Insights veya log analytics'ten User-Agent ve X-Client-Id header 'larına bakarak çıkarabilirsiniz.Bunu bilmeden versiyonlama yapamazsınız.
  2. Sonra "v1 sözleşmesi" : Mevcut API 'yi olduğu gibi "v1" olarak dondurun.Yeni hiçbir özellik v1'e gitmeyecek.Bu psikolojik bariyer garip şekilde işe yarıyor.
  3. En son v2 başlat : Yeni özellikler ve breaking change 'ler artık burada.Asp.Versioning v10'u kurun, OpenAPI dökümanlarını ayırın, SwaggerUI'a iki tab ekleyin.Bitti.

Bu üç adımı bir telekom projesinde altı haftada tamamladık.Düşündüğümüzden çok daha kısa sürdü açıkçası.Azure DevOps Git Policy API: 10-15 Kat Hız Geldisinde bahsettiğim CI hızlanması da bu süreçte epey işimize yaradı çünkü pipeline her commit'te iki ayrı OpenAPI dökümanını validate ediyordu.

Bir hata, bir çözüm: yaşadığım tuhaf vaka

Aslında, Mart ayında bir e-ticaret platformunda şu hatayı aldım: v2 endpoint'i SwaggerUI'da görünüyor. "Try it out" deyince "404 Not Found" dönüyor.Yarım gün uğraştım.Sebep meğer şuymuş:SubstituteApiVersionInUrl = true ayarını yapmamışım.Yani OpenAPI dökümanında URL/api/v{version}/orders olarak kalıyordu, gerçek route ise/api/v2/orders idi.Tek satır kod, yarım gün hata. Daha fazla bilgi için Cosmos DB Maliyet Optimizasyonu: AI Yüklerinde 7 Taktik yazımıza bakabilirsiniz.

Başka bir tane: aynı endpoint'i hem v1 hem v2'yeMapToApiVersion ile bağlamayı unutursanız, default versiyon ne ise oraya düşüyor — hata da vermiyor.Bu çok sinsi.Production'a gitmiş bir API'da v2 client'ı v1 davranışı alıyor mesela.Test yazmadan canlıya çıkmayın yani.

Maliyet ve operasyonel boyut *

Bu işin sadece kod tarafı yok.Azure tarafında da hesabınızı yapmak lazım.Eğer API'niziAzure API Management'in arkasında host ediyorsanız (kurumsalda hemen hemen herkes öyle yapıyor), her versiyon ayrı bir API olarak tanımlanır.APIM'in versiyonlama desteği var, bu güzel;ama her versiyon için ayrı policy, ayrı subscription key yönetimi gerekiyor (yanlış duymadınız)

Kabaca hesap: APIM Standard tier'da tek bölgede aylık ~700 USD civarı çıkabiliyor.İkinci versiyonu eklemek ekstra maliyet getirmiyor (aynı APIM instance), ama operasyonel yük artıyor.Bir DevOps mühendisi en az haftada dört saatini sadece versiyon yönetimine ayırıyor diyebilirim.Yıllık ~10..000 TL adam-saat maliyeti yani.Bunu CFO'nuza önceden söyleyin ki sonra "biz bunu hesaba katmamıştık" cümlesi gelmesin.

Bakın, burayı atlarsanız yazının kalanı anlamsız kalır.

Sıkça Sorulan Sorular

Asp.Versioning v10'a geçiş zor mu?

Aslında hayır, düşündüğün kadar zorlu değil. Namespace'ler ve API yüzeyi büyük ölçüde aynı kaldı. Asıl değişiklik hani AddApiExplorer ile Microsoft.AspNetCore.OpenApi entegrasyonunda yaşanıyor. Mevcut Swashbuckle tabanlı kodun (söylemesi ayıp) çalışmaya devam ediyor, (evet, doğru duydunuz). Aynı zamanda yeni OpenAPI dökümanlarını da üretebiliyorsun. Bence geçişi adım adım yapman çok daha mantıklı — big-bang yaklaşımını kesinlikle tavsiye etmem.

URL yerine header versiyonlama kullansam olur mu?

Teknik olarak olur, ama açıkçası kurumsal partner'ları olan API'lerde pek önermiyorum. Header'ı debug etmek zor, cache'lemek karmaşık (evet, doğru duydunuz). Bir de mesela junior geliştiriciler URL'den göremedikleri için "neden farklı yanıt geliyor?" diye saatlerce kafa yoruyor. Tecrübeme göre URL Path en pragmatik çözüm.

Her küçük değişiklik için yeni versiyon çıkarmak şart mı?

Şunu söyleyeyim, Bence hayır. Versiyon yalnızca breaking change'lerde artıyor. Yani yeni alan eklemek, opsiyonel parametre eklemek ya da hata mesajı iyileştirmek versiyon bump gerektirmiyor. SemVer mantığını API'lere uyarlamak işe yarıyor: major = breaking, minor/patch = non-breaking. Aslında çoğu kurumda sadece major versiyonu URL'e koyuyoruz (v1, v2), minor'ı ise header ile belirtiyoruz.

Eski versiyonu ne zaman kapatmalıyım?

Telemetri olmadan sakın kapatma. Application Insights ya da benzer bir araçla v1 kullanım oranını takip et. Kullanım %1'in altına indiğinde kalan kullanıcılara 90 gün önceden mail at, bir de deprecation header'ı ekle — hani Sunset header standardı RFC 8594'te tanımlı. Ondan sonra kapatabilirsin. Aniden kapatmak hiç profesyonelce değil, özellikle B2B'de bunu yaşadım.

OpenAPI dökümanını CI'da test edebilir miyim?

Evet, hatta bence etmen gerekiyor. Spectral veya openapi-diff araçlarıyla pipeline'da OpenAPI'yi lint'leyebilir, breaking change'leri otomatik yakalayabilirsin (kendi tecrübem). Mesela bir önceki commit'in OpenAPI'siyle yeni commit'inkini diff'leyip breaking change varsa PR'ı bloklayan bir kontrol koymak — açıkçası bu çok hayat kurtarıyor. Vallahi tavsiye ederim.

Kaynaklar ve İleri Okuma

Combining API versioning with OpenAPI in.NET 10 applications — Microsoft DevBlogs

Microsoft.AspNetCore.OpenApi Resmi Dokümantasyonu (ki bu çoğu kişinin gözünden kaçıyor)

Asp.Versioning GitHub Repository

RFC 8594: The Sunset HTTP Header Field (inanın bana)

Aşkın KILIÇ

20+ yıl deneyimli Azure Solutions Architect. Microsoft sertifikalı bulut mimari ve DevOps danışmanı. Azure, yapay zekâ ve bulut teknolojileri üzerine Türkçe teknik içerikler üretiyor.

AZ-305AZ-104AZ-500AZ-400DP-203AI-102

Bu içerik işinize yaradı mı?

Benzer içerikleri kaçırmamak için beni sosyal medyada takip edin.

← Onceki Yazi
Copilot Student'tan GPT-5.3-Codex Çıktı: Ne Anlama Geliyor?
Sonraki Yazi →
Copilot Cloud Agent %20 Daha Hızlı: Custom Image Etkisi

Yorum Yaz

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

İçindekiler
← Copilot Student’tan GPT-...
Copilot Cloud Agent %20 Daha H... →