Polly ile Resiliency Patterns
Günümüzde modern yazılım mimarilerinde, özellikle microservice mimarilerinde, sistemler artık tek başına çalışmıyor. Uygulamalar; farklı…
Polly ile Resiliency Patterns
Günümüzde modern yazılım mimarilerinde, özellikle microservice mimarilerinde, sistemler artık tek başına çalışmıyor. Uygulamalar; farklı servisler, API’ler ve üçüncü parti sistemlerle sürekli iletişim halindeler. Bu yoğun iletişim ortamında yaşanan tek bir ağ problemi, zincirleme hatalara yol açabiliyor.

Basit bir örnek üzerinden düşünelim. Anlık döviz kurlarını çeken bir API servisinden EUR → TRY kurunu aldığımız bir senaryomuz olsun:
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync(
"https://api.exchangerate.host/latest?base=EUR&symbols=TRY");
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var document = JsonDocument.Parse(json);
var rate = document
.RootElement
.GetProperty("rates")
.GetProperty("TRY")
.GetDecimal();
Console.WriteLine($"Güncel EUR → TRY kuru: {rate}");
Peki bu servis anlık olarak çökerse, timeout olursa ya da 500 hatası dönerse ne olur?
EnsureSuccessStatusCode() satırı bir exception fırlatır ve eğer bu hata uygulama seviyesinde yönetilmiyorsa, uygulamamızın çökmesine kadar gidebilen bir süreci tetikleyebilir. Daha da kötüsü, bu çağrı başka servisler tarafından da kullanılıyorsa, tek bir hata zincirleme sistem problemlerine dönüşebilir.
İşte tam bu noktada Resiliency (Dayanıklılık) Patterns devreye giriyor. .NET dünyasında bu desenleri pratik ve temiz bir şekilde uygulamamızı sağlayan en güçlü araçlardan biri ise Polly kütüphanesi. Ancak Polly’ye geçmeden önce, Resiliency (Dayanıklılık) Patterns nedir? Öncelikle bunun üzerinde duralım.
Resiliency Nedir?
Resiliency’yi en basit haliyle şöyle düşünebiliriz: Sistem hata aldığında patlayıp tamamen durmasın. Hatalar olacak; network kopacak, dış servis cevap vermeyecek, bazen her şey yolundayken bile 500 hatalarıyla karşılaşacağız. Buradaki mesele hataları yok etmeye çalışmak değil, zaten bu çoğu zaman mümkün de değil. Asıl önemli olan, bu hatalarla karşılaştığımızda sistemi kontrol altında tutabilmek ve kullanıcıya mümkün olduğunca az hissettirerek yoluna devam edebilmek.
Resiliency ile alakalı kafamızda ne işe yaradığını netleştirmiş olduk. Peki biz bunu uygulama tarafında nasıl kullanırız? Bu noktada .NET ekosisteminde yaygın olarak kullanılan Polly kütüphanesinden bahsedeceğim ve bu kütüphaneyi kullanarak birkaç örnek göstereceğim. Örneklere geçmeden önce, Polly nedir ona bakalım; ardından Polly’nin bize sunduğu Resiliency Pattern / Policy tiplerine kısaca birlikte göz atalım.
Polly Nedir?
Polly, .NET için geliştirilmiş bir resilience (dayanıklılık) ve transient fault handling (geçici hata yönetimi) kütüphanesidir. Uygulamanın dış dünyayla yaptığı çağrılarda örneğin HTTP istekleri, mikroservis iletişimleri veya üçüncü parti servisler oluşabilecek geçici hatalara karşı sistemi daha dayanıklı hâle getirmeyi amaçlar.

