Mert Özen Her Satırda Daha İleri
Backend External Service Integration ve Resilience — #09

HttpClient ve Resilience'ta Sürümlerle Gelenler: Microsoft.Extensions.Http.Polly'den Http.Resilience'a, Polly v7'den v8'e ve .NET 11

Mert Özen 14 Kas 2026 13 dk 12 görüntülenme
HttpClient ve Resilience'ta Sürümlerle Gelenler: Microsoft.Extensions.Http.Polly'den Http.Resilience'a, Polly v7'den v8'e ve .NET 11

Microsoft.Extensions.Http.Polly'nin yerini Microsoft.Extensions.Http.Resilience aldı ve Polly v8'de policy'ler tek bir ResiliencePipeline'a dönüştü; eski kod AsAsyncPolicy köprüsüyle adım adım taşınabilir. .NET 11 de request compression, deneysel bir connection eviction callback'i ve tipli DNS kayıtları getiriyor.

Sürüm notu: Bu yazıdaki kodlar .NET 10, Polly 8.9, Microsoft.Extensions.Http.Resilience 10.11 ve Microsoft.Extensions.Http.Polly 10.0 ile; .NET 11 örnekleri .NET 11 SDK 11.0.100 ile derlenip denenmiştir.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) bu seride hep yeni API'lerle yazıldı. Ama gerçek hayatta çoğu zaman yıllar önce yazılmış bir entegrasyon kodunu devralırsın. Üç soru:

Birincisi: projende Microsoft.Extensions.Http.Polly var. dotnet list package --deprecated bu paketi deprecated olarak listeler mi?

İkincisi: Polly'yi 7.2'den 8.9'a yükselttin. Policy.Handle<HttpRequestException>().WaitAndRetryAsync(...) gibi v7 kodu derlenmeye devam eder mi?

Üçüncüsü: .NET 11'deki SocketsHttpHandler.ShouldEvictConnection callback'i her istekten önce mi çağrılır?

Üçünün cevabı da "hayır", "evet", "hayır" ve aşağıdaki gerçek çalıştırmalardan geliyor. Bu son yazıda HttpClient ve resilience tarafında hangi sürümle ne geldiğine bakıyor, eski kodu tanımayı ve adım adım taşımayı öğreniyor, sonra .NET 11'in getirdiklerini çalıştırıyoruz.

Kısa Tarihçe: Hangi Sürümle Ne Geldi

SürümNe geldiBu serideki karşılığı
.NET Core 2.1 (2018)IHttpClientFactory; Polly v7 ile AddPolicyHandler (Microsoft.Extensions.Http.Polly)02. yazı; bu yazıdaki geçiş
.NET 6WebRequest, HttpWebRequest, WebClient obsolete (SYSLIB0014)Eski kodu tanımak
Polly 8.0 (Eylül 2023)ResiliencePipeline, Polly.Core, telemetri04. yazı
.NET 8 (Kasım 2023)Microsoft.Extensions.Http.Resilience 8.0, ConfigureHttpClientDefaults, HttpClient metric'leri02, 03 ve 05. yazılar
Polly 8.3 (Şubat 2024)Simmy'nin chaos strategy'leri Polly'nin içinde05. yazı
.NET 9Keyed client (AddAsKeyed), factory'de varsayılan SocketsHttpHandler, header değerlerinin loglarda gizlenmesi02. yazı
.NET 11 (Kasım 2026)Request compression, connection eviction callback'i, InitialHttp2MaxConcurrentStreams, tipli DNS kayıtlarıBu yazının son bölümü

Tarihleri NuGet'teki yayın kayıtlarından aldım: Microsoft.Extensions.Http'nin ilk kararlı sürümü 2.1.0 Mayıs 2018'de, Polly 8.0.0 28 Eylül 2023'te, Microsoft.Extensions.Http.Resilience 8.0.0 de .NET 8 ile aynı gün, 14 Kasım 2023'te yayımlanmış. ConfigureHttpClientDefaults'un .NET 8 ile geldiğini de derleyerek doğruladım: aynı kod Microsoft.Extensions.Http 7.0.0'a karşı CS1061 veriyor, 8.0.0'a karşı derleniyor.

