Parçalarını Söküp Takabileceğimiz Bir Codebase Oluşturmak — 2.Bölüm: Dependency Injection
Birinci bölümde, yaşayan bir kod yapısı kurabilmek için bize yardımcı olan kavramlardan biri olan Interfacelerden bahsetmiştim.
Hayali örneğimizde, projenin veri erişim katmanını bir gün farklı bir ORM kütüphanesi kullanarak yeniden yazmak durumunda kalırsak, bu işin altından en az eforla kalkabilmek için şimdiden yapabileceklerimizi ele almaya başlamıştık.
İşleri yapacak olan classlarımızı paldır küldür yazıp kullanmadan önce, bu classların neler yapacağını, nasıl görüneceğini belirleyen Interfaceler yazmanın önemini görmüştük.
Bu bölümde ise yazdığımız yeni classları eskileri ile değiştirmek zorunda kalabileceğimiz gün için yapabileceğimiz hazırlıkları konuşacağız.
Bu konuda yardımımıza koşacak olan kavramın ismi Dependency Injection.
Programlama bağlamında dependency, bir classın kendi işini yapabilmek için ihtiyaç duyduğu başka bir objedir.

Örneğin Student tipiyle ilgili mantıksal işlemler yapmakla sorumlu bir class, veritabanına kendisi gitmez de, giden bir objeden yardım alır. Böylelikle o objeye bağımlı olmuş olur. Bağımlılıklardan kaçamayız ama onları kontrol altında tutabiliriz.
Dependency Injection, bir objenin bir classa dışarıdan verilmesi, başka bir deyişle enjekte edilmesidir.

Peki bir obje bir classa neden ve nasıl enjekte edilir?
Dependency Injection’ın uygulanmadığı durumlarda, classların bütün dependencylerini kendilerinin yaratmaları gerekir. Fakat bu pek de hoş bir şey değil.

Yukarıdaki gibi yazılmış bir StudentOperation classında, classın bir görevi de StudentRepositoryVersion1 objesi yaratmaktır. Peki gerçekten böyle mi olmalı?
Hayır.
Farklı kategorilerdeki işleri halletmek için farklı farklı classlar yazarız. Her classa spesifik bir sorumluluk veririz ve üzerine düşenden fazlasını yaptırmamaya dikkat ederiz.
Oysa obje yaratma işi de başlı başına bir sorumluluktur.
Objelerin değişik şartlar altında değişik classlardan yaratılmaları gerekiyor olabilir; uygulamanın çalışma zamanı boyunca, N adet instance yaratılması yerine 1 instance ile sınırlandırılmasına ihtiyaç duyuluyor olabilir, vs.
Dolayısıyla tüm bunlarla ilgilenecek özel yapılar kurulmalı, classlar dependencylerini kendileri yaratmak zorunda kalmadan bir yerlerden temin edip sadece kullanmalılar.
Bir class, public elemanları aracılığıyla dışarıdan dependency alabilir.


Sol taraftaki kodda StudentOperation classı ihtiyaç duyduğu objeyi kendisi yaratıyor. Sağdaki kodda ise constructorından hazır olarak alıyor. Soldaki örnekte sadece StudentRepositoryVersion1 sınıfı ile çalışabilecek durumdayken, sağda IStudentRepository interfaceini sağladığı sürece, kendisine verilen herhangi bir obje ile çalışabilecek şekilde yazılmış.
Bu da tam olarak bizim istediğimiz şey.
Bugün Entity Framework kullanılarak yazılmış bir classın objesini kullanırken, yarın bambaşka bir classı rahatlıkla kullanabilir. Çünkü işi hangi classın yapacağına kendisi karar vermiyor, bununla ilgilenmiyor.
Dependencylerin neden dışarıdan alınması gerektiğini konuştuk. Şimdi bunun nasıl yapılabileceğine geçelim.
Bu Dependency Injection yapısını kendimiz yazabileceğiniz gibi, bu iş için yazılmış kütüphaneleri de kullanabiliriz.
Autofac, Ninject, Unity .Net uygulamalarında kullanılabilecek Dependency Injection kütüphanelerinden birkaçı.
.Net Core’da ise bu iş için projeye harici bir kütüphane eklememize gerek kalmıyor. Dependency Injection yapısı, .Net Core frameworkünde built-in olarak mevcut durumda.
Şu anda elimizde 2 adet StudentRepository classı var. Aynı arayüzü sağlıyorlar. Aynı işi farklı ORM kütüphaneleri yaparak yapıyorlar.


Yukarıdaki 2 classtan birini kullanacak olan sınıflarımızı da, bu dependencyyi dışarıdan alacakları şekilde yazdık.
.Net Core Dependency Injection yapısı, IStudentRepository tipinde bir obje isteyen herkese bu iki classtan birini verecek. Fakat şu haliyle bir problem var. Hangisini vermesi gerektiğini henüz bilmiyor. Bunun özel olarak belirtilmesi gerek.
.Net Core uygulaması ayağa kalkıktan çok kısa bir süre sonra çalışan ConfigureServices methodunun içinde bu belirtme işlemini yapmamız gerekiyor. services parametresi yardımıyla enjekte edilecek classları belirtip, kontrolü tamamen dependency enjektörüne bırakıyoruz.

Yukarıdaki kod sayesinde Dependency Injector, IStudentRepository tipinde bir obje isteyen tüm classlara, StudentRepositoryVersion2 classından yaratacağı bir objeyi vermesi etmesi gerektiğini artık biliyor.
Yeni bir class yazdıktan sonra, eskisini çıkartıp yeni classı devreye almak için değiştirmemiz gereken tek yer de burası olmuş oluyor.
Dikkat çekmek istediğim bir nokta var. Ben örnekte AddTransient() methodunu kullandım. Bu methodun, AddSingleton() veya AddScoped() isimli alternatifleri de var.
IStudentRepository tipinde bir obje, birden fazla class tarafından isteniliyor olabilir. AddTransient kullandığımızda, her istek için yeni bir obje yaratılıp verilirken, AddSingleton ile herkese aynı obje verilir. AddScoped ise, bir request-response döngüsü boyunca uygulama genelinde aynı objenin kullanılmasını sağlar.
Neden N adet ya da yalnızca 1 instance kullanılmasına ihtiyaç duyabileceğimiz de önemli bir konu. Fakat bu yazının odağını dağıtmamak için bu konuyu burada bırakıyorum.
Dependency Injection sayesinde neleri başarmış olduğumuzu özetleyip 2.bölümü de noktalayalım.
Proje içindeki classları, direkt olarak başka classlara muhtaç bırakmaktansa, yalnızca iş tanımları ile ilgilenecekleri şekilde yazdık. Bu iş tanımlarını sağlayan classları inşa etme görevini de ellerinden aldık. Bu görevi, bu iş için özelleşmiş bir yapıya bıraktık.
Bu sayede, bazı parçalarını söküp yerine yeni parçaları rahatlıkla takabildiğimiz bir codebaseimiz oldu.
Bir sonraki bölümde, bu şekilde kod yazmanın bonusu olarak kazandığımız bir imkandan bahsedeceğim: Bu şekilde kod yazmak, Unit Test yazmayı da mümkün kılıyor.
Unit Test üzerine konuşacağımız 3.bölümde görüşmek üzere…
Bu serinin diğer bölümlerine aşağıdaki linklerden ulaşabilirsiniz:

