.NET'in Evrimi: Modern Web Uygulamalarında AI ve Mobil-Native Özelliklerin Entegrasyonu
Milan Jovanović kısa süre önce erken API versiyonu almanın gereksiz olduğu hakkında güçlü bir argüman yayınladı. Ana konu: Çoğu ekip, sözleşme gelişim stratejileri olmadığı için v2'ya çok erken yöneliyor. Versiyonlama bir uyumluluk aracıdır, tasarım stratejisi değildir.
Argüman, Iron Software'deki mühendislik ekibimizle yankı uyandırıyor. .NET kütüphaneleri gönderiyoruz, bu da ürünlerimizin genel yüzeyinin bir API olduğu anlamına gelir. Her metot imzası, her özellik, her varsayılan davranış, binlerce müşteri kod tabanının içinde bir sözleşmedir. Büyük bir versiyon artışı bir sürüm değildir. Bu, herkesin akış aşağı projesidir.
Aşağıda, Milan'ın parçasına bir kütüphane yazarının perspektifinden bir geliştirici bakışı ve bir REST API veya bir NuGet paketi gönderiyor olsanız da aynı uyumluluk kurallarının nasıl uygulandığı hakkında bir şey geliyor.
Özet
- Versiyonlama bir tasarım stratejisi değildir. Birlikte varoluş başarısız olduğunda bir kaçış kapısıdır.
- Bozulan değişiklikler yalnızca URL'lerde veya şemalarda değil, aynı zamanda davranışlarda da gizlenir.
- Dört uyumluluk kuralı: çıkarmayı, işlemeyi değiştirmeyi sıkılaştırmayı, eklemeleri isteğe bağlı tutmayı yapmayın.
- Yeni bir işlem, neredeyse her zaman yeni bir sürümden daha ucuzdur.
- Gerçek kullanım dışı bırakma, yalnızca belge güncellemeleri değil, çalışma zamanı sinyalleri ve telemetri gerektirir.
HTTP kuralları kütüphane API'lerine de uygulanır
Milan, tartışmayı /orders için bir REST API etrafında şekillendiriyor, ancak API'niz bir NuGet paketinde gönderilen bir C# sınıfı olduğunda da aynı kurallar geçerli. Eşleştirme doğrudan:
| REST API değişikliği | NuGet kütüphanesi eşdeğeri |
|---|---|
| Bir JSON alanını yeniden adlandırma | Genel bir özelliği yeniden adlandırma |
| Bir uç noktayı kaldırma | Genel bir metodu kaldırma |
| İstek doğrulamayı sıkılaştırma | Boş değer almayan bir parametre ekleme |
| İşlem davranışını değiştirme | Bir metodun kaputun altında ne yaptığı değiştirme |
| Gerekli bir alan ekleme | Gerekli bir yapıcı parametre ekleme |
Eğer popüler bir .NET kütüphanesinin ana sürümünü çektiyseniz ve yeniden adlandırılmış API'leri düzeltmek için yarım gün harcadıysanız, muhtemelen ekleyici bir şekilde ele alınabilecek bir v2 kararının alıcı tarafında oldunuz.
Aslında tüketicileri ne bozuyor
Milan'ın listesi hassastır:
- Alanları kaldırma veya yeniden adlandırma
- Mevcut verilerin anlamını değiştirme
- İstek doğrulamayı sıkılaştırma
- Sayfa numaralandırmayı veya hata biçimlerini değiştirme
- Algoritmik değerlerin sonsuza kadar kapalı olduğunu varsayma
İkinci madde, genellikle ekipleri hazırlıksız yakalar: mevcut verilerin anlamını değiştirme, şeklini değiştirmeden. JSON aynı görünüyor. C# imzası aynı görünüyor. Her şey derleniyor. Çalışma zamanında hiçbir şey atılmıyor. Ancak alan şimdi farklı bir şey ifade ediyor ve eski semantiklere güvenen her tüketici sessizce yanlıştır.
Milan'ın örneği:
// Before
{ "total": 100 }
// After
{ "total": { "amount": 100, "currency": "USD" } }Aynı alan adı. Aynı uç nokta. total'yı bir sayı olarak ayrıştıran her müşteri artık çalışmaz durumda.
Kütüphane eşdeğeri, neyin döndüğünü veya girdilerini nasıl yorumladığını değiştirmedir. Daha önce üzerine yazan ve şimdi ekleyen bir Save() yöntemi. Varsayılanı true'dan false'ya dönen bir Trim parametresi. Daha önce geçersiz girdilerde hata veren ve şimdi sessizce varsayılan değer döndüren bir metod.
Dört uyumluluk kuralı
Milan kuralları şöyle özetler: hiçbir şeyi kaldırmayın, işlem kurallarını değiştirmeyin, isteğe bağlı öğeleri gerekli hale getirmeyin; eklediğiniz her şey isteğe bağlı olmalıdır. Dört ilke, genel bir API'den sorumlu olan herhangi bir ekibin önünde tutmaya değer:
- Mevcut alanları ve davranışları yerinde tutun.
- İsteğe bağlı istek verilerini gerekli verilere dönüştürmeyin.
- Mevcut bir işlemin ne yaptığını değiştirmeyin.
- Eklediğiniz her şey yeni ve isteğe bağlı olarak varsayılan olsun.
Bunlar doğrudan kütüphane tasarımına uygular. "Hiçbir şeyi kaldırmayın" ifadesi, genel üyeleri silmeyin anlamına gelir. "İşlem kurallarını değiştirmeyin" ifadesi, mevcut metodların, gönderildikleri tarihteki gibi davranmaları gerektiği anlamına gelir. "İsteğe bağlıyı gerekli hale getirmeyin" ifadesi, mevcut bir metoda gerekli parametreler eklemeyin; bunun yerine bir yükleme sağlayın. "Ek ve isteğe bağlı" ifadesi, yeni işlevselliğin yeni metodlarda veya mantıklı varsayılanlarla isteğe bağlı parametrelerde yer alması gerektiği anlamına gelir.
Bu uygulamada nasıl gerçekleşir
Bu kuralları en iyi şekilde açıklamanın yolu, gerçek bir API kararı göstermek, işte bizimkilerden biri.
Birkaç sürüm önce, IronPDF'nin, HTML'den PDF'ye dönüştürme için daha zengin bir çizim seçenekleri setini desteklemesi gerekiyordu: özel kağıt boyutları, özel kenar boşlukları, CSS medya öykünmesi, üstbilgi ve altbilgi şablonları ve daha fazlası. En basit yaklaşım, mevcut çizim yöntemini yeni seçenekleri kabul etmek üzere değiştirmek olurdu. Bu karar, API'nin basit formunu kullanan her müşteriyi sekteye uğratmış olurdu.
Bağlam olarak, kütüphane standart .NET paket kanalları üzerinden kurulur:
# .NET CLI
dotnet add package IronPdf
# Package Manager Console
Install-Package IronPdf# .NET CLI
dotnet add package IronPdf
# Package Manager Console
Install-Package IronPdfIronPdf NuGet paketi bugüne kadar 18 milyondan fazla kez indirildi, bu da API kararlılığının neden önemli olduğunu gösteriyor: her değişiklik bu kadar çok entegrasyonu etkiler.
Bizim yerine getirdiğimiz yaklaşım:
// The original, three-year-old API. Still works. Still unchanged.
var renderer = new ChromePdfRenderer();
PdfDocument pdf = renderer.RenderHtmlAsPdf("<h1>Hello</h1>");
// New rendering options live on an options object, not in the method signature.
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.CssMediaType = PdfCssMediaType.Print;
renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter { HtmlFragment = "..." };
PdfDocument pdf = renderer.RenderHtmlAsPdf("<h1>Hello</h1>");// The original, three-year-old API. Still works. Still unchanged.
var renderer = new ChromePdfRenderer();
PdfDocument pdf = renderer.RenderHtmlAsPdf("<h1>Hello</h1>");
// New rendering options live on an options object, not in the method signature.
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.CssMediaType = PdfCssMediaType.Print;
renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter { HtmlFragment = "..." };
PdfDocument pdf = renderer.RenderHtmlAsPdf("<h1>Hello</h1>");' The original, three-year-old API. Still works. Still unchanged.
Dim renderer As New ChromePdfRenderer()
Dim pdf As PdfDocument = renderer.RenderHtmlAsPdf("<h1>Hello</h1>")
' New rendering options live on an options object, not in the method signature.
renderer = New ChromePdfRenderer()
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4
renderer.RenderingOptions.MarginTop = 20
renderer.RenderingOptions.CssMediaType = PdfCssMediaType.Print
renderer.RenderingOptions.HtmlHeader = New HtmlHeaderFooter With {.HtmlFragment = "..."}
pdf = renderer.RenderHtmlAsPdf("<h1>Hello</h1>")Kararla ilgili üç gözlem:
- Orijinal
RenderHtmlAsPdf(string html)imzası değişmeden kalır. Yükseltme yapan müşterilerin bir satır kod değiştirmesi gerekmedi. - Yeni yetenekler, kullanıcıların karar verdiği bir seçenek nesnesinde mevcuttur. Yöntemde yeni gereken parametreler yoktur.
RenderingOptionsüzerindeki varsayılanlar, önceki API'ye eşdeğer çıktı üretir. Hiçbir şey yapılandırmayan herkes için davranış değişmez.
Bu, Milan'ın listesindeki kurallar 1, 2 ve 4'ün bir kerede uygulanmasıdır. Ürün evrim geçirdi. Anlaşma geçirmedi.
Gerçekten RenderHtmlAsPdfV2(string html, RenderingOptions options) göndermek cazipti. API referans sayfasında daha düzenli görünürdü. Her müşteriye bir geçiş maliyeti olurdu. Biz farklı bir seçim yaptık.
Tolerant okuyucular
Milan'ın değiştirme-ekleme argümanının diğer yarısı, kullanıcıların da sorumluluk taşıdığıdır. İyi davranışlı bir istemci, anlamadığı alanları görmezden gelmelidir.
.NET'te, System.Text.Json varsayılan olarak bilinmeyen özellikleri yoksayar, ki bu doğru varsayılandır. Risk genellikle iki yerde ortaya çıkar:
- Beklenmedik alanları reddeden katı şemalara sahip oluşturulan SDK'lar
- Tam JSON eşitliğini doğrulayan sözleşme testleri
Her ikisi de "bilinmeyen alanları görmezden geliyoruz" garantisini bir tuzak hattına dönüştürür. Sunucu yeni, isteğe bağlı bir özellik eklediğinde anında CI'niz bozuluyorsa, geri uyumluluğunuz yoktur. Uyumluluk politikası olarak giydirilmiş bir regresyon dedektörünüz var.
Davranış da sözleşmenin bir parçasıdır
Milan'ın, yumuşak silmeden sert silmeye sessizce geçiş yapan DELETE /orders/{id} üzerindeki bölümü, bu konunun en açık yazılı tedavisi.
URL aynı. İstek gövdesi aynı. Yanıt şekli aynı. İşlemin sunucuda yaptığı şey farklı.
Bu, şema farkındalığı ile tespit edilemeyecek en tehlikeli kırılma değişikliği kategorisidir. OpenAPI spesifikasyonu aynıdır. Oluşturulan istemci derlenir. Entegrasyon testleri geçer. Ve "silinmiş siparişler kurtarılabilir" üzerine araçlar geliştiren her tüketici sessizce üretimde veri yok eder.
Kütüphane eşdeğeri, bir yöntemin ne yaptığını yöntem imzasını değiştirmeden değiştirmektir. Açıkça kaçındığımız örnekler:
- Daha önce eş zamanlı olarak boşaltan ve sessizce asenkron-ateşle-birak olan bir
Save()yöntemi - Ham sonuçları döndüren bir OCR yöntemi ve bunlar üzerinde işlem yapmaya başlamak
- Okunamayan girişte hata veren ve boş bir dize döndürmeye başlayan bir barkod okuyucu
Bunların her biri, bir iyileştirme olarak önü giydirilen bir sözleşme kırılmasıdır. Doğru yanıt, Milan'ınki ile aynıdır: yeni bir yöntem veya seçenek eklemek, eski davranışı değiştirmemek ve ancak telemetri güvenli olduğunu gösterdiğinde eski yolu kullanım dışı bırakmak.
Doğrulama sıkılaştırma
Bu kategori sonunda her ekibi etkiler. Aynı hatanın iki varyantı vardır:
- Mevcut isteğe bağlı bir alanı zorunlu hale getirmek
- Yeni bir alan ekleyip başından itibaren zorunlu olarak işaretlemek
Her ikisi de eski istemcileri kırar. Son nokta yolu hareket etmez, ancak daha önce başarılı olan istekler şimdi çalışma zamanında başarısız olur.
Bu hatanın kütüphane versiyonu, gerekli bir yapıcı parametre eklemek veya mevcut isteğe bağlı parametreyi zorunlu hale getirmektir. Mevcut her arayan derleme zamanında kırılır, bu çalışma zamanı hatasından daha iyidir, ancak yine de her kullanıcıya bir geçiş maliyeti bindirir.
Daha güvenli yollar:
- Geçiş penceresi süresince eksik değerleri kabul etmek ve mümkün olduğunda varsayılanları çıkarmak.
- Daha zengin giriş şekli gerektiren yeni bir aşırı yükleme veya oluşturucu eklemek.
- Daha sıkı iş akışı için yeni bir operasyon veya yapıcı getirmek.
Temel kural tutarlıdır: sözleşmeye eklenen her şey isteğe bağlı olmalıdır, ve daha önce isteğe bağlı olan her şey isteğe bağlı kalmalıdır. Gerçekten daha sıkı gereklilikler gerekiyorsa, bunlar yeni bir işlemde yer almalıdır, mevcut olanın sıkılaştırılmasında değil.
Yeni bir işlem neredeyse her zaman yeni bir sürümden daha ucuzdur
Bu, içselleştirilmesi en önemli ilkedir.
Kullanım durumu, mevcut bir son noktanın temiz bir şekilde desteklediğinin ötesine gerçekten geçtiğinde, yaygın refleks, son noktaya bayraklarla yük bindirmektir:
POST /orders?validateOnly=true&includeTaxEstimate=true&reserveInventory=trueYa da daha keskin bir şekilde, değişikliği bir sürümlendirme problemi olarak ilan etmek ve /v2/orders üzerinde çalışmaya başlamak. Her ikisi de genellikle yanlıştır. Daha temiz yaklaşım, mevcut olandı yanında yeni bir işlemdir:
POST /orders
POST /orders/quote
POST /checkout-sessionsHer işlem temiz bir sözleşmeye, ayrı izinlere, bağımsız doğrulamaya ve kendi evrim yoluna sahiptir. Orijinal son nokta basit kalır. API'nin geri kalanı büyük bir sürüm sıçramasına zorlanmaz.
Kütüphane bağlamında, eşdeğer olan mevcut bir yöntemi isteğe bağlı parametrelerle aşırı yüklemek yerine yeni bir yöntem eklemektir. ExtractText() basit metin çıkarıcı olarak kalır. ExtractTextWithLayout() daha zengin varyanta dönüşür. ExtractStructuredDocument() en zengin hale gelir. Sekiz optional parametreye sahip bir yöntem yerine net sözleşmelere sahip üç yöntem daha tercih edilebilir.
Planlı kullanım dışı bırak
Bu, API değişikliği yönetiminin çoğu ekibin görmezden geldiği yarısıdır ve stratejinin işe yarayıp yaramadığını belirleyen yarısıdır.
Gerçekten kullanım dışı bırakma, değişiklik günlüğünde bir not değildir. Dört adım içerir:
- Alana veya uç noktaya OpenAPI açıklamasında (veya .NET dünyasında
[Obsolete]özniteliği ile) kullanım dışı olarak işaretleyin. - Canlı trafiğin yüzeye çıkması için çalışırken kullanım dışı bırakmayı işaretleyin.
- Gerçek bir geçiş kılavuzuna bağlantı verin.
- Telemetri ile kullanımını ölçün ve kaldırmanın güvenli olduğunu belirleyin.
HTTP API'leri için, çalışma zamanı işaretlemesi basittir:
Deprecation: true
Sunset: Wed, 31 Dec 2026 23:59:59 GMT
Link: <https://docs.example.com/migrations/orders-total>; rel="deprecation".NET kütüphaneleri için eşdeğer, [Obsolete("Use NewMethod yerine kullanın. Bu, v2026.x'de kaldırılacak', DiagnosticId = 'IRON001')] attribute paired with a UrlFormat, bir geçiş sayfasına işaret eder. Derleyici uyarısı her tüketicinin yapı çıktısında yer alır, teşhis tanımlayıcı kasıtlı bastırmaya izin verir ve bağlantı tüketicilere belgelenmiş bir geçiş yolu verir.
Telemetri adımı tartışılmazdır. Hala hangi müşterilerin eski yönteme bağımlı olduğunu bilmeden kaldırmak tahmin işine dönüşür. Sonuç, aktif entegrasyonları kıran zamanından önce bir kaldırma veya kullanım dışı bırakmanın amacını yenilgiye uğratan süresiz taşıma maliyetidir.
Sürümleme ne zaman doğru bir çağrıdır
Milan sürümlemeye karşı değildir, biz de değiliz. Sürümleme uygun olduğunda:
- Eski ve yeni semantik kesinlikle beraber var olamaz
- Kaynak modeli köklü bir şekilde değişti
- Uyumluluk kuralları, kimsenin hakkında akıl yürütemeyeceği bir sözleşmeyi zorlayacaktır
Amaç, sürümlemeyi tamamen önlemek değildir. Amaç, sürümlemeye, birlikte var olma başarısız olduğunda başvurmaktır, masadaki ilk fikir olduğu için değil.
Sürümleme gerekli olduğunda, gerçek bir kullanım dışı bırakma süreci ile eşleştirilmelidir. Zor olan iş v2 göndermek değil. Zor olan iş, tüketicileri v1 dışında bırakmaktır.
Karar kuralı
Milan'ın çerçevesini uygulamak doğru olandır:
- Yerine ekleyebilir miyim?
- Eski ve yeni sözleşmeler bir geçiş penceresinde beraber var olabilir mi?
- Eski bir işlemi değiştirmek yerine yeni bir işlem başlatabilir miyim?
- Eski şekli belgelerle, üstbilgilerle ve telemetri ile kullanım dışı bırakabilir miyim?
Dördüne de yanıt evetse, yeni bir sürüm büyük olasılıkla gereksizdir. Yanıt hayırsa ve iki dünya kesinlikle beraber var olamazsa, dikkatli bir şekilde sürümleyin.
Sözleşmeleri evrimleşecek şekilde tasarlayın. Tüketicileri bugünün kodu yerine uzun ömürlü entegrasyonlar olarak değerlendirin. Uyumluluğun gerçekten tükendiği durumlar için sürümlemeyi ayırın.
Tam parça için, daha uzun örnekler de dahil olmak üzere, Milan'ın orijinal gönderisini okuyun.
.NET kütüphanesine bağımlı birini seçerken sormaya değer soru, Milan'ın gönderisinin etrafında inşa edildiği sorudur: bu kütüphane, üç yıl sonra entegre ettiğim API gibi görünecek mi?
Bu, her sürümde yanıtlamaya çalıştığımız sorudur. 2020'den basit çağrılar hala çalışıyor. Yeni yetenekler bunlarla yan yana duruyor, isteğe bağlı ve ekleyici. Zorunlu büyük sürüm geçişleri yok.
Eğer bu kütüphane tasarımı yaklaşımı ihtiyacını karşılıyorsa, ücretsiz 30 günlük denemeye başlayın ve API referansını kendiniz inceleyin. Beş dakikalık hızlı başlangıç, kurulum, lisans aktivasyonu ve ilk işlenmiş PDF üzerinden geçer. Paket, herhangi bir .NET projesine tek bir komut mesafesindedir:
NuGet'in tercih edilen yol olmadığı ortamlar için, doğrudan indirme DLL ve Windows yükleyici sağlar.