.NET 9'un iki değişikliği, eski kodu yeni sürüme taşıyanları en çok şaşırtanlar. Factory artık primary handler olarak HttpClientHandler değil SocketsHttpHandler kullanıyor ve PooledConnectionLifetime'ı HandlerLifetime'a eşitliyor; primary handler'ı HttpClientHandler'a cast eden kod InvalidCastException alıyor. Factory'nin log'larındaki header değerleri de, RedactLoggedHeaders çağrılmamışsa, artık varsayılan olarak * ile gizleniyor. İkisini de 02. yazıda gerçek çıktılarla gördük.

Eski Kodu Tanımak

Devraldığın bir entegrasyonda şu izlerden biri varsa kod büyük ihtimalle .NET Core 2.1 öncesinden ya da Polly v7 döneminden kalmadır:

  • WebRequest.Create, HttpWebRequest, WebClient: .NET Framework döneminin API'leri.
  • Her çağrıda using var client = new HttpClient(): socket exhaustion riski (02. yazı).
  • AddPolicyHandler, AddTransientHttpErrorPolicy, HttpPolicyExtensions.HandleTransientHttpError(): Microsoft.Extensions.Http.Polly.
  • Policy.WrapAsync, PolicyRegistry, IAsyncPolicy<HttpResponseMessage>: Polly v7 API'si.

İlkini derleyici zaten söylüyor. WebRequest.Create ve new WebClient() içeren küçük bir dosyayı .NET 10 ile derlediğimde:

Program.cs(3,31): warning SYSLIB0014: 'WebRequest.Create(string)' is obsolete: 'WebRequest, HttpWebRequest, ServicePoint, and WebClient are obsolete. Use HttpClient instead.' (https://aka.ms/dotnet-warnings/SYSLIB0014) [Old.csproj]
Program.cs(5,17): warning SYSLIB0014: 'WebClient.WebClient()' is obsolete: 'WebRequest, HttpWebRequest, ServicePoint, and WebClient are obsolete. Use HttpClient instead.' (https://aka.ms/dotnet-warnings/SYSLIB0014) [Old.csproj]

Bu uyarılar .NET 6'dan beri var. Önemli bir ayrıntı daha: ServicePointManager üzerindeki ayarlar (bağlantı limiti, TLS (Transport Layer Security; HTTPS bağlantısını şifreleyen protokol) sürümü, DNS yenileme süresi) artık HttpClient'ı etkilemiyor. Eski kodda bu ayarlarla çözülmüş bir sorun varsa, karşılığını SocketsHttpHandler'ın property'lerinde aramak gerekiyor.

Microsoft.Extensions.Http.Polly'den Http.Resilience'a

Microsoft.Extensions.Http.Polly'nin NuGet sayfasındaki açıklama paketin deprecated olduğunu ve yerine Microsoft.Extensions.Resilience ya da Microsoft.Extensions.Http.Resilience kullanılmasını söylüyor. Ama paket hâlâ her .NET sürümüyle yayımlanıyor (son sürümü 11.0.0, .NET 11 ile aynı gün) ve NuGet'in resmî deprecation işareti onda yok. Tahmin sorusunun birinci cevabı bu yüzden "hayır". Migration demolarının durduğu Bookstore.ResilienceLab projesinde:

dotnet list samples/Bookstore.ResilienceLab package --deprecated --include-transitive
Project `Bookstore.ResilienceLab` has the following deprecated packages
   [net10.0]: 
   Transitive Package                                     Resolved   Reason(s)   Alternative                                  
   > Polly.Extensions.Http                                3.0.0      Legacy      Microsoft.Extensions.Http.Resilience >= 0.0.0

Listede WireMock.Net'in getirdiği birkaç başka transitive paket de vardı; Polly'yle ilgili satırı bıraktım. İşaret, Microsoft.Extensions.Http.Polly'nin kendisinde değil, onun bağımlılığı olan Polly.Extensions.Http 3.0.0'da çıkıyor ve alternatif olarak Http.Resilience'ı gösteriyor. Yani --include-transitive olmadan bakan biri hiçbir uyarı görmüyor. Paket Polly 7.2.4 ve üstüne bağlı; 8.x ile de çalışıyor (bu demolar Polly 8.9.1 ile koştu), ama v7 API'sini kullandığı için yalnız Polly.Core'a geçmek onu kaldırmayı gerektiriyor.

Aynı kargo client'ını iki şekilde kaydettim. Önce eski hâli:

// Önce: v7 policy'siyle AddPolicyHandler, .NET Core 2.1-7 dönemindeki birçok projede olduğu gibi
services.AddHttpClient("legacy-carrier", c => c.BaseAddress = server.Url)
    .AddPolicyHandler(HttpPolicyExtensions
        .HandleTransientHttpError()
        .WaitAndRetryAsync(2, attempt => TimeSpan.FromMilliseconds(200 * attempt),
            onRetry: (outcome, delay, attempt, _) =>
                Console.WriteLine($"v7 retry {attempt} after {(int?)outcome.Result?.StatusCode}, waiting {delay.TotalMilliseconds} ms")))
    .AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(2)));