Polly sayesinde; belirli hatalarda isteği yeniden deneyebilir, sürekli hata veren servisleri geçici olarak devre dışı bırakabilir, belirli bir sürede cevap gelmezse işlemi sonlandırabilir ya da hata anında kontrollü bir geri dönüş sağlayabiliriz. Tüm bunları yaparken uygulamanın tamamen çökmesini engeller ve hataların sistem geneline yayılmasının önüne geçer.
Aşağıdaki GitHub linkinden Polly’yi projene nasıl kuracağınızı ve kullanacağınızı adım adım öğrenebilirsiniz :)
Şimdi Polly kütüphanesinin bize sunduğu policy tiplerini listeleyelim:
- Retry
- Circuit Breaker
- Fallback
- Timeout
- Cache
- Bulkhead
Bu policy’lerin her biri farklı bir probleme çözüm üretir. Bazıları geçici hatalarla başa çıkmayı hedeflerken, bazıları sistemi korumaya, bazıları da kullanıcıya daha kontrollü bir deneyim sunmaya odaklanır. Şimdi sırasıyla bu policy’lerin ne işe yaradığını birlikte inceleyeceğim.
Retry Pattern
Retry yöntemi, hata durumlarıyla karşılaşıldığında isteklerin belirli kurallar çerçevesinde tekrar gönderilmesini sağlar. Genellikle network kopmaları, kısa süreli servis kesintileri ve timeout gibi geçici problemlerde oldukça etkilidir.
Bu noktada retry davranışının kurallarını tamamen siz belirlersiniz. Örneğin bir isteğin en fazla 5 kez tekrar denenmesi ya da denemeler arasında bekleme süresinin kademeli olarak artırılması (ilk denemeden sonra 2 saniye, bir sonrakinde 4 saniye gibi) şeklinde senaryolar tanımlayabilirsiniz.
var retryPolicy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.WaitAndRetryAsync(
retryCount: 3, // toplam 3 kez dene
sleepDurationProvider: attempt => TimeSpan.FromSeconds(2) // her denemede 2 saniye bekle
);
Circuit Breaker
Retry pattern’ını öğrendik; peki belirlediğimiz kurallar dahilinde istekleri tekrar tekrar gönderdik ama hâlâ istediğimiz yanıtı alamıyorsak ne olacak? İşte bu noktada Circuit Breaker pattern devreye giriyor.
Örnek olarak, A API’sinden B API’sine istek attığınızı düşünelim. Belirli bir süre boyunca yanıt alamadınız ve hata/istek sınırına ulaştınız. Bu durumda sürekli olarak istek göndermeye devam etmenin bir anlamı yoktur; hem karşı servise hem de client tarafına gereksiz yük oluşturur.
Circuit Breaker, belirlenen hata veya istek eşiği aşıldığında artık istek göndermeyi durdurur. Böylece sistem kendini korumaya alır ve kullanıcıya durumu açıklayan anlamlı bir mesaj döndürülebilir.
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 5, // 5 hata sonrası devreyi aç
durationOfBreak: TimeSpan.FromSeconds(30) // 30 saniye boyunca istekleri kes
);
Circuit Breaker Durumları
Closed: Bu aşamada tüm istekler sisteme iletilir. Eğer belirlenen hata oranı aşılırsa Circuit Breaker Open durumuna geçer.
Open: Bu durumda artık dış servise istek gönderilmez. Amaç, hatalı servisi zorlamamak ve sistemi gereksiz yükten korumaktır.
Half-Open: Belirli bir süre sonra Circuit Breaker, servisin toparlanıp toparlanmadığını kontrol etmek için sınırlı sayıda isteğe izin verir. Eğer bu istekler başarılı olursa durum tekrar Closed’a alınır; hata alınmaya devam ederse Open durumuna geri dönülür.
Fallback
Bir hata durumunda devreye giren alternatif bir davranışı tanımlar. Örnek vermek gerekirse bir servisten veri alınamazsa cache’ten veri dönmek, kullanıcıya sabit bir bilgilendirme mesajı göstermek ya da önceden tanımlanmış bir response üretmek gibi senaryolarda kullanılır.
Buradaki asıl amaç, kullanıcıya doğrudan bir hata göstermek yerine uygulamanın akışını mümkün olduğunca kesmeden devam ettirmeyi amaçlar.
var fallbackPolicy = Policy<HttpResponseMessage>
.Handle<Exception>()
.FallbackAsync(
fallbackAction: ct =>
Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("Geçici bir sorun oluştu, varsayılan veri gösteriliyor.")
})
);
Timeout
Bir işlemin ne kadar sürede tamamlanması gerektiğini belirlemek için kullandığımız bir policy’dir. Eğer bir işlem, bizim tanımladığımız süreyi aşarsa otomatik olarak iptal edilir.
Bu sayede takılı kalan veya çok yavaş çalışan isteklerin sistem kaynaklarını gereksiz yere tüketmesinin önüne geçmiş oluruz. Özellikle dış servislere yapılan çağrılarda timeout kullanımı kritik öneme sahiptir.
var timeoutPolicy = Policy
.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(5) // 5 saniyeden uzun sürerse iptal et
);
Polly’de timeout’lar Optimistic ve Pessimistic olmak üzere ikiye ayrılır.
Optimistic Timeout: İstek CancellationToken’ı destekliyorsa kullanılır.
var timeoutPolicy = Policy
.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(3),
TimeoutStrategy.Optimistic
);
var response = await timeoutPolicy.ExecuteAsync(
async (cancellationToken) =>
{
return await httpClient.GetAsync(
"https://api.example.com/data",
cancellationToken
);
},
CancellationToken.None
);
Pessimistic Timeout: CancellationToken desteği olmayan işlemler için kullanılır.
var timeoutPolicy = Policy
.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(3),
TimeoutStrategy.Pessimistic
);
var response = await timeoutPolicy.ExecuteAsync(async () =>
{
return await httpClient.GetAsync("https://api.example.com/data");
});
Cache
Cache policy, aslında daha önce yapılan isteklerin sonuçlarını belirli bir süre boyunca saklamamızı sağlayan bir policy’dir. Böylece aynı isteği tekrar tekrar dış servislere atmamızın önüne geçmiş oluruz.
Bu yaklaşım hem uygulama performansını artırır hem de dış servislere binen yükü azaltır. Özellikle sık değişmeyen veriler için cache kullanımı oldukça etkilidir.
var memoryCache = new MemoryCache(new MemoryCacheOptions());
var cachePolicy = Policy
.CacheAsync<HttpResponseMessage>(
new MemoryCacheProvider(memoryCache),
TimeSpan.FromMinutes(1) // 1 dakika cache’te tut
);
Bulkhead
Sistem kaynaklarını izole etmeyi amaçlar. Örneğin bir servis çok fazla isteğe maruz kaldığında, bu servis için ayrılan kaynaklar sınırlandırılarak sistemin tamamının kilitlenmesi engellenir. Bu sayede bir serviste yaşanan problem tüm sistemi etkilemez.
var bulkheadPolicy = Policy
.BulkheadAsync<HttpResponseMessage>(
maxParallelization: 10, // aynı anda en fazla 10 istek
maxQueuingActions: 20 // kuyrukta en fazla 20 istek
);
Bir sonraki yazıda görüşmek üzere 👋🎉
메타데이터
- post_id
- 4eb832559bcf
- slug
- polly-ile-resiliency-patterns-4eb832559bcf
- url
- https://medium.com/@muratagyuz/polly-ile-resiliency-patterns-4eb832559bcf
- canonical_url
- https://medium.com/@muratagyuz/polly-ile-resiliency-patterns-4eb832559bcf
- author_url
- https://medium.com/@muratagyuz
- status
- ok
- fetched_at
- 2026-06-28 10:39:35