Asp.NET Core – Çok Katmanlı Mimaride İstenilen Katmanda Dependency Injection Kullanımı

Merhaba,

Önceki içeriklerimizden Asp.NET Core 3.0 – Çok Katmanlı Mimaride Migration İşlemleri başlıklı makalemizde çok katmanlı mimari yapılanmasının migration işlemleriyle birlikte yapısal inceliklerini ele almıştık. İlgili makaleye göz atarsanız eğer, katmanlar arasında DAL -> BL -> PL olması gereken ilişkiyi DAL -> PL && (BL -> PL) şeklinde kurduğumuzu göreceksiniz. Bunun nedeni DAL katmanı aracılığıyla veritabanından gelen verileri BL katmanında işleyebilmek için BL katmanında DAL katmanındaki repository, context vs. nesnelerinin dependency injection ile talep edilebiliyor olması gerekmektedir. Bunun içinde PL katmanında talep edilen bu nesneler BL’ye gönderilmekte ve dolayısıyla PL’de DAL katmanını referans göstermek mecburiyetinde kalınmaktadır. Bu mecburiyet esasında çok katmanlı mimari dediğimiz stratejik yaklaşımı zedelemekte ve stratejide taktiksel hataya sebebiyet vermektedir. İşte bizler bu makalemizde BL katmanında PL’ye gereksinim duymaksızın dependency injection stratejisinin nasıl uygulaması gerektiğini ele alacak ve ortaya mimarisel açıdan ideal yaklaşımın nasıl olması gerektiğini irdeleyerek, doğru yapılanmanın somut örneğini geliştireceğiz.

Yani uzun lafın kısası; PL katmanında DAL katmanını referans etmeksizin(mesela “Startup.cs” dosyasına context nesnesini AddDbContext ile eklemeksizin) tüm veritabanı işlemlerini BL katmanında ihtiyaca dönük nasıl yapılandırılacağını inceleyeceğiz.

Şimdi adım adım çok katmanlı mimari yaklaşımını benimsediğimiz bir proje geliştirelim. Herşeyden önce en temel katmanımız olan DAL katmanını ele alarak işe başlayalım.

  • DAL
    Veritabanı(context) nesnesi ve migration vs. gibi yapılanmaların bulunduğu katmandır.
    Asp.NET Core - Çok Katmanlı Mimaride İstenilen Katmanda Dependency Injection Kullanımı
    Kaynak kodlar oldukça standart yapıda olduğu için ekstradan göstermeye lüzum görmemekteyim.
  • Entities
    DAL katmanıyla yüksek oranda ilişkisel nesneleri barındıracak olan Entities katmanı içerisinde uygulamada kullanılacak tüm entity yapılanlarını barındırmaktadır. Entityler; DAL’ın dışında BL, PL ve hatta çoğu custom katmanda ihtiyaca dönük kullanıldığından dolayı gönül rahatlığıyla istenilen katman tarafından referans edilebilmesi ve çok katmanlı mimari yapılanmasını zedelememek için DAL’dan ayrılmıştır.
    Asp.NET Core - Çok Katmanlı Mimaride İstenilen Katmanda Dependency Injection Kullanımı
  • BL
    Bilindiği gibi her türlü iş, eylem, fiiliyat, algoritma, istatistik vs. yani operasyonel tüm işlevleri kapsayan katmandır. Bizler örneğimizde Repository yapılanmasını kullanacağımızdan dolayı BL katmanımızda ilgili yapılanmaya dair çalışmalar bulunmaktadır.
    Asp.NET Core - Çok Katmanlı Mimaride İstenilen Katmanda Dependency Injection Kullanımı

    BL katmanındaki bu çalışmalar dependency injection gerektirdiğinde PL üzerinden beslenmeyeceğinden dolayı buradaki dependency injection providerına ilgili nesneleri atabilmeli ve yeri geldiğinde kullanabilmeliyiz. Bu nokta kritik önem arz etmekte olduğundan dolayı odaklanmamız gereken merkezlerden birisidir.

    Şimdi gelin BL katmanındaki tüm inşa sürecini tek tek ele alıp, bu işlemin nasıl gerçekleştirildiğini konuşalım.

    • IRepository Interface’i
      Repository imzasını sunan arayüzdür.

          public interface IRepository<T> where T : class
          {
              List<T> Get();
              List<A> Get<A>() where A : class;
              List<T> GetWhere(Expression<Func<T, bool>> metot);
              List<A> GetWhere<A>(Expression<Func<A, bool>> metot) where A : class;
              T GetSingle(Func<T, bool> metot);
              A GetSingle<A>(Func<A, bool> metot) where A : class;
              T GetById(int id);
              A GetById<A>(int id) where A : class;
              bool Add(T model);
              bool Add<A>(A model) where A : class;
              bool Remove(T model);
              bool Remove<A>(A model) where A : class;
              bool Remove(int id);
              bool Remove<A>(int id) where A : class;
              bool Update(T model, int id);
              bool Update<A>(A model, int id) where A : class;
              int Save();
          }
      
    • Repository Sınıfı
      Repository yapılanmasında base class olarak kullanacağımız sınıftır.

          public class Repository<Type> : IRepository<Type> where Type : class
          {
              protected SirketContext _sirketContext;
              public Repository()
              {
                  IServiceCollection services = new ServiceCollection();
                  services.AddDbContextService();
                  ServiceProvider provider = services.BuildServiceProvider();
      
                  _sirketContext = provider.GetService<SirketContext>();
              }
              [NonAction]
              public DbSet<Type> Table()
              {
                  return Table<Type>();
              }
              [NonAction]
              public DbSet<A> Table<A>() where A : class
              {
                  return _sirketContext.Set<A>();
              }
              [NonAction]
              public bool Add(Type model)
              {
                  return Add<Type>(model);
              }
              [NonAction]
              public bool Add<A>(A model) where A : class
              {
                  Table<A>().Add(model);
                  Save();
                  return true;
              }
              [NonAction]
              public List<Type> Get()
              {
                  return Get<Type>();
              }
              [NonAction]
              public List<A> Get<A>() where A : class
              {
                  return Table<A>().ToList();
              }
              [NonAction]
              public Type GetById(int id)
              {
                  return GetById<Type>(id);
              }
              [NonAction]
              public A GetById<A>(int id) where A : class
              {
                  return GetSingle<A>(t => typeof(A).GetProperty("Id").GetValue(t).ToString() == id.ToString());
              }
              [NonAction]
              public bool Remove(Type model)
              {
                  return Remove<Type>(model);
              }
              [NonAction]
              public bool Remove<A>(A model) where A : class
              {
                  Table<A>().Remove(model);
                  return true;
              }
              [NonAction]
              public bool Remove(int id)
              {
                  return Remove<Type>(id);
              }
              [NonAction]
              public bool Remove<A>(int id) where A : class
              {
                  A silinecekData = GetSingle<A>(x => (int)typeof(A).GetProperty("Id").GetValue(x) == id);
                  Remove<A>(silinecekData);
                  Save();
                  return true;
              }
              [NonAction]
              public int Save()
              {
                  return _sirketContext.SaveChanges();
              }
              [NonAction]
              public bool Update(Type model, int id)
              {
                  return Update<Type>(model, id);
              }
              [NonAction]
              public bool Update<A>(A model, int id) where A : class
              {
                  A guncellenecekNesne = GetById<A>(id);
                  var tumPropertyler = typeof(A).GetProperties();
                  foreach (var property in tumPropertyler)
                      if (property.Name != "Id")
                          property.SetValue(guncellenecekNesne, property.GetValue(model));
                  Save();
                  return true;
              }
              [NonAction]
              public List<Type> GetWhere(Expression<Func<Type, bool>> metot)
              {
                  return GetWhere<Type>(metot);
              }
              [NonAction]
              public List<A> GetWhere<A>(Expression<Func<A, bool>> metot) where A : class
              {
                  return Table<A>().Where(metot).ToList();
              }
              [NonAction]
              public Type GetSingle(Func<Type, bool> metot)
              {
                  return GetSingle<Type>(metot);
              }
              [NonAction]
              public A GetSingle<A>(Func<A, bool> metot) where A : class
              {
                  return Table<A>().FirstOrDefault(metot);
              }
          }
      
    • Concreate Repository Sınıfları
      Entity bazlı somut repository sınıflarımızdır.

          public class PersonelRepository : Repository<Personel>
          {
          }
      
          public class SatisRepository : Repository<Satis>
          {
          }
      
    • DbContextService.cs Dosyası
      DAL katmanındaki context nesnesini dependency injectiondan talep edilebilir hale getirmek için ilgili nesneyi providera eklememiz gerekmektedir. Bunun için class librariy’de dependency injection’ın nasıl yapıldığını ele almamız gerekmektedir. Esasında evvelinde bu konuya dair şuradaki makaleyi kaleme almış bulunmaktayım. Burada biraz soluklanıp ilgili makaleye göz atabilirsiniz. Kısaca özetlemek gerekirse bu işlem için IServiceCollection arayüzü kullanılmaktadır.

          static class DbContextService
          {
              public static IServiceCollection AddDbContextService(this IServiceCollection services)
              {
                  ServiceProvider provider = services.BuildServiceProvider();
      
                  services.AddDbContext<SirketContext>(x => x.UseSqlServer("Server=.;Database=SirketDB;Trusted_Connection=True;"));
                  return services;
              }
          }
      

      Yukarıdaki kod bloğunu incelerseniz eğer IServiceCollection arayüzüne özel bir extension metot geliştirilmiştir. İlgili metot, services parametresi üzerinden provider inşa etmekte ve üretilen bu providera “AddDbContext” metodu ile DAL katmanındaki context nesnemizi gerekli konfigürasyonlarıyla birlikte eklemektedir.

      Ve yukarıda incelediğimiz repository sınıfınasatıra gitmek için tıklayınız göz atarsanız eğer ilgili sınıfın 4 ile 11. satır aralığında tanımlanmış olan constructer metodu içerisinde üretilen ServiceCollection nesnesi üzerinden bu oluşturduğumuz “AddDbContextService” isimli metot çağrılmakta ve oluşturulan provider üzerinden de context nesnemiz talep edilmektedir. Böylece uygulama, PL katmanında DAL referansına ihtiyaç duymaksızın direkt olarak BL katmanında veritabanı yeteneğini kazanmaktadır.

    • RepositoryService.cs Dosyası
      Yukarıda oluşturulan repository iskeletinide benzer mantıkla dependency injection providerına ekleyebilme sorumluluğunu üstlenmesi için oluşturduğumuz dosyadır.

          public static class RepositoryService
          {
              public static IServiceCollection AddRepositoryService(this IServiceCollection services)
              {
                  services.AddTransient<IRepository<Personel>>(x => new PersonelRepository());
                  services.AddTransient<IRepository<Satis>>(x => new SatisRepository());
                  return services;
              }
          }
      

      Görüldüğü üzere generic parametresine göre repository nesneleri üretilmekte ve Transient ile dependency injection providerına eklenmektedir.

Üretilen Servislerin Enjekte Edilmesi

Bu işlem için yapılması gereken tek şey PL katmanında Startup.cs dosyasının “ConfigureServices” metodunda “AddRepositoryService” isimli metodu çağırmaktır. Böylece tüm repository nesnelerimiz uygulamaya inject edilmiş olacaktır. Peki hoca, context nesnesini nasıl inject edeceğiz? sorunuzu duyar gibiyim… Hatırlarsanız eğer onu zaten BL katmanındaki oluşturduğumuz Repository sınıfında inject edip kullanmıştık. O yüzden PL’de context nesnesine dair inject işlemine gerek yoktur.

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            .
            .
            services.AddRepositoryService();
            .
            .
        }
    }

Bu işlemin ardından artık controller sınıflarınızı, context nesnelerini inject etmeye ihtiyaç duymaksızın oluşturabilir ve endpointler yahut actionlarınızı repository ile gönül rahatlığıyla kullanabilirsiniz.

Örnek bir controller;

    public class PersonelController : Controller
    {
        readonly PersonelRepository _personelRepository;
        public PersonelController(IRepository<Personel> personelRepository)
        {
            _personelRepository = (PersonelRepository)personelRepository;
        }
        public IActionResult Index()
        {
            return View(_personelRepository.Get());
        }

        public IActionResult Create()
        {
            return View();
        }

        [HttpPost]
        public IActionResult Create(Personel model)
        {
            _personelRepository.Add(model);
            _personelRepository.Save();
            return RedirectToAction("Index");
        }
    }

Nihai olarak;
Genellikle DAL katmanında oluşturulan context sınıfının PL’de kullanılarak işlevsel olarak problem yaratmayan ama çok katmanlı mimari stratejisinin etiğine aykırı olan bu davranışı irdelediğimiz yöntemde olduğu gibi ortadan kaldırmış ve stratejiyi olması gereken en doğru yöntemlerle, prensibe uygun bir şekle getirip oturtturmuş bulunmaktayız.

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

Not : Örnek projeyi indirebilmek için buraya tıklayınız.

Bunlar da hoşunuza gidebilir...

4 Cevaplar

  1. Samet dedi ki:

    Hocam ellerinize sağlık, biraz geç gördüm ama hala çözüme kavuşmamış halde projemde o kısmı bırakmıştım. Sayenizde hemen düzenleyeceğim. Yardımlarınız ve bizlerle paylaştığınız değerli bilgiler için çok çok teşekkür ederim.

  2. Ogulcan dedi ki:

    Repository patterni kullanmalı mıyız ? Anti-pattern olarak geçiyor bazı yerlerde.

    • Gençay dedi ki:

      Merhaba;

      Repository Design Pattern’in anti pattern olduğuna dair bazı yazarların fikirlerine göz atmışlığım vardır.
      Anladığım kadarıyla eleştirileri genel olarak aşağıdaki maddeler etrafında toplayabiliriz;

      • Alışkanlıktan ibaret olması; ORM vs. gibi teknolojilerin arkaplanda kullandıkları stratejilerden ötürü artık Repository tasarım desenine ihtiyaç duyulmadığı ve alışkanlıktan ötürü kullanıldığına dair eleştiride bulunanlar mevcuttur. Kesinlikle katılmadığım bir önermedir. Nihayetinde ORM gibi teknolojilerin her ne kadar verim arttırıcı ve maliyet düşürücü birçok nimeti sunmuş olması bizlerin uygulamalarımıza bu teknolojileri uygun desenlerle entegre etmememiz anlamına gelmemektedir. Repository arkaplanda doğru stratejileri benimsemiş ORM araçlarını uygulamanızda da pratik bir şekilde kullanılmasını sağlayan bir tasarım desenidir.
      • SOLID prensiplerine uygun olmaması; Repository tasarımının SOLID prensiplerine uymadığına dair eleştirilerde söz konusudur. Buda katılmadığım bir önermedir. Nihayetinde bir amaç uğruna geliştirilen ve gerçekleştirilen stratejinin, mantığın yahut fikrin bir prensibe uygun olup olmaması, o prensibi uygulayıp uygulamamasıyla ilişkilidir. Dolayısıyla prensip denen olgu, iş için işten önce yolu belirleyen normlar olmasından dolayı SOLID prensibini benimseyen bir kişinin yazdığı kod ve yaptığı inşa ilgili prensipler çerçevesinde her türlü tasarımları uygulayabilecek seviyede düşünülmüş ve tasarlanmış olacaktır.
      • Bir select sorgusunda tüm kolonların elde edilmesi; Repository tasarımı bir Generic Repository üzerinden gerçekleştirilen veritabanı operasyonuna odaklı bir geliştirme esasını benimsediğinden dolayı basit bir select sorgusu için çağrılan GET metodunun ilgili tabloya karşılık tüm kolon bilgilerini getirdiğine dair bir eleştiri söz konusudur. Kısmi olarak hak verebilirim. Lakin doğru bir tasarımla, Repository üzerinden oluşturulan sorgularda isteğe göre opsiyonel bir şekilde kolon talebini developer’a bırakabilmekte pek zor olmasa gerek.

      Bunların dışında tabi ki de tutarsız olduğu kadar tutarlı eleştirilerde mevcuttur. Ben burada Repository taraftarı konumuna düşmek istemiyorum 🙂 O yüzden son söz olarak her nimetin bir yan etkisi, az buçuk negatif etkisi hiç yoktan bir nebze maliyeti vardır, olacaktırda… O yüzden projenizin hacmine göre kullandığınız stratejiden ya da teknolojiden beklentiniz olmasını öneririm. Bir orta ölçekli e-ticaret tasarlıyorsanız Repository kullanmanızın hiçbir mali kaybı olmayacaktır, lakin daha büyük ölçekli bir çalışma gerçekleştiriyorsanız Domain Driven Design yapmanız daha makul ve lüzumsuz tartışmalardan arındırılmış bir karar olacaktır.

      Sevgiler…

Bir cevap yazın

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

*