Sonra yeni hâli:

// Sonra: tek bir resilience handler, strategy'ler dıştan içe eklenir
services.AddHttpClient("legacy-carrier", c => c.BaseAddress = server.Url)
    .AddResilienceHandler("carrier", pipeline => pipeline
        .AddRetry(new HttpRetryStrategyOptions
        {
            MaxRetryAttempts = 2,
            BackoffType = DelayBackoffType.Linear,
            Delay = TimeSpan.FromMilliseconds(200),
            UseJitter = false,
            OnRetry = args =>
            {
                Console.WriteLine($"v8 retry {args.AttemptNumber + 1} after {(int?)args.Outcome.Result?.StatusCode}, waiting {args.RetryDelay.TotalMilliseconds} ms");
                return default;
            }
        })
        .AddTimeout(TimeSpan.FromSeconds(2)));

İki demo da iki kez 500, sonra 200 dönen aynı fake sunucuya gidiyor:

== migration-v7
v7 retry 1 after 500, waiting 200 ms
v7 retry 2 after 500, waiting 400 ms
v7 -> 200 after 3 attempts
== migration-v8
v8 retry 1 after 500, waiting 200 ms
v8 retry 2 after 500, waiting 400 ms
v8 -> 200 after 3 attempts

Davranış aynı, ama birkaç fark var ve taşırken gözden kaçıyor. v7'de iki AddPolicyHandler iki ayrı DelegatingHandler demek; ilk eklenen dışta. v8'de tek handler ve sıra builder'daki sıra; bu da 04. yazıdaki "ilk eklenen en dışta" kuralı. HandleTransientHttpError() 5xx, 408 ve HttpRequestException'ı ele alıyor; HttpRetryStrategyOptions'ın varsayılan ShouldHandle'ı bunlara 429'u ve timeout exception'ını da ekliyor. v7'deki attempt sayacı 1'den, v8'deki AttemptNumber 0'dan başlıyor. Ve yeni kodda jitter'ı bilerek kapattım; çıktılar karşılaştırılabilsin diye. Production'da v8'in varsayılanı olan jitter'ı açık bırak.

Eski kodu taşırken birebir çeviri yerine 03. yazıdaki standard pipeline'ı da düşün. Eski projelerin çoğunda timeout, retry ve circuit breaker zaten birlikte kurulmuştur ve AddStandardResilienceHandler bunların doğrulanmış varsayılanlarını tek satırda veriyor.

Polly v7'den v8'e: Kavramlar Nereye Gitti

Http.Polly'yi bırakmak, v7 API'sini bırakmanın bir parçası. Polly'nin geçiş rehberindeki eşleştirmelerden en sık karşılaşacakların:

v7v8
IAsyncPolicy, ISyncPolicy ve generic hâlleriResiliencePipeline, ResiliencePipeline<T>
Policy.WrapAsync(...)Builder'da art arda Add* çağrıları
WaitAndRetryAsyncAddRetry ve RetryStrategyOptions
Policy.BulkheadAsyncAddConcurrencyLimiter
Advanced circuit breakerAddCircuitBreaker; klasik (ardışık hata sayan) breaker yok
Isolate(), Reset()CircuitBreakerManualControl.IsolateAsync(), CloseAsync()
ContextResilienceContext ve ResiliencePropertyKey<T>
PolicyRegistryResiliencePipelineRegistry, okuma için ResiliencePipelineProvider

En önemli fark felsefede: v7'de her policy bağımsız bir nesneydi ve onları Wrap ile birleştiriyordun; v8'de tek bir pipeline var ve strategy'ler onun parçaları. Telemetri (log ve metric), TimeProvider desteği ve DI (Dependency Injection; bağımlılıkların dışarıdan verilmesi) entegrasyonu da v8'de pipeline seviyesinde geliyor; 05. yazıdaki metric'lerin hepsi bu sayede ek kod yazmadan çıktı.

Tahmin sorusunun ikinci cevabı Polly'nin paket yapısında. v8'de Polly.Core yalnız yeni API'yi içeriyor, Polly paketi ise hem Polly.Core'u hem de v7 API'sini taşıyor. Rehber de bu yüzden önce Polly paketini 8.x'e yükseltmeyi, policy'leri zamanla taşımayı ve eski kod tamamen bittiğinde Polly.Core'a geçmeyi öneriyor. Klasik circuit breaker'ın v8'de karşılığı olmaması ise bir geçişin davranış değiştirdiği nadir yerlerden biri: "art arda 5 hata" mantığıyla kurulmuş bir breaker'ı v8'de hata oranı ve minimum istek sayısıyla yeniden düşünmek gerekiyor.

Kademeli Geçiş: v8 Strategy'yi v7 Zincirinde Kullanmak

Büyük bir codebase'i tek pull request'te taşımak gerçekçi değil. Polly v8 bunun için bir köprü veriyor: ResiliencePipeline'ı AsAsyncPolicy() ile v7'nin IAsyncPolicy'sine çeviriyor ve eski Policy.WrapAsync zincirine koyabiliyorsun. Aşağıdaki demoda yeni yazılan bir v8 circuit breaker'ı, henüz taşınmamış v7 retry policy'siyle aynı client'ta çalışıyor:

// Arada: geri kalanı taşınırken, eski v7 policy zincirinin içinde yeni bir v8 circuit breaker
var breaker = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
    {
        FailureRatio = 0.5,
        MinimumThroughput = 3,
        BreakDuration = TimeSpan.FromSeconds(30)
    })
    .Build()
    .AsAsyncPolicy();
var retry = HttpPolicyExtensions.HandleTransientHttpError()
    .Or<BrokenCircuitException>()
    .RetryAsync(5, onRetry: (outcome, attempt) =>
        Console.WriteLine($"v7 retry {attempt}: {(outcome.Exception is { } ex ? ex.GetType().Name : ((int)outcome.Result.StatusCode).ToString())}"));

services.AddHttpClient("legacy-carrier", c => c.BaseAddress = server.Url)
    .AddPolicyHandler(Policy.WrapAsync(retry, breaker));

Sunucu her isteğe 500 dönüyor:

v7 retry 1: 500
v7 retry 2: 500
v7 retry 3: 500
v7 retry 4: BrokenCircuitException
v7 retry 5: BrokenCircuitException
gave up: BrokenCircuitException, server saw 3 requests

v8 breaker üç istekten sonra açıldı; v7 retry'ı iki deneme daha yaptı ama sunucu yalnız 3 istek gördü, çünkü açık breaker çağrıyı ağa çıkmadan kesti. Sıranın kuralı burada da geçerli: WrapAsync(retry, breaker) retry'ı dışa, breaker'ı içe koyuyor. Bu demoda bir şey daha görünüyor: retry'ın BrokenCircuitException'ı da tekrar denemesi, açık bir breaker'a karşı boşuna dönen denemeler demek. Taşıma sırasında bu tür eski kararları da gözden geçir.

Bookstore gibi bir codebase'te sıra şöyle olurdu: önce Polly paketini 8.x'e yükselt ve testlerin geçtiğini gör; yeni yazılan her şeyi v8 ile yaz, gerekiyorsa AsAsyncPolicy() ile eski zincire bağla; client'ları tek tek AddResilienceHandler ya da AddStandardResilienceHandler'a taşı; en sonda Microsoft.Extensions.Http.Polly'yi ve Polly paketini kaldırıp yalnız Polly.Core (ve gerekiyorsa Polly.Extensions) bırak. Her adımda 05. yazıdaki testler, davranışın değişmediğini söyleyen güvencen.

.NET 11: HttpClient Tarafındaki Yenilikler

.NET 11, 10 Kasım 2026'da yayımlandı ve Microsoft Learn'deki "What's new in .NET 11 libraries" sayfasının (ms.date 10 Kasım 2026) networking başlığındaki yeniliklerden dördü doğrudan HttpClient ve dış servis çağrılarıyla ilgili. Bu seri .NET 10 LTS (Long Term Support; uzun süreli destek) tabanında kaldığı için örnekleri ayrı bir projeye koydum: samples/Bookstore.Net11Lab, kendi global.json'ıyla .NET 11 SDK'sını istiyor ve Bookstore.slnx'e dahil değil. Microsoft.Extensions.Http.Resilience'ın 11.0 sürümü de .NET 11 ile birlikte çıktı; seri 10.x'te kalıyor.

cd samples/Bookstore.Net11Lab
dotnet run -- compression

Birincisi request compression. GZipCompressedContent, BrotliCompressedContent ve ZstandardCompressedContent, herhangi bir HttpContent'i sarıp Content-Encoding header'ını koyuyor ve içeriği gönderirken sıkıştırıyor. Kurumsal bir okulun 30 satırlık toplu siparişini, gelen byte'ları ve açılmış JSON'un boyutunu raporlayan lokal bir endpoint'e gönderdim:

HttpContent[] contents =
[
    JsonContent.Create(order),
    new GZipCompressedContent(JsonContent.Create(order), CompressionLevel.Optimal),
    new BrotliCompressedContent(JsonContent.Create(order), CompressionLevel.Optimal),
    new ZstandardCompressedContent(JsonContent.Create(order), CompressionLevel.Optimal)
];
JsonContent                  Content-Encoding:       -> {"encoding":"none","wireBytes":2855,"jsonBytes":2855}
GZipCompressedContent        Content-Encoding: gzip  -> {"encoding":"gzip","wireBytes":347,"jsonBytes":2855}
BrotliCompressedContent      Content-Encoding: br    -> {"encoding":"br","wireBytes":254,"jsonBytes":2855}
ZstandardCompressedContent   Content-Encoding: zstd  -> {"encoding":"zstd","wireBytes":292,"jsonBytes":2855}

Tekrar eden alanlarla dolu bir JSON'da 2.855 byte, Brotli ile 254 byte'a indi. Karşı tarafın sıkıştırılmış request'i kabul etmesi gerekiyor; bu da çoğu dış servis için varsayılan değil. ASP.NET Core tarafında bunun için 7.0'dan beri request decompression middleware'i var ve .NET 11'de desteklediği formatlara zstd da eklendi. Dış bir servise sıkıştırılmış gövde göndermeden önce dokümanında Content-Encoding desteğini ara; yoksa 400 ya da 415 alırsın.

İkincisi SocketsHttpHandler.ShouldEvictConnection. Şimdiye kadar bağlantıların ömrünü yalnız PooledConnectionLifetime ile, yani sabit bir süreyle sınırlayabiliyorduk. Yeni callback bağlantının yaşını, uzak adresini, DnsEndPoint'ini ve HTTP sürümünü görüp "bu bağlantıyı bırak" kararını sana bırakıyor; örneğin DNS kaydı değişmiş bir endpoint'e açık bağlantıları kapatmak için. İlk derlemede şu hatayı aldım:

samples/Bookstore.Net11Lab/Program.cs(97,9): error SYSLIB5008: 'System.Net.Http.SocketsHttpHandler.ShouldEvictConnection' is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. (https://aka.ms/dotnet-warnings/SYSLIB5008) [samples/Bookstore.Net11Lab/Bookstore.Net11Lab.csproj]

Property .NET 11'de [Experimental] olarak işaretli ve böyle bir API'yi kullanmak varsayılan olarak derleme hatası veriyor; hatanın kendisi de bastırmadan devam edilemeyeceğini söylüyor. Kodda bilerek ve yalnız o satırlar için bastırdım:

var handler = new SocketsHttpHandler
{
    // .NET 11: sabit bir PooledConnectionLifetime yerine bağlantı başına karar
    // 11.0'da deneysel (SYSLIB5008): şekli hâlâ değişebilir
#pragma warning disable SYSLIB5008
    ShouldEvictConnection = (context, _) =>
    {
        var evict = context.Age > TimeSpan.FromSeconds(1);
        Console.WriteLine($"  check connection {context.ConnectionId}, HTTP/{context.HttpVersion}, age {context.Age.TotalMilliseconds:0} ms -> evict: {evict}");
        return Task.FromResult(evict);
    },
#pragma warning restore SYSLIB5008
    // .NET 11: sunucunun SETTINGS frame'i gelene kadar yeni HTTP/2 bağlantısındaki ilk patlamayı sınırla
    InitialHttp2MaxConcurrentStreams = 20
};

Üç saniye arayla üç istek gönderdim. Callback'i her istekte çağrılır diye bekliyordum; öyle olmadı:

[0.0 s] request 1: {"connection":"0HNQ26UERUQHD"}
[3.0 s] request 2: {"connection":"0HNQ26UERUQHD"}
  check connection 0, HTTP/1.1, age 4999 ms -> evict: True
[6.0 s] request 3: {"connection":"0HNQ26UERUQHE"}

İkinci istek, yaşı 1 saniyeyi geçmiş bağlantıyı kontrol edilmeden kullandı. Callback 5. saniyede, pool'un bekleyen bağlantıları periyodik olarak gözden geçirdiği anda çağrıldı; bağlantı bırakıldı ve üçüncü istek yeni bir bağlantı açtı. Ayrı bir denemede PooledConnectionIdleTimeout'u 4 saniyeye indirince kontrollerin aralığı yaklaşık 1 saniyeye düştü. API dokümanı bu zamanlamayı anlatmıyor ve property deneysel; production kodunu bu davranışa bağlamadan önce kendi senaryonda ölç.

Üçüncüsü InitialHttp2MaxConcurrentStreams. Yeni bir HTTP/2 bağlantısında, sunucu kendi limitini SETTINGS frame'iyle bildirene kadar istemcinin aynı anda açacağı stream sayısını sınırlıyor; varsayılanı 100. Limiti düşük olduğunu bildiğin bir sağlayıcıya bağlantı açılır açılmaz yüzlerce istek gönderiyorsan bu ilk dalgayı yumuşatıyor. Lokal sunucumuz HTTP/1.1 olduğu için bunu ölçmedim; yalnız derlendiğini ve ayarlanabildiğini gösterdim.

Dördüncüsü tipli DNS kayıtları. Dns.ResolveMxAsync, ResolveSrvAsync, ResolveTxtAsync gibi method'lar kayıtları TTL (Time To Live; kaydın cache'te ne kadar tutulabileceği) ve yanıt koduyla birlikte dönüyor; DnsResolver sınıfı da kendi DNS sunucu listeni vermeni sağlıyor. Bookstore'da kurumsal müşteri kaydında e-posta alan adının gerçekten e-posta kabul edip etmediğine bakmak için kullanılabilir:

MX example.org: NoError, records: 1, negative cache TTL: 00:00:00
  preference 0, exchange '.', ttl 00:02:43
MX no-such-school.example: NxDomain, records: 0, negative cache TTL: 00:14:01

example.org'un tek MX (Mail Exchanger; bir alan adına gelen e-postayı kabul eden sunucuyu gösteren DNS kaydı) kaydının hedefi .: bu "null MX", alan adının hiç e-posta kabul etmediğini söylüyor. Var olmayan alan adı ise NxDomain ve negative cache süresiyle döndü. Bu bilgiyi eskiden Dns.GetHostAddresses ile alamıyor, ayrı bir DNS kütüphanesine ihtiyaç duyuyordun.

Üretimde Dikkat

Deneysel API'leri production'a alırken bunu bilinçli yap. SYSLIB5008 gibi bir diagnostic'i projenin tamamında değil, yalnız kullandığın satırda bastır; bir sonraki .NET sürümüne geçerken bu satırları tek tek gözden geçirmen gerekecek.

Geçişte davranış farklarını testle yakala. v7'den v8'e geçerken retry'ın ele aldığı status code'lar, sayaçların başlangıcı, jitter ve breaker'ın türü değişebiliyor. 05. yazıdaki gibi karşı tarafın gördüğü istek sayısını sayan testler, bu farkları pull request'te görünür kılıyor.

Request compression'ı ölçerek aç. Küçük gövdelerde sıkıştırmanın CPU maliyeti kazancından büyük olabilir; tekrar eden alanlarla dolu büyük toplu gövdelerde ise yukarıdaki gibi on kata yakın küçülme mümkün.

Sık Düşülen Tuzaklar

Yalnız doğrudan referanslara bakmak. Microsoft.Extensions.Http.Polly için uyarı ancak --include-transitive ile ve bağımlılığı üzerinden görünüyor.

Polly'yi 8.x'e yükseltince v7 kodunun bittiğini sanmak. Polly paketi v7 API'sini taşımaya devam ediyor; kod derlendiği için geçiş yapılmış gibi görünür.

Policy sırasını taşırken ters çevirmek. WrapAsync(a, b) ile builder'da a'yı önce eklemek aynı sırayı verir; iki ayrı AddPolicyHandler'ı tek bir AddResilienceHandler'a toplarken sırayı kontrol et.

Klasik circuit breaker'ı birebir taşımaya çalışmak. v8'de karşılığı yok; eşikleri hata oranı ve minimum istek sayısı olarak yeniden tasarla.

Küçük Bir Deneme

★☆☆ Kendi projende dotnet list package --deprecated --include-transitive çalıştır. Hangi satırlar doğrudan referanslarından, hangileri transitive bağımlılıklardan geliyor?

★★☆ migration-v7 demosundaki retry'ı AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(...)) ile yaz, sonra AddStandardResilienceHandler'a taşı. Fake sunucu 429 döndüğünde iki sürümün davranışı nasıl farklılaşıyor?

★★★ Bookstore.Net11Lab'deki ShouldEvictConnection callback'ini, context.DnsEndPoint'teki host adının güncel IP'leri arasında context.RemoteEndPoint yoksa bağlantıyı bırakacak şekilde yaz (ipucu: Dns.ResolveAddressesAsync). Callback içinde DNS sorgusu yapmanın maliyetini nasıl sınırlarsın?

Bu yazıyla seri bitiyor. Bookstore'un ödeme sağlayıcısı, kargo firması ve kurumsal müşterileriyle konuşan kodu artık zaman sınırlı, idempotent, testli ve gözlemlenebilir; geçmişten gelen kodu da yeni API'lere adım adım taşıyabiliyoruz. Dış servisle konuşmanın bir sonraki adımı, mümkün olan her yerde onu kullanıcının isteği içinde hiç çağırmamak: siparişi alıp ödeme, fatura ve kargo adımlarını mesajlarla yürütmek. Bunun altyapısını Message Queue'larla Çalışmak serisinde, akışın tasarımını da Messaging ve Event-Driven Architecture serisinde bulabilirsin.