Modern .NET Core uygulamaları, bağımlılık enjeksiyonunu (Dependency Injection – DI) temel mimari taşlarından biri olarak kullanır. Uygulamanın ölçeklenebilir, test edilebilir ve yönetilebilir olması için servislerin yaşam döngüsü doğru belirlenmelidir. İşte tam bu noktada devreye service lifetime kavramı girer.
Bu yazıda, .NET Core’un DI konteyneri tarafından desteklenen üç temel yaşam döngüsünü; Singleton, Scoped ve Transient servisleri, çalışma zamanına etkileri ve doğru kullanım alanlarıyla birlikte ele alacağım.
Dependency Injection ve Service Lifetime Neden Önemlidir?
Servis yaşam süreleri, uygulamanın:
-
Performansını
-
Bellek tüketimini
-
Thread ve request yönetimini
-
Veri tutarlılığını
doğrudan etkiler.
Yanlış seçilmiş bir lifetime, özellikle multi-thread senaryolarda memory leak'lere, gereksiz CPU yüküne ve istenmeyen state paylaşımlarına yol açabilir Bu nedenle hangi servisin hangi yaşam döngüsünde olması gerektiğini bilmek, profesyonel bir .NET Core uygulamasının temel gerekliliğidir.
Service Lifetime Türleri
|
Lifetime
|
Ne Zaman Oluşur?
|
Ne Zaman Yok Olur?
|
Her Request İçin Yeni Örnek?
|
Thread-Safe Gerekli mi?
|
|---|---|---|---|---|
|
Singleton
|
Uygulama başlarken / ilk ihtiyaçta
|
Uygulama kapanınca
|
Hayır
|
Evet
|
|
Scoped
|
Her HTTP request başladığında
|
Request bittiğinde
|
Evet
|
Kısmen
|
|
Transient
|
Her talepte
|
Talep sonrası
|
Evet, her defasında
|
Hayır
|
Singleton Service
Singleton servisler uygulama boyunca yalnızca bir kez örneklenir. DI konteyner içinde tek bir instance oluşturulur ve tüm uygulamada paylaşılır.
services.AddSingleton<IMyService, MyService>();
Kullanım Senaryoları
-
Konfigürasyon yöneten sınıflar
-
Hafif, thread-safe utility komponentleri
-
Cache yöneticileri
-
Log yazıcıları
-
Sabit veri üreten servisler
Avantajlar
-
Minimal bellek tahsisi — tek örnek
-
Yüksek performans — tekrar oluşturulmaz
Riskler
-
Thread-safe olmayan kodlarda yarış koşullarına neden olabilir.
-
State tutuyorsa, global paylaşım kaynaklı hatalara açık olur.
-
Test senaryolarında izole edilmesi zordur.
Ne Zaman Kaçınmalıyız?
-
İçerisinde kullanıcıya veya request'e özel veri tutuyorsan
-
EF Core DbContext gibi disposable objeler içeriyorsa
-
Çok fazla state varsa ve değişiyorsa
Scoped Service
services.AddScoped<IMyService, MyService>();
Kullanım Senaryoları
-
EF Core DbContext
-
Unit of Work pattern
-
Request’e bağlı işlem yapan servisler
-
Kullanıcıya veya işleme özel state tutan sınıflar
Avantajlar
-
Request başına tutarlı veri yönetimi
-
En ideal state izolasyonu
-
Web uygulamaları için varsayılan en sağlıklı yaklaşım
Riskler
-
Background task veya Singleton içinde Scoped servis kullanılırsa sorun olabilir.
-
Request-scope dışında referans alırsan “ObjectDisposedException” hatası alınabilir.
Ne Zaman Kaçınmalıyız?
-
Singleton içinden scoped servis istemek
(Bu, .NET Core’un en sık karşılaşılan anti-pattern’lerinden biridir.)
Transient Services
services.AddTransient<IMyService, MyService>();
Kullanım Senaryoları
-
Hafif ve stateless bileşenler
-
Hizmet çağrılarının her defasında yeni oluşması gereken servisler
-
Kısa ömürlü operasyon class’ları
Avantajlar
-
State karışıklığı olmaz, her kullanımda sıfırdan başlar
-
Deadlock ve concurrency riskleri daha düşüktür
Riskler
-
Çok sık oluşturulan ağır sınıflarda performans kaybı
-
Gereksiz GC (Garbage Collector) yükü
Ne Zaman Kaçınmalıyız?
-
İçinde ağır kaynaklar veya bağlantılar varsa
-
Sık çağrılan business logic bloklarında yoğun yük oluşturuyorsa
EF Core DbContext İçin Hangi Lifetime Doğru?
DbContext’in Singleton veya Transient olması hem thread yönetimini, hem transaction yapısını bozar. Microsoft tarafından resmi olarak Scoped önerilir.
Karar Verme Matrisi
|
Soru
|
Evet | Hayır | |
|---|---|---|---|
|
Servis state tutuyor mu?
|
Scoped
|
Transient
|
|
|
Request’e özel veri mi işliyor?
|
Scoped
|
Transient
|
|
|
Uygulama boyunca tek bir kez kullanılmalı mı?
|
Singleton
|
Scoped/Transient
|
|
|
Ağır bir sınıf mı?
|
Singleton/Scoped
|
Transient
|
|
|
Thread-safe mi?
|
Singleton olabilir
|
Scoped/Transient daha güvenli
|
Örnek Kod: Üç Lifetime'ın Uygulanışı
public void ConfigureServices(IServiceCollection services)
{
// Her talepte yaratılır
services.AddTransient<IEmailSender, EmailSender>();
// Her request'te tek örnek
services.AddScoped<IOrderService, OrderService>();
// Uygulama süresince tek örnek
services.AddSingleton<ICacheProvider, MemoryCacheProvider>();
}
Sonuç
.NET Core’da service lifetime seçimi, doğru mimariyi kurmanın temel adımlarından biridir. Doğru seçilmemiş bir yaşam döngüsü; performans sorunlarından veri tutarsızlıklarına kadar çok katmanlı problemlere yol açabilir.
Özetle:
-
Singleton: Evrensel, thread-safe, state taşımayan servisler için
-
Scoped: Request odaklı, DbContext gibi stateful servisler için
-
Transient: Hafif, stateless, her kullanımda yenilenmesi gereken servisler için
Doğru lifetime = daha hızlı, daha temiz, daha güvenli bir .NET Core uygulaması.
Diğer Bloglarımda Görüşmek Üzere, 👋