Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

.NET 8 – Keyed Service | Dependency Injection

Merhaba,

Bu içeriğimizde .NET 8 ile IoC container’a gelen keyed service özelliğini inceleyecek, nasıl ve ne zaman kullanılmaları gerektiğini, olayın perde arkasını masaya yatırarak bol istişare eşliğinde değerlendireceğiz. O halde buyurun başlayalım…

Keyed Service Nedir?

Malumunuz dependency injection, Asp.NET Core mimarisinin üzerine inşa edildiği en kritik davranışsal yapılanmalardan birisidir. Mimarinin çekirdeği de dahil olmak üzere hemen hemen her yerinde kullanılan bu model, biz geliştiriciler tarafından da rahatça kullanılabilmesi ve konfigüre edilebilmesi için minimum özellikleri barındıran built-in bir IoC container ile birlikte gelmektedir. Ha tabi istek doğrultusunda Autofac gibi third party kütüphaneler eşliğinde de daha yetenekli container’lar sisteme dahil edilip, kullanılabilmektedir.

Velhasıl…

Şimdi bizler varsayılan container açısından olayı değerlendirirsek eğer, herhangi bir servisi provide ederken aşağıdaki üç parametreye odaklı bir yaklaşım sergilenmesi gerektiğini söyleyebiliriz;

  1. Lifetime
    Built-in container’a eklenecek olan servisin instance’ının nasıl oluşturulacağını veya oluşturulmuş olan instance’ın ne sıklıkla yeniden kullanılacağını ifade eden parametredir.

    Transient, Scoped veya Singleton olarak davranış belirlenebilir.

  2. Service Type
    Container’a eklenen servisin, hangi referans/type ile ilişkilendirileceğini ifade etmektedir. Bu tür, bir interface ya da class veya record gibi concrete tür olabilir.
  3. Implementation Type
    Service Type’a karşılık verilecek servis türünün ta kendisidir. Misal olarak; ‘IPersonService’ interface’i service type’a karşılık gelirken, bu interface’i implemente etmiş olan ‘PersonService’ class’ı ise implementation type’a karşılık gelmektedir.

Not: Konuya dair tam teferruatlı bilgi için youtube kanalımda yayınladığım Asp.NET Core 5.0 – Derinlemesine Dependency Injection – IoC Yapılanması başlıklı videoya göz atabilirsiniz.

Yani anlayacağınız .NET mimarisinde built-in container’a bir servisi kaydetmek bu parametrelerden geçmektedir. Ha arka planda bu yapılandırmaların bir ServiceDescriptor türüne karşılık depolandığını söylemekte de fayda görmekteyim.

Şimdi, .NET 8 ile gelmiş olan keyed service’lere gelirsek eğer bu özellik sayesinde provide edilmiş olan servisi tanımlayacak farklı bir bilgi parçası da devreye girmektedir. Bu bilgi parçası Service Key olarak nitelendirilmektedir ve yine ServiceDescriptor sınıfı üzerinde konfigüre edilmektedir. Service key, herhangi bir türden nesne olabileceği gibi genellikle string veya enum türden değer tercih edilmektedir. (Angular’da ki injection token akla geliyor 😉 )

Bu teorik izahatten sonra mevzuyu daha da netleştirebilmek için keyed service özelliğini pratiksel olarak gözlemlemeye geçelim.

Keyed Service Özelliğinin Kullanımı

Keyed service özelliğinin kullanımını pratikte tecrübe edebilmek için öncelikle aşağıdaki servis yapılanmasını inşa edelim:

public interface IPersonService
{
    string GetPersonType { get; }
}
public class SalesDepartmentPersonService : IPersonService
{
    public string GetPersonType => "Sales Department";
}
public class JobDepartmentPersonService : IPersonService
{
    public string GetPersonType => "Job Department";
}
public class AccountingDepartmentPersonService : IPersonService
{
    public string GetPersonType => "Accounting Department";
}

Yukarıdaki çalışmalara göz atarsanız eğer ‘IPersonService’ isminde bir interface ve bu interface’i implemente eden üç farklı concrete sınıfı mevcuttur.

Eğer ki, keyed service özelliği olmaksızın bu servisleri IoC Container’a provide ediyor olsaydık aşağıdaki gibi bir çalışma yapılması gerekiyor olacaktı:

.
.
.
builder.Services.AddSingleton<IPersonService, SalesDepartmentPersonService>();
builder.Services.AddSingleton<IPersonService, JobDepartmentPersonService>();
builder.Services.AddSingleton<IPersonService, AccountingDepartmentPersonService>();
.
.
.

Tabi bu şekilde birden fazla aynı service type’a karşılık gelen provide yapılandırmalarında, yapılacak inject’e her daim sonuncu tanımlanan türün instance’ı gönderilecektir. Yani aşağıdaki örneği ele alırsak eğer;

