Scoped, Transient ve Singleton Yaşam Döngüleri ile Dependency Injection
SOLID prensiplerinin bize verdiği yetkiye dayanarak; clean code, reuseability vs. derken konu bir şekilde dönüp dolaşıp Dependency Injection’a da geliyor. Konu buraya gelince de, başlıktaki 3 kavramla karşılaşıyoruz.
Peki nedir bu kavramlar birlikte görelim.
Singleton: Burası Single (tek) kelimesinden aklımızda kalsın. Singleton tanımlı servislerimiz, uygulama ayağa kalkınca bir defa oluşur ve uygulama ömrü boyunca aynı referans üzerinden bize hizmet eder. Bunun açıklaması aslında en basiti. Diğerlerinde iş biraz daha değişiyor.
Loglama, uygulama özelliğini açma kapama gibi genel uygulama durumunu kontrol etme amaçlı servislerin, her istek için yeniden oluşturulmasına gerek yoktur. Bundan dolayı bu tarz durumlarda singleton tercih edilebilir.
Scoped: Bu yaşam döngüsü her HTTP istek başı bir adet nesne üretmek için kullanılır. HTTP isteği sona erdiğinde ise Garbage Collector’un hışmına uğrayacaktır. Genel anlamda HTTP isteği ile ilgili bir state kontrol etmek ve de tutmak isterseniz scope yaklaşımı daha uygun olacaktır.
Bunu yetkilendirme, oturum yönetimi gibi kavramlar için kullanabilirsiniz.
Transient: Transient tanımlı servislerin, oluşturma ve yıkım maliyetlerinin oldukça minimum seviyede olmaları gerekmektedir. Çünkü transient servisler, ne zaman talep edilirse her defasında yeniden oluşturulur. Burada transient tanımladığımız servisler çok önemli. Eğer maliyetli servisleri transient tanımlar isek, GC esnasındaki bekletmeler, uygulamayı yanıt veremez duruma getirebilir. İki instanceın birbirinden bağımsız olması gereken durumlarda, state vs. kaygısı yok ise, transient kullanılabilir.
Teorisi Tamam Ama Koduna Da Bakalım
Öncelikle bir .NET 7.0 API projesi ve bir class library oluşturuyorum. Sınıf kütüphanemiz bizim API projemiz için yardımcı kütüphane olduğundan, kütüphaneyi, API projesine bind ediyoruz.
Sonrasında classlarımı ve interfacelerimi yardımcı kütüphanemde oluşturuyorum.
Tüm servislerimde bir Guid ve DateTime alabileceğim birer property ekliyorum. Hepsi aşağıdaki gibi.
Sonrasında ise somut classlarımda bunların karşılıklarını ve setlendikleri constructorları yazıyorum.
En son ise bunları .NET’in kendi DI lifetime yönetimine bırakmak için gerekli tanımlarımı yapıyorum.
Başka hiçbir şeyi değiştirmeden yeni bir controller oluşturarak, kendime bir endpoint yaratıyorum.
Bu sayede istek atarak, nesnelerimin tuttuğu değerler arasındaki değişimleri inceleyebileceğim. Bu şekilde projemi ayağa kaldırarak ilk isteğimi gönderiyorum. İlk isteğimin cevabı:
[ “SingletonDate”, “24.04.2023 18:28:27”
, “SingletonId”, “5239ece0-aadd-472a-a7ca-d7ea3202d618”
, “TransientDate”, “24.04.2023 18:28:27”
, “TransientId”, “48e81e4a-688c-4eb7–9275-c0731f9d5bd2”
, “ScopedDate”, “24.04.2023 18:28:27”
, “ScopedId”, “ab56fa34–4fa8–4725–9a66-e997c96fb145”
, “TransientDate2”, “24.04.2023 18:28:27”
, “TransientId2”, “1bf374fd-fcf9–4f9b-911e-9ee0cfc9b7d8”
, “ScopedDate2”, “24.04.2023 18:28:27”
, “ScopedId2”, “ab56fa34–4fa8–4725–9a66-e997c96fb145” ]
Görüldüğü üzere, ValueController isimli controllerımda tam 5 adet nesne talep ettim. Bunlardan 1 tanesi singleton, 2 tanesi transient ve 2 tanesi scoped yaşam döngüsüne sahip. Ancak görüyorum ki transient nesnemdeki ID’ler her bir nesnem için farklı iken, scoped tanımlı nesnemdeki idler değişmemişler. Tekrar bir istek atıyorum.
[ “SingletonDate”, “24.04.2023 18:28:27”
, “SingletonId”, “5239ece0-aadd-472a-a7ca-d7ea3202d618”
, “TransientDate”, “24.04.2023 18:31:32”
, “TransientId”, “7f54b383–58a1–46ee-a957-a968441650c6”
, “ScopedDate”, “24.04.2023 18:31:32”
, “ScopedId”, “ed697ddc-b5c6–4257–9982–5a231c6d8f83”
, “TransientDate2”, “24.04.2023 18:31:32”
, “TransientId2”, “fcb594ed-4343–4f03–8e69–06a921dc430f”
, “ScopedDate2”, “24.04.2023 18:31:32”
, “ScopedId2”, “ed697ddc-b5c6–4257–9982–5a231c6d8f83” ]
İkinci istekte de görülüyor ki, singleton yaşam döngüsüne sahip nesnemin ID’si ve tarihi değişmemiş. Bu da uygulama yaşam döngüsü boyunca kaç defa çağırırsam çağırayım değişmeyecek olduğu görülüyor. Ancak scoped yaşam döngülü nesnemin idlerine baktığım zaman 2. istek için ikisinin de aynı, ilk istekten farklı olduğunu görüyorum. Bu da her HTTP isteğinde scoped nesnemin tekrar oluşturulduğunu bana gösteriyor. Transient ise görüldüğü üzere burada da ilk istekte olduğu gibi farklı görünmekte.
Bu şekilde singleton, transient ve scoped yaşam döngülerinizi DI containerlarınızda ihtiyaca ve performans kaygılarınıza göre şekillendirebilirsiniz.
Projeyi incelemek isterseniz buyrun: https://github.com/idylmz/DISample