Modern yazılım mimarisi alanında, Bağımlılık Enjeksiyonu (DI), nesne oluşturma ve sarmalanmayı yönetmek için varsayılan standart haline gelmiştir. Basit bir monolitik yapıda DI uygulamak kolay olsa da, mikroservislerden oluşan dağıtık bir sistemde yaşam döngülerini yönetmek, hatta kıdemli mühendisleri bile zorlayan bir karmaşıklık katmanı ekler. Kontrolün Tersine Çevrildiği (IoC) container sadece nesneler için bir fabrika değildir; kaynakların ne zaman tahsis edileceğini ve en önemlisi ne zaman serbest bırakılacağını belirleyen bir yaşam döngüsü yöneticisidir.
Bu yaşam döngülerinin yanlış yönetilmesi bellek sızıntılarına, eski veri sorunlarına veya bağlantı havuzu tükenmesine yol açabilir. Bu yazıda, DI yaşam döngülerinin pratik uygulanışını inceleyecek ve servis sınırları boyunca durumu yönetmenin nüanslarına odaklanacağız.
Temel Yaşam Döngülerini Anlamak
Autofac, Spring Framework veya .NET Core'un yerleşik sağlayıcısı gibi çoğu IoC container, üç temel yaşam döngüsü kapsamını destekler. Bu kavramlar arasındaki anlamsal farkı anlamak, sağlam bir mimariye giden ilk adımdır.
- Transient: Servis her istendiğinde yeni bir örnek oluşturulur. Bu, durum bilgisi taşımayan servisler veya hafif nesneler için idealdir.
- Scoped: Her kapsam için tek bir örnek oluşturulur. Web uygulamalarında bir kapsam genellikle tek bir HTTP isteğine karşılık gelir. Bu, Entity Framework
DbContextgibi birim iş kalıpları için mükemmeldir. - Singleton: Container başına tek bir örnek oluşturulur. Bu, durum bilgisi taşıyan yapılandırmalar veya küresel durumu koruyan servisler için kullanılır.
Mikroservislerde Scoped Yaşam Döngüsü Tuzağı
En yaygın mimari hata, geliştiricilerin orijinal istek kapsamına bağlı olmayan asenkron sınırlar boyunca scoped servisleri paylaşmaya çalışmasıdır. Mikroservis mimarisinde, tek bir gelen istek, mesaj kuyrukları veya HTTP istemcileri aracılığıyla iç çağrıların bir zincirini tetikleyebilir.
Eğer bir scoped servis (bir veritabanı bağlamı gibi) istek kapsamının dışında çalışan bir arka plan işçisine enjekte edilirse, çözümleme hatası verebilir veya ciddi eşzamanlılık sorunlarına yol açabilir.
C#'ta bu sorunlu desene bir örnek:
// ❌ TEHLİKELİ: Singleton içine scoped bir servis enjekte etmek
public class BackgroundWorker
{
private readonly IDataService _dataService; // IDataService Scoped olarak kaydedilmiştir
public BackgroundWorker(IDataService dataService)
{
_dataService = dataService; // Container burada hata atabilir
// veya atılmış bir örneğe referans tutabilir
}
}
Bunu düzeltmek için, arka plan görevi içinde kapsamı açıkça yönetmeliyiz. Tüketici, scoped servisi doğrudan singleton işçiye enjekte etmemelidir. Bunun yerine, işçi bir IServiceScopeFactory kabul etmeli ve her birim iş için yeni bir kapsam oluşturmalıdır.
Pratik Çözüm: Açık Kapsam Yönetimi
Kapsam fabrikasını enjekte ederek, veritabanı bağlamının veya diğer scoped bağımlılıkların, işçi tarafından işlenen her tekil mesaj için doğru şekilde oluşturulup atıldığını garanti ederiz.
// ✅ GÜVENLİ: Scoped bağımlılıkları yönetmek için IServiceScopeFactory kullanımı
public class SafeBackgroundWorker
{
private readonly IServiceScopeFactory _scopeFactory;
public SafeBackgroundWorker(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public async Task ProcessMessageAsync(string message)
{
// Bu belirli işlem için yeni bir kapsam oluşturun
using (var scope = _scopeFactory.CreateScope())
{
// Yeni kapsam içinde scoped servisi çözün
var dataService = scope.ServiceProvider.GetRequiredService<IDataService>();
await dataService.UpdateAsync(message);
} // Kapsam ve atılmış tüm servisleri burada temizlenir
}
}
Sonuç
Mikroservislerde IoC yaşam döngülerini yönetmek, bağımlılık çözümlemesinde disiplinli bir yaklaşım gerektirir. Transient'ler güvenli ve singleton'lar güçlü olsa da, scoped servisler açık sınır yönetimi gerektirir. Bağımlılıklarınızın ömrünü anlayarak ve asenkron sınırlar boyunca örtük kapsam paylaşımından kaçınarak, yalnızca modüler değil, aynı zamanda bellek sızıntılarına ve eşzamanlılık hatalarına karşı da dirençli sistemler inşa edebilirsiniz. Unutmayın: Dağıtık sistemlerde, yaşam döngüsünün netliği, kodun netliği kadar önemlidir.