app.MapGet("/", (IPersonService personService) =>
{
    ...
});

‘personService’ referansına ‘AccountingDepartmentPersonService’ türünden bir nesne gönderilmiş olacaktır.

Ee peki hoca, ben ‘IPersonService’ referansına karşılık ‘SalesDepartmentPersonService’ veya ‘JobDepartmentPersonService’ nesnelerini nasıl elde edebilirim?

Keyed service özelliğinden önce aynı referansa karşılık provide edilmiş instance’lardan istediğimizi almanın pekte basit bir yolu olduğunu söyleyemem. Burada yapılması gereken bu nesneleri farklı referanslarla işaretleyebilmek için hususi interface’lerin implemente edilmesi ve bu interface’ler üzerinden provide edilmesi uygulayabileceğimiz esas çözümlerden birisidir diyebilirim.

Ya da anlayacağınız üzere bu ihtiyacı .NET 8 ile gelmiş olan keyed service özelliğiyle de rahatlıkla çözümleyebiliriz. Bunun için ‘AddKeyed…’ fonksiyonlarını aşağıdaki gibi kullanmak yeterlidir;

.
.
.
builder.Services.AddKeyedSingleton<IPersonService, SalesDepartmentPersonService>("sales");
builder.Services.AddKeyedSingleton<IPersonService, JobDepartmentPersonService>("job");
builder.Services.AddKeyedSingleton<IPersonService, AccountingDepartmentPersonService>("accounting");
.
.
.

Görüldüğü üzere, keyed service özelliği aynı service type’a karşılık provide edilmiş olan instance’ları bu sefer de vermiş olduğumuz key’ler üzerinden ayırmamızı sağlayan bir niteliğe sahiptir. Tabi bu özelliği illaki bir service type’a karşın çoklu provide durumlarında tercih edeceğiz diye bir kaide yok. Yapının niteliğine vakıf olduğunuz taktirde istediğiniz her noktada gönül rahatlığıyla kullanabilirsiniz.

Keyed service özelliği ile provide edilmiş olan servisleri inject edebilmek için FromKeyedServices attribute’undan aşağıdaki gibi istifade edebiliriz.

.
.
.
app.MapGet("/", ([FromKeyedServices("sales")] IPersonService personService) =>
{
    ...
});
.
.
.

Ya da ‘IServiceProvider’ üzerinden de IoC container’dan talepte bulunabiliriz.

.
.
.
app.MapGet("/", (IServiceProvider keyedServiceProvider) =>
{
    var personService = keyedServiceProvider.GetRequiredKeyedService<IPersonService>("accounting");
    return personService.GetPersonType;
});
.
.
.

Anlayacağınız, keyed service özelliği esasında non-keyed service’lerle aynı şekilde çalışmakta, tek fark container’da ki servislerin tanımlanmasındadır. Yani non-keyed service’ler sadece service type ile provide edilirken, keyed service’ler ise service type eşliğinde service key ile birlikte tanımlanmaktadırlar. Ha eğer ki gidip birden fazla provide’ı aynı keyed service özelliğiyle kaydederseniz bu durumda da non-keyed service’ler de olduğu gibi sonuncu olarak provide edilen instance’ın geleceğini bilmenizde fayda vardır.

Nihai olarak,
Keyed service özelliği incelediğimiz üzere IoC container’daki kimi durumlara karşın davranışlarımızı basite indirgemekte ve dependency injection açısından farklı bir yaklaşım sergileyebilmemize olanak tanımaktadır. Ha çok mu gerekli bir yapılanmaydı diye sorarsanız kesinlikle olmadıkları konusunda hemfikiriz diyebilirim 🙂 O yüzden sırf var oldukları için kullanma mecburiyetinde olduğunuzu düşünmeyin.

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Bunlar da hoşunuza gidebilir...

1 Cevap

  1. Tarık dedi ki:

    Hocam selamlar. Biz burada interfcae mantığından gitmeyip bu clasların base’ni yazıp içinde factory pattern uygulayıp istediğimiz classın referansını döndürsek aynı şey olmuyor mu. Ana kodumuzda new leyerek base class kullanıp parametre olarak istediğimiz referans tipini yollsak interface gibi davranacak zaten. İlerde gelecek değişiklikler gene ana kodumuzu etkilemeyip baseclasın içinde düzelteceğiz. Neden interfacee ihtiyacım var arayüz olmadan da loose coupling sağlayabiliyorum. Ornek olarak

      class BaseMailService
        {
            public void SendMail(int provider)
            {
                
                if (provider == 1 )
                {
                    var gmail =new GmailService();
                    s.SendMail();
                }
                if (provider == 2 )
                {
                    var outlook =new OutlookService();
                    s.SendMail();
                }
            }
    

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir