Asp.Net Core Dependency Injection ve Servis Ömürleri

Giriş

Bu makalede Asp.Net Core Dependency Injection ve servis ömürleri (servis lifetimes) hakkında konuşacağız. Ayrıca üretim (production) sürecinde nasıl kullanılacağı, nasıl işledikleri hakkında bazı ipucu ve öneriler de paylaşacağım. Servis kapsayıcısının (service container) farklı servis ömürleriyle servisleri nasıl yönettiğini ve izlediğini gösteren örnek kod ekledim. Bu makalede genel olarak Dependency Injection ile ilgili az bahsedilen konulara ve arkaplanda ne şekilde çalıştığı ile ilgili bilgilere yer verdim. Dependency Injection nedir, nasıl kullanırım diye merak ediyorsanız giriş seviyesi bir çok güzel anlatımlı makaleler var, bu konuyla ilgili öncelikli olarak Microsoft’un kendi dökümanlarını öneririm.

Asp.Net Core, ekstra bir ayar yapmanıza ihtiyaç duymadan dependency injection modelini kullanır. Diğer taraftan, bir .Net Core konsol uygulaması yazıyorsanız, Dependency Injection kullanmak için servis kapsayıcıyı kendiniz ayarlamalısınız. Bu aslında karmaşık bir işlem değil, verdiğim örnek kod ve repository (kod deposu)’de nasıl yapıldığını görebilirsiniz. Bir şablon kullanarak oluşturduğunuz yeni bir Asp.Net Core Web uygulaması, otomatik olarak Startup isimli, servis kayıtlarını ve servis ilk hazırlıklarını yapabileceğiniz bir sınıf (class) oluşturur. Çoğunlukla uygulamanızın ihtiyaç duyduğu servisleri bu sınıfın ConfigureServices metodunda tanımlarsınız.

Asp.Net Core’da varsayılan olarak Dependency Injection (DI) (Bağımlılık Enjeksiyonu) için Microsoft.Extensions.DependencyInjection kütüphanesi kullanılmaktadır. Bu kütüphane çoğu uygulamaya yeterli olacak bir çok özellik içerir. Bu kütüphanenin sunduğundan daha fazla özelliğe ihtiyaç duyarsanız, üçüncü parti kütüphaneleri direk olarak veya entegre bir şekilde (önerilen) kullanabilirsiniz. Özelleştirilmiş servis ömürleri, alt servis kapsayıcıları (child service containers) ve benzeri ekstra özelliklere ihtiyacınız yoksa varsayılan kütüphaneyi kullanmanızı tavsiye ederim, çünkü bu kütüphane hem hafif, hem performanslı hem de .Net framework içinde yer aldığı için sürekli olarak geliştirilip güncel tutulmaktadır.

Neden Dependency Injection (Bağımlılık Ekleme)?

  • Servis kayıdı için bir arabirim (interface) veya soyut bir temel sınıf (abstract base class) kullanabilirsiniz. Bu yazdığınız kodun test edilebilir olmasına yardımcı olur. Ayrıca, farklı ortamlar veya uygulama ayarları için servisin farklı tanımlamalarını da (implementation) kaydedebileceğiniz için esneklik sağlar. Örnek olarak, arka uç servisinizin (backend service) kullanıcı tarafından yüklenen belgeleri depoladığını ve sunduğunu varsayalım. IDocumentStorageService (IDokumanDepolamaServisi) şeklinde depolama hizmetinizi soyutlarsanız, kendi bilgisayarınızda yerel geliştirmeniz (local development) için FileDocumentStorageService (IDosyaDokumanDepolama Servisi), birim testleri (unit tests) için MemoryDocumentStorageService (IHafizaDokumanDepolamaServisi), Azure Depolama Hizmetlerini (Azure Storage Services) kullanmak için AzureDocumentStorageService (IAzureDokumanDepolamaServisi) kullanabilirsiniz. Diğer bulut hizmeti sağlayıcılarıyla kullanmak için servisin diğer tanımlamalarını da yazabilirsiniz.
  • Framework (çerçeve) içinde tanımlı olan servis kapsayıcısı (service container), bağımlı servisleri (dependent services) otomatik olarak servisin oluşturma metoduna (constructor) enjekte eder. Ayrıca, servisleri ve bunların bağımlılıklarını oluşturmaktan ve temizlenebilir (disposable) servisleri izlemekten sorumludur. Bu, bağımlı kaynakları temizlemek ve bellek sızıntılarını (memory leaks) önlemek için daha az kod yazmanıza yardımcı olacaktır.

Servis Kapsamı (Service Scope) nedir?

Servis kapsamını (service scope), kısa ömürlü bir alt kapsayıcı (child container) gibi düşünebilirsiniz. Servis kapsamında çözümlenen tüm temizlenebilir (disposable) scoped ve transient servisler, servis kapsamı ile beraber temizleneceklerdir (dispose). Asp.Net’te sunucuya gelen her istek (request) ile beraber yeni bir servis kapsamı oluşturulur, bu nedenle istek sona erdiğinde istek içinde çözümlenen tüm servisler isteğe bağlı olan servis kapsamı ile beraber temizlenir. Bu, hem izolasyon sağlar, hem de bellek sızıntılarını (memory leaks) önlemeye yardımcı olur. İzolasyon için, her istek, sadece tenant ve kullanıcı kapsamındaki verilere erişebilen servisler oluşturabilir.

Servis ömürleri nedir?

Varsayılan dependency injection (bağılılık ekleme) framework’ü (çerçeve) bize üç adet servis ömrü sunar. Bu servis ömürleri, servislerin ne şekilde çözümleneceğini ve temizleneceğini belirler.

  1. Transient (Geçici): Servis sağlayıcıdan, her servis talep edildiğinde yeni bir tane servis oluşturulur. Eğer servis temizlenebilir ise, servis kapsamı bu servise ait oluşturulan tüm örnekleri (instances) takip eder ve servis kapsamı sona erdiğinde hepsini temizler.
  2. Singleton (Tekton): Hazır bir örnek ile kaydedilmediyse, bu servislerin tek örneği oluşturulur. Eğer servis kapsayıcı tarafından örneği oluşturuldu ise, bu servisler kök kapsam (root scope) tarafından takip edilir. Bunun anlamı bu servisler kök kapsam sona ermediği sürece hayatta kalırlar. Eğer singleton servisiniz temizlenebilir ise, tanımlanmış tipi (implemented type) veya servis sağlayıcı fabrikası (service provider factory) olarak kaydetmediyseniz, hali hazırdaki bir örnekle kaydettirdiyseniz, servis kapsayıcı bu servisi takip edip temizlemez. Bu durumda servis kapsayıcısı sona erdiğinde manuel olarak temizlemelisiniz.
  3. Scoped (Kapsamlı): Her service kapsamı için yeni bir örnek oluşturulur. Bu servisler, servis kapsamı içinde singleton (tekton) gibi davranırlar. Eğer servis temizlenebilir ise, servis kapsamı sona erip ortadan kaldırıldığında otomatik olarak temizlenir.

Örnek KodÖrnek repository burada

bulabilirsiniz.

https://gist.github.com/fatihmemis/bd1e9853a3b454788395a8921596d974#file-000-servicelifetimes-cs

Yukarıdaki örnek koddan oluşan çıktı
Dikkat ederseniz yukarıdaki örnek kodda her servis için oluşturma ve temizleme evrelerini raporluyoruz. Ayrıca servisin mevcut bir örneğinin mi yoksa yeni bir örneğinin mi oluşturulduğundan emin olmak için SayHello metodunu çağırıyoruz.

Servis ömürleri örnek kod çıktısı

Temizlenebilir Servisler (Disposable Services)

Bir servis IDisposable ve/veya IAsyncDisposable arabirimini (interface) tanımlıyorsa temizlenebilir kabul edilmektedir.

Bir singleton servisi temizlenebilir ise ve hazır bir örnek ile kaydedildiyse, bu servis, servis kapsayıcısı tarafından takip edilip temizlenmez. Genellikle kök servis kapsayıcısı, uygulama sona ererken ortadan kaldırıldığı için bu sorun olmayabilir. Ancak hal böyle değilse, service kapsayıcısı ortadan kaldırıldıktan sonra manuel olarak servisi temizlemez iseniz, bellek sızıntısı oluşacaktır. Lütfen aşağıdaki kodu inceleyiniz.

https://gist.github.com/fatihmemis/bd1e9853a3b454788395a8921596d974#file-010-servicelifetime-singleton-tracking-cs

Eğer transient servisiniz temizlenebilir ise, bu servisleri kök kapsam dışındaki kapsamlarda oluşturmalısınız. Kök kapsamlar genellikle uygulama sona erdiğinde ortadan kaldırıldığı için, temizlenebilir transient servisinizi kök kapsamda çözümlerseniz, bu servisin oluşturulan her örneği uygulama süresince hayatta kalacaklardır. Bu da sürekli olarak hafızada yer kaplayacakları anlamına gelir ve bellek sızıntısı oluşturabilirler. Temizlenebilir transient servisler için önerilen oluşturma yöntemi, bir servis fabrikası (service factory) kullanmaktır. Eğer transient servisinizin diğer servislere bağımlılığı varsa, service fabrika metoduna mevcut servis sağlayıcısını (service provider) referans olarak gönderebilirsiniz. Bu yöntemi kullandığınızda, service kapsayıcısı temizlenebilir transient servisleri takip etmeyecektir, bu durumda servise artık ihtiyaç olmadığınızda, servisi temizlemek sizin sorumluluğunuzdadır.

Örnek Kod;

https://gist.github.com/fatihmemis/bd1e9853a3b454788395a8921596d974#file-020-servicelifetime-disposable-services-cs

Servis Kapsamları temizlenebilir servisleri nasıl takip eder?

Aşaığıdaki şemalarda Kök Kapsam “Root Scope”, İstek “Request”, Kapsam “Scope” terimlerine karşılık gelmektedir.

İsteklere bağlı kapsamların oluşması

Asp.Net Core’da her istekte yeni bir servis kapsamı oluşturulur. İstek, yanıt döndürüp veya hata ile karşılaşıp sona erdiğinde beraberinde oluşturulan servis kapsamı ve servis kapsamı ile beraber çözümlenip temizlenmek üzere takip edilen tüm servisler temizlenir.

Servislerin ömürlerine ve çözümlendikleri kapsayıcıya göre takip edilmesi

Servis kapsamından…
* Bir scoped servis talep edildiğinde;
* Servis kapsamı, servisin zaten servis kapsamı içinde bir örneği yoksa yeni bir örneğini oluşturur.
* Servis kapsamı scoped servisleri her zaman takip eder.
* Bir transient servis talep edildiğinde;
* Servis kapsamı, her zaman servisin yeni bir örneğini oluşturur.
* Servis kapsamı, sadece temizlenebilir transient servisleri takip eder.
* Bir singleton service talep edildiğinde;
* Eğer servisin henüz oluşturulmuş bir örneği yoksa, kök kapsam servisin bir örneğini oluşturur.
* Kök kapsam, kapsayıcı tarafından oluşturulmuş singleton servisleri her zaman takip eder.

Servisler Nasıl Oluşturulur?

Tüm servis örnekleri talep üzerine oluşturulur, bu nedenle servis kapsayıcısına, çok fazla sayıda farklı singleton servis kaydetmiş bile olsanız, bu servisleri hali hazırda oluşturulmuş bir örnek ile kaydetmediyseniz, sadece gerektiğinde oluşturulurlar. Bu, kaydetme şeklinize göre uygulamanızın başlatılırken geçen süreyi ve kullanılan kaynakları etkiler.

Eğer servisinizi bir servis tanımlama tipi (service implementation type) ile kaydederseniz;
* Servisiniz ilk talep edildiğinde reflection (yansıma) kullanılarak oluşturulur.
* Servisiniz tekrar talep edildiğinde, eğer bir singleton servis değilse, yine reflection kullanılarak oluşturulur, ancak servis motoru ikinci talepten sonra arka planda bir servis oluşturma fabrikası (service creation factory) derler.
* Servis fabrikası derlemesinden sonra, servis yeniden talep edildiğinde bu servis fabrikası kullanılarak oluşturulur ve bu noktadan sonra servisin yeni örneklerini oluşturmak oldukça hızlı olacaktır. Eğer servis, arka planda servis fabrikası derlenir iken tekrar talep edilirse, henüz servis fabrikası hazır olmadığı için 2 kereden fazla reflection ile oluşturulabilir.
* Bu yöntem aslında bir çok framework tarafından kullanılmaktadır, çünkü genellikle servis fabrikası derlemek, Activator.CreateInstance kullanarak reflection ile oluşturmaktan daha uzun sürer. Servis, singleton ise bir kereden fazla örneği oluşturulmayacağı için hiç derleme olmaz. Eğer bir scoped veya transient servis bir kereden fazla oluşturulmadıysa, bu yöntem sayesinde gereksiz derleme önlenmiş olur. Ayrıca servis ilk talep edildiğinde önceden derleme yapılsaydı, uygulamanın başlama sürelerini uzatırdı.
* Çoğu zaman bu yöntem işe yarar. Uygulamanın başlangıçtaki ilk ısınma süresi (warm-up time) uygulama performansını etkiliyor ise, servisinizi kaydettirirken bir servis fabrikası kullanmayı göz önüne alabilirsiniz. Bu şekilde reflection ve derleme olmayacağı için servis örneği oluşturma daha hızlı olacaktır.

https://gist.github.com/fatihmemis/bd1e9853a3b454788395a8921596d974#file-030-servicelifetime-transientservice-registration-cs

Servisleriniz için ömür belirleme

Bağlam (context) veya fonksiyonelite ile

  • Singleton kullan;
    • Servisiniz, önbellekleme (cache) servisleri gibi paylaşılan bir duruma (state) sahip ise. Sabit olmayan, değiştirilebilir bir duruma (mutable state) sahip singleton servisleri thread safety (iş parçacıkları arasında güvenli bir erişim) için bir kilitleme mekanizması kullanmayı düşünmelidir.
    • Servisiniz durumsuz (staless) ise. Eğer servis implementasyonunuz, oldukça hafif ve nadir kullanılıyorsa, transient bir servis olarak kaydettirmeyi de göz önüne almalısınız.
  • Scoped kullan;
    • Servisinizin istek süresince bir singleton gibi tek bir örneğinin olmamasını istiyorsanız. Asp.Net Core’da her istek kendi servis kapsamına sahiptir. Veritabanı ve repository servisleri genellikle scoped servis olarak kaydedilirler. EntityFramework Core‘daki DbContext te varsayılanda bir scoped servis olarak kaydedilir. Scoped servis ömrü, istek süresince çözümlenmiş tüm servislerin aynı DbContext örneğini kullanmasını sağlar.
  • Transient kullan;
    • Servisiniz, yürütme bağlamı (execution context) içinde özel (paylaşılmayan) bir duruma sahipse.
    • Servisiniz aynı anda birden fazla iş parçacığı (thread) tarafından kullanılacaksa ve iş parçacığı için erişim güvenli değilse (not thread safe).
    • Servisiniz, HttpClient gibi transient ve kısa ömürlü olması gereken bir bağımlılığa sahip ise.

Bağımlılık ile

  • Singleton servisler diğer singleton servislere bağımlı olabilir. Singleton servisler transient servisleri de bağımlılık olarak kullanabilir ancak farkında olunması gereken nokta şudur ki, bu bağımlı olunan transient servisler de singleton servisler hayatta olduğu sürece yaşar. Bu da genellikle uygulamanın ömrü ile aynıdır.
  • Singleton servisleri, en iyi uygulama olarak (best practices), scoped servisleri bağımlılık olarak kullanmamalıdır. Çünkü bu şekilde scoped servis bir singleton gibi davranır, ve mimari anlamında genellikle istenen bu değildir. Scoped servisler diğer scoped servisleri ve singleton servisleri bağımlılık olarak kullanabilir. Bu servisler aynı zamanda transient servisleri de bağımlılık olarak kullanabilir, ancak bu durumda tasarımınızı gözden geçirip, bu kullanmak istediğiniz transient servisi scoped bir servis olarak kaydedip kaydemeyeceğinizi değerlendirmenizi öneririm.
  • Transient servisler tüm servisleri bağımlılık olarak kullabilirler. Bu servisler adından da anlaşılacağı üzere çoğunlukla kısa ömürlü geçici servislerdir.
  • Bir singleton veya scoped servisin bağımlılığı olarak transient servis kullanmak isterseniz, bu servis için bir servis fabrikası kullanmayı göz önünde bulundurmalısınız. Transient servisiniz temizlenebilir ise, servis fabrikası kullanmanızı öneririm.

Faydalı Öneriler

  • Kök kapsamında oluşturulan scoped servisler, temel olarak tek örnekli olur ve singleton servis gibi davranırlar, çünkü kök kapsamının ömrü boyunca takip edilip temizlenmezler. Scoped servisin bağımlılıkları varsa bu servisler de yine kök kapsamı içinde çözümlenirler. Bir scoped servisi kök kapsamdan çözümlediğinizde, eğer bu servisin temizlenebilir bir transient servise bağımlılığı varsa, transient servis te benzer şekilde kök kapsam tarafından takip edilip, kök kapsamının ömrü boyunca temizlenmeyecektir.

https://gist.github.com/fatihmemis/bd1e9853a3b454788395a8921596d974#file-040-servicelifetime-scopedservice-correctresolution-cs

  • Diğer bir kötü tasarım ise bir singleton servisin, bir scoped servisi bağımlılık olarak kullanmasıdır. Bu tür yanlış servis referanslarını önlemek için servis kapsayıcıyı derlerken validateScopes parametresini kullanabilirsiniz. Asp.Net Core tarafından kullanılan kök servis kapsayıcı bu parametreyi true olarak kullanmaktadır.

https://gist.github.com/fatihmemis/bd1e9853a3b454788395a8921596d974#file-050-servicelifetime-scopevalidation-cs

  • Mümkün olduğunca servis bulucu deseni (service locator pattern) kullanmaktan kaçının. Ayrıca, IServiceProvider‘ın GetService metodunu manuel kullanmaktansa dependency injection (otomatik bağımlılık belirleme) kullanın. Bu, daha kolay test etmenizi, bakım yapmanızı ve okunabilir koda sahip olmanızı sağlar.

Bunu yapmaktan kaçının

https://gist.github.com/fatihmemis/bd1e9853a3b454788395a8921596d974#file-060-servicelifetime-avoid-manualresolution-cs

Servis sağlayıcıya statik olarak erişmekten kaçının

https://gist.github.com/fatihmemis/bd1e9853a3b454788395a8921596d974#file-070-servicelifetime-avoid-servicelocator-pattern-cs

Daha iyi bir yöntem

https://gist.github.com/fatihmemis/bd1e9853a3b454788395a8921596d974#file-080-servicelifetime-use-autoresolution-cs

  • Mvc denetleyicinizin (Mvc Controller) birden fazla uç noktası (endpoint) varsa ve bir veya daha fazla uç noktanız, diğer uç noktalar tarafından ortak kullanılmayan belirli bir servisi kullanıyorsa, Asp.Net Core Mvc, FromServicesAttribute özniteliği (attribute) sayesinde özel bir bağlama (custom binding) sağlar. Servis parametrenizde bu özniteliği kullanırsanız, Mvc servisleri, eylemi (action) çağırmadan önce servisi otomatik olarak çözümler ve metod parametresine bağlar.

Örnek kod;

https://gist.github.com/fatihmemis/bd1e9853a3b454788395a8921596d974#file-090-servicelifetime-controller-fromservicesattribute-cs

İlkler

Bu makaleyi yayınlayabilmek için, aşağıdaki öğeleri ilk defa yaptım.
* Bu benim genele açık ilk makalem. Dökümantasyon, bilgi tabanı (knowledge base), firma içi paylaşım ve eğitim amaçlı daha önce makale yazdım ancak bu benim genele açık yayınladığım ilk makalem. Şu anda çalıştığım PEAKUP firmasına, boş zamanlarımda yaptıklarımı fikri mülkiyet olarak etiketlemek yerine, beni topluluğa (community) paylaşmaya ve katkıda bulunmaya teşvik ettikleri için minnettarım. Onlar da aynı benim benim gibi topluluğa katkıda bulunmanın oldukça önemli olduğunu düşünmekteler.
* Azure Static Web Apps servislerini ilk defa kullandım. Azure Static Web Apps Azure’a eklenmiş yeni yönetilen (managed) servislerden biri ve daha önce denemeye fırsatım olmadı. Ben şu sıralar uygulamaları yayınlamak için genellikle Azure Kubernetes Servisleri, Azure App Servisleri ve Function Apps’leri kullanıyorum.
* İlk defa Docker Hub’a public 2 adet docker imajı paylaştım. Üretim için çoğunlukla private container registries (özel kapsayıcı kayıt defterleri), özellikle Azure Container Registry kullanıyorum.
* İlk defa Git Actions kullandım. Geliştirdiğimiz kurumsal uygulamalar için özel kod depoları (repositories) ve Azure DevOps Pipeline‘ları kullanıyoruz. GitHub tarafından sunulan, Git Actions’ı uzun zamandır denemeyi düşünüyordum ve sonunda, hedefime ulaşmak için kullandığım bir adımın bir parçası olarak başardım.
* İlk defa özel (custom) bir git action geliştirmem gerekti. Yani.. tek yaptığım mevcut bir repository’yi fork edip üzerinde düzenlemeler yapmaktı, ama yine de sayılır değil mi?
* Vue.js’i üretim (production) için ilk defa kullandım. Bu basit bir proje ama yine de, daha önce üretim için hiç kullanmadığınız bir teknoloji ile üretilmiş ve başkalarının kullanımına sunulmuş bir uygulama oldukça önemli bir konu. Ayrıca halihazırda oluşturulan kod üzerinde, bana uygun bir hale getirmek için bir çok düzenleme yapmış olmam da oldukça heyecan verici.

Krediler

Kapak resmi
Fotoğraf Hans-Peter Gauster tarafından Unsplash üzerinde

.NET Core, .NET Standart ve Felsefe Taşı – Part II

Previously on : .NET Core, .NET Standart ve Felsefe Taşı – Part I

“It does not do to dwell on dreams and forget to live.” ― J.K. Rowling, Harry Potter and the Sorcerer’s Stone

J.K. Rowling’in de dediği gibi, hayal dünyasının içerisinde hayat sürüp yaşamayı unutmak pek olmuyor. İşte PCL’ler hayatımıza böyle girmişti fakat, hayal dünyasının içerisinde, yani teoride mantıklı bir yaklaşım, gerçek hayata uyarlandığında pek de tutarlı olmadı, olamadı. Çok mu abarttım? Olabilir. Ama bu ortada çözülmesi gereken bir durumun olduğu gerçeğini değiştirmiyor.

Soruyu tekrar alabilir miyim?

Ha, şu soru. .NET Standart neden var? Ondan öncesinde PCL’ler neden var diye sormamız gerekiyor değil mi? Çözmeye çalıştığı problem aslında yazdığınız kodların farklı .NET proje tiplerinde kullanılabilmesini sağlamak. Peki neler bu proje tipleri ? .NET Framework, Silverlight, Windows 8 (Metro), Windows Phone, Xamarin, Mono, Universal Windows Platform (UWP), XBox 360, liste daha uzuyor.

enter image description here

Evet, tabii, güzel fikir. Bir çok ihtiyacı da karşıladı aslında, bu sebeple tamamen kötülemek doğru olmaz ama bir süre sonra bu yaklaşımın eksikliklerini görmeye başladık.

Nerede benim API’ım ?

dotnet-today

Birinci problem, PCL altyapısı, uygulamayı yazarken kullanabileceğiniz API’ı, desteklemeye çalıştığınız platformların kesişimine göre belirliyor. Bir platformda olan bir API diğer platformda yoksa, o kısım çıkartılıyor. Örneğin Xamarin.iOS, Xamarin.Android ve UWP için yaratayım bir PCL dediğinizde, yapacağınız dosya okuma ve yazma işlemleri artık düşündüğünüz kadar kolay olmuyor. Çünkü bir tarafta System.IO varken, diğer tarafta Windows.Storage var. Kesişimin içerisinde bu iki API da yok. Yani, var da yok. System.IO namespace olarak var ama tanıdık olduğumuz File ve Directory yok mesela. Bu sefer kulağınızı tersten tutmaya ve şuradaki gibi bir guide’ı takip etmeye başlıyorsunuz. Bu da size hayatınızı ve mesleki kararlarınızı sorgulatabiliyor. Halbuki sadece bir dosya okumak istemiştiniz. Bu tür eksiklikleri kapatmak için ortaya PCLStorage gibi paketler çıkıyor ama bu nedense yama hissiyatı yaratıyor. Her kombinasyonda eksik kalan parçalar ve o eksiklikleri kapatmaya çalışan paketler. Sonu gelmez bir döngü.

Ama Cross-Platform oluyor değil mi ?

Aslında tam değil. Yazdığınız kod her platform için ayrı ayrı derleniyor. Evet, bir PCL paketini NuGet üzerinden çekerken bütün desteklenen platformlar için derlenen dosyalar da yanında geliyor. Şimdi, bundan iki yıl sonra başka bir .NET platformu çıksa – ki bu hızda giderse hiç de düşük bir olasılık değil – bu paket o platformda çalışacak mı? Tabii ki hayır. Çünkü paketin, bu platform da eklenerek tekrar derlenmesi gerekiyor. Bu arada kaybedebileceğiniz API’ları da bir yandan düşünseniz iyi olur. Onlara da bir “yama” gerekecek.

.NET Standart

Yani bu iki büyük soru işaretine karşı çözüm olacak yapının, öncelikle stabil bir API sunması gerekiyor. Bu ise, hali hazırdaki platformların bu API’ı implemente etmesi gerektiği anlamına geliyor ki geliştirmek istediğim platformlara göre erişebildiğim API oranı azalmasın. Aynı zamanda, sonrasında geliştirilebilecek diğer platformların da aynı API’ı implemente etmesi sağlanırsa, bu yapı için derlenen paketlerin de gelecek platformda çalışması sağlanabilir.
Buradan ise yapının görevinin, bütün .NET platformları için ortak bir API sunmak ve bunun devamlılığını sağlamak olduğunu söyleyebiliriz ve iki soru işaretimiz de böylece ortadan kalkmış olur.

Sonuç olarak ortaya .NET Standart çıkıyor.

.NET Standart sayesinde artık tek bir API kullanarak, .NET Standart API’ını implemente etmiş olan bütün .NET platformlarını destekleyebiliyoruz. Yakın zamanda 2.0 versiyonu çıkan .NET Standart tarafından desteklenen API setinin içerisinde Networking, IO ve Threading gibi önemli yapı taşları mevcut.

netstandard-apis

Bir süre sonra PCL’ler yerini .NET Standart’a bırakacak. Kişisel kanaatim, .NET Standart’ın çok daha iyi bir yaklaşım olduğu yönünde. Umarım yakın zamanda bir standarta daha ihtiyaç duymayız.

“The truth.” Dumbledore sighed. “It is a beautiful and terrible thing, and should therefore be treated with great caution.” ― J.K. Rowling, Harry Potter and the Sorcerer’s Stone

Bir sonraki yazıda görüşmek üzere.

.NET Core, .NET Standart ve Felsefe Taşı – Part I

Merak etmeyin, bu yazının ne Harry Potter, ne de maddeyi altına dönüştürme ile bir ilgisi yok. Bu yazı dizisinde daha çok, .NET Core ve .NET Standart’ın kendilerini, ortaya çıkmalarındaki nedenleri, ekosistemde yapacakları değişiklikleri ve bizim bu değişikliklere nasıl ayak uydurmamız gerektiği ile ilgili konuşacağız.

Giriş

Yakın zamanda .NET Core 2.0 ve ASP.NET Core 2.0 duyuruldu. Bir çok değişiklik ve güzel haber var fakat boşverin, şimdi oradan kopup, biraz daha geriye, bütün bunların başladığı zamana gidelim. Fazla değil Kasım 2014’te Microsoft, .NET Core’u (CoreFX) Open Source yaptı. Tabii kimse .NET Core’un ne olduğunu bilmiyordu. Blog yazısında, aslında bu teknolojinin içeride ASP.NET 5 (vNext) ve .NET Native’de -ki başlı başına ayrı bir konu- hali hazırda kullandıklarını, gelecekte .NET platformunun temel taşının bu yapı olacağını söylediler. Open Source olmasındaki sebep ise, .NET’in arkasında daha güçlü bir ekosistem oluşturmak ve cross-platform bir hale getirmek için hazırlıklara başlamaktı.

.NET Core’un arkasındaki ana sebep ise, .NET Framework yaklaşımının pek de doğru olmaması ve bunu değiştirmeye yönelik çalışmalar yaparken de aynı zamanda zaten olması gereken cross-platform desteğini de göz önünde bulundurmaktı. İki amaç da zamana göre daha fazla önem kazanabilir.

.NET Core’un alt yapısını anlatmadan önce size .NET Framework yaklaşımını ve yanında getirdiği problemleri biraz daha açmam gerek.

Bir Sorunun Tanımı

Yukarıdaki ekranı hatırladınız değil mi? Hiç görmediyseniz eğer ne mutlu size, bugünün şanslı 10.000 kişisinden birisiniz. Özetlemek gerekirse, yüklediğiniz bir uygulama, çalışmak için .NET Framework’ü gerektiriyor ve önce onu yüklemeniz gerek. Bunun sadece Windows içerisinde karşılaşılan bir durum olduğunu söylemiyorum, keza diğer sistemlerde yüklediğiniz paketler, başka paketleri gereksinim olarak isteyebiliyor, fakat buradaki ana sorun teknik değil. Ana sorun, yukarıdaki problemin son kullanıcı tarafından çözülmesinin beklenmesi. Aynı zamanda bu paket öyle elzem ki, bütün .NET uygulamaları* kullanıyor ve versiyonları var. Uygulama hangi versiyonu kullanıyorsa ona göre yüklenmesi gerek. Yukarıdaki “Yes” butonuna basınca sizi o paketin indirme sayfasına yönlendiriyor, siz de inen paketi kuruyorsunuz. Tabii paket kullandığınız işletim sistemi tarafından destekleniyorsa. Örneğin Windows 7 kullandığınızı varsayarsak ve açmaya çalıştığınız uygulama .NET Framework 4.6 kullanıyorsa eğer, SP1 yükseltmesini önceden yapmış olmanız gerek. Yapmamışsanız uygulama çalışmıyor. Yani bir uygulamayı kullanmak için önce sistem güncellemesi, sonra paket kurulumu ve en sonunda kullanmak istediğiniz uygulamanın kurulumunu yapmanız gerekebilir. Bu arada bütün bunları teknik bilgisi fazla olmayan birinin yapmaya çalıştını düşünürseniz, durum pek iç açıcı değil.

Diğer bir sorun, bu kütüphanenin içerisinde herşeyin olması. Siz bir uygulama yazarken sadece küçük bir kısmını bile kullansanız, bu o uygulamanın .NET Framework kullandığı gerçeğini değiştirmiyor. .NET Framework 4.6’nın kurulum paketi 62.4 MB. Kurulum sonrası boyutu 100 MB’ı aşıyor. Amacı iki sayı toplamak olan bir konsol uygulamasının çalışması için de bu kütüphanenin kurulması gerek.

Ha bu arada yukarıdaki bu iki uygulama da diğer işletim sistemlerinde çalışmıyor.

Sanırım yavaş yavaş problemin kaynağının ne olduğunu ve nasıl çözülebileceğini düşünmeye başlamışsınızdır. Öyle bir şey olmalı ki, uygulama herhangi bir dış kaynağa gereksinim duymamalı, yani kullandığı paketleri yanında götürmeli.

* = Bütün .NET Framework uygulamaları yani WinForm, WPF, ASP.NET gibi.

Bir Efsanenin Doğuşu

.NET Core işte bu fikrin hayata geçmiş hali. Tabi şu “kullandığı paketleri yanında götürmesi” kısmı için .NET Framework parçalara ayrıldı ve çoğu paket NuGet üzerinden referans edilebilir hale getirildi. En temel parçalar (CLR ve içerisindeki GC ve JIT) ise cross-platform çalışması için baştan yazıldı ve .NET Core CLR oluştu. Tabii hepsinin cross-platform çalışması için değiştirilen bir çok yaklaşım da oldu.

Peki Web?

ASP.NET 5 (vNext) yani ASP.NET Core ise, .NET Core’un üzerine ASP.NET mimarisinin bindirilmesi ile oluşmuş bir platform. Ocak 2016’ta ASP.NET 5 adı ASP.NET Core olarak değiştirildi çünkü ASP.NET  5 sanki ASP.NET 4.5’in bir üst versiyonu gibi anlaşılıyordu. Halbuki ASP.NET 5,  yapısal anlamda ASP.NET 4.5’i andırmıyordu bile ve bu insanlarda kafa karışıklığı yaratabilirdi. Bu sebeple isim değişikliğine gidildi.

Soru İşaretleri

Peki .NET Standart bunun neresinde. Bugün itibarıyla üç tane .NET platformu var; .NET Framework, .NET Core ve Xamarin. Her birisinin amacı ve kullandıkları altyapılar farklı. Tabii bu kullanılan dil aynı olsa bile oluşan paket uyuşmazlığı, yazılan kodların platformlar arası kullanılmasını engelliyor. Zaten her biri programlayan kişiye farklı API’lar sunuyor. Yani her türlü yazılan kod belirli bir platforma özel olmuş oluyor. Bu sorunu çözmek için üretilen PCL ise sorunu daha da büyük bir hale getiriyor.

Part II’de ise .NET Standart’ın ne olduğunu ve yukarıdaki probleme nasıl bir çözüm getirdiğini göreceğiz.

.NET Core, .NET Standart ve Felsefe Taşı – Part II