Profesyonel Projelerimizde Dependency Injection Yaklaşımı

   Dependency Injection makele serisinde geldiğimiz noktada sıra geldi bir solution üzerinden konuyu örneklemeye. Aslında, bu makalemde paylaşacaklarım örneklemeden öte olacak. Amacım iş hayatınızda kullanabileceğiniz bir yaklaşım, bir metodolojiyi sizlerle paylaşmak. Bu sebeple dikkatle okumanızı tavsiye ederim. Öncelikle, gevşek bağlı (loosely coupled) uygulamalar geliştirmenin ne kadar önemli olduğunu bir kez daha hatırlatmak isterim. Hatta önemli olmakta da öte bir yazılım geliştiricinin hayatını kolaylaştırdığını söylemeliyim. Solution’ımız makalelerimde hep örneklediğimiz öğrenci işlemleri olsun.

   Kodlamaya başlamadan önce elimize kağıdı kalemi alıp tasarıma başlayalım. Aktörlerimiz kimler? bu aktörler arası etkileşimler nedir? Bu etkileşimlerde ne tür veriler kullanılmakta? Kağıda yazdığımız bu notlar aslında biz farkında olmasak da uygulamamızın ilk satırları olmaya başladılar bile. Kağıt üzerindeki tasarımımızın ardından bir Visual Studio açarak Enterprisecoding.DIOrnek adıyla bir solution oluşturalım. Solution’ımız gereksiz detaylarla uğraşmamak ve konuya odaklanmak adına bir konsol uygulaması olsun. Hemen ardından biraz önce tasarladığımız domain’imizi anlatacağımız Enterprisecoding.DIOrnek.Domain projesini oluşturalım. Enterprisecoding.DIOrnek.Domain bir sınıf kütüphanesi projesi olmalı.

Solution'ımızda yer alan projeler

   Enterprisecoding.DIOrnek.Domain projesi aktif bir iş mantığı kodu barındırmamalı. Bu proje ile amacımız sadece gerçek hayattaki modelimizi (Domain’imizi) programsal olarak anlatabilmek. Dolayısıyla içerisinde POCO sınıfları ve arayüzler dışında bir şey barındırmamalı;

Enterprisecoding.DIOrnek.Domain projesinin son hali

   Yukarıda da görüldüğü gibi projede günlükleri tutacak, verileri saklayacak ve öğrenci işlemlerini gerçekleştirecek atomik depolarımızın tanımı var. Ek olarak; öğrenci verisini taşıyacak bir de POCO sınıfımız. Enterprisecoding.DIOrnek konsol uygulamamızın referanslarımız arasına hemen Enterprisecoding.DIOrnek.Domain projesini ekleyelim. Bir başka deyişle, uygulamamızı modellediğimiz domain’imizden haberdar edelim. Bu adımla artık uygulamamız domain’imiz içerisinde neler yapabileceğini bilecek. Burada dikkat etmemiz gereken nokta uygulamamızın sadece neler yapabileceğini bilmesi… Nasıl yapıldığı konusunda hiçbir fikri yok. Zaten olmamalı da. Serinin ilk makelesindeki prensibimizi hatırlayın 😉

   Şimdi. Madem uygulamamız artık bu domain üzerinde neler yapabileceğini biliyor. Bu durumda öğrenci deposu ile aşağıdaki gibi bir kod yazabiliriz;

var ogrenci = new Ogrenci {
               No = 12345,
               Adi = "Fatih",
               Soyadi = "Boy",
               Bolum = "Bilgisayar Mühendisliği"
           };

var kayitBasariliMi = ogrenciDeposu.Kaydet(ogrenci);
Console.WriteLine(kayitBasariliMi ? "Öğrenci başarıyla kaydedildi" : "Öğrenci kaydedilemedi");

   Dikkat ederseniz öğrenci deposunun nasıl ilklendiğini paylaşmıyorum, henüz Ninject’i kullanmadık. Çünkü Ninject amacımıza, gevşek bağlı sisteme, ulaşmak için sadece bir araç. Sırası geldiğinde kullanacağımız bir araç. Uygulamamı sadece Domain’imde belirttiğim POCO ve arayüzleri kullanarak geliştirmeye başladım. Nasıl’ını önemsemediğim için işlevlerin tam ve eksiksiz olduğunu varsayıyorum. Hatta, iş mantığı kodlarını bir başka geliştiricinin yazdığını varsayalım. Bu noktada uygulamamı açarak test etmek, iş mantığını değil; ama kendi kodlarımı doğru yazdığımı görebilmek amacıyla bu arayüzlerin birer örneğine ihtiyacım var. İşte tam da bu noktada artık sıra geldi yeni bir proje’yi daha solution’a eklemeye. Enterprisecoding.DIOrnek.Domain.Moq adıyla bir sınıf kütüphanesi oluşturalım ve çıktısının Enterprisecoding.DIOrnek projesi ile aynı dizin olduğunu garanti edelim;

Moq projesinin çıktı klasör ayarı

   Projemiz domain’imizi anlatacağı için Enterprisecoding.DIOrnek.Domain proje referansını da ekleyelim. Bu projede mocking yaparak arayüzlerimin boş implemantasyonlarını yapmayı planlıyorum. Sıfır iş mantığı… Bunun için de Moq kütüphanesini tercih ediyorum. Bu sebeple projemize Moq Nuget paketini de ekleyelim;

Moq Nuget paketinin eklenmesi

   Moq kütüphanesi kolay bir şekilde sahte sınıflarla ilgili arayüzleri implemente etmemi sağlayacak. Detaylarını bilmiyorsanız da önemli değil.

   Proje içerisinde hemen bir Modul sınıfı açıp önceki makalelerimizde öğrendiğimiz şekilde bir Ninject modülü oluşturalım. Daha sonra da modülümüzü içerisinde aşağıdaki şekilde sahte sınıflarımızı oluşturalım.

public sealed class Modul : NinjectModule {
    public override void Load() {
        var gunlukDeposuMock = new Mock<IGunlukDeposu>();
        var ogrenciDeposuMock = new Mock<IOgrenciDeposu>();
        var veriDeposuMock = new Mock<IVeriDeposu>();

        veriDeposuMock.Setup(veriDeposuOrnegi => veriDeposuOrnegi.Kaydet(It.IsAny<object>())).Returns(635428485297202582);

        ogrenciDeposuMock.Setup(ogrenciDeposuOrnegi => ogrenciDeposuOrnegi.Kaydet(It.IsAny<Ogrenci>())).Returns(true);
        
        Bind<IGunlukDeposu>().ToConstant(gunlukDeposuMock.Object);
        Bind<IOgrenciDeposu>().ToConstant(ogrenciDeposuMock.Object);
        Bind<IVeriDeposu>().ToConstant(veriDeposuMock.Object);
    }
}

   Moq ile ilk defa karşılaşıyorsanız yukarıdaki satırlar kafanızı karıştırmış olabilir. Üzülmenize gerek yok. Sadece bu satırlarda boş bir VeriDeposu sınıfı oluşturup Kaydet fonksiyonunda her zaman için 635428485297202582 değerinin dönmesini sağladığımızı bilmeniz yeterli. Benzer şekilde boş bir OgrenciDeposu sınıfı da sadece her kaydet dediğimizde true yanıtını verecektir. Unutmayın, konsol projemizde bu sınıflarının içinin nasıl olduğu ile ilgilenmiyoruz; hatta içlerini başkasının yazdığını varsayıyoruz.

   Şimdi tekrardan Konsol projemize geri dönerek Ninject yardımıyla bu boş sınıfları / sahte domain implemantasyonunu kullanalım;

using Com.Enterprisecoding.DIOrnek.Domain.Depo;
using Com.Enterprisecoding.DIOrnek.Domain.Varlik;
using Ninject;
using System;
 
namespace Com.Enterprisecoding.DIOrnek {
    class Program {
        static void Main() {
            var ogrenci = new Ogrenci {
                No = 12345,
                Adi = "Fatih",
                Soyadi = "Boy",
                Bolum = "Bilgisayar Mühendisliği"
            };
 
            var kernel = new StandardKernel();
 
            kernel.Load("DIOrnek.Domain.Moq.dll");
 
            var ogrenciDeposu = kernel.Get<IOgrenciDeposu>();
 
 
            var kayitBasariliMi = ogrenciDeposu.Kaydet(ogrenci);
            Console.WriteLine(kayitBasariliMi ? "Öğrenci başarıyla kaydedildi" : "Öğrenci kaydedilemedi");
        }
    }
}

   Geldiğimiz noktada neden böylesi bir patikayı tercih ettiğimiz sorusu aklınıza gelebilir. Basit; amacımız projenin hızla ilerleyebilmesi. Bunun için de birden fazla kişinin aynı anda çalışabilmesi gerekir. Yani iş mantığı kodları geliştirilirken paralelde önyüzlerde beklenmeden geliştirilmeli, test edilmeli. Ancak bu sayede ekip üyelerinin hepsi tam zamanlı ve yüksek verimli olarak kullanılabilir. Bakın, iş mantığına dair bir satır kod yazmadık; ama bunu beklemeye gerek kalmaksızın önyüz geliştirebildik. İş mantığı tarafında her şeyin doğru yapıldığını varsaydık…

   Ok. Diyelim ki önyüz geliştirilirken bir başka ekip üyesi de iş mantığını geliştirmeye başladı. Hemen solution’a yeni bir proje daha ekleyelim. Projemiz Enterprisecoding.DIOrnek.Domain.Imp adıyla yine bir sınıf kütüphanesi olsun. Yeni projemize domain’imizin referansını ekleyelim. Moq projesinden farklı olarak bu defa gerçekten iş mantığını da yazalım;

Imp projesi

   Yine bu projede de bir Modul sınıfı oluşturup NinjectModule’den türetelim;

using Com.Enterprisecoding.DIOrnek.Domain.Depo;
using Com.Enterprisecoding.DIOrnek.Domain.Imp.Depo;
using Ninject.Modules;
 
namespace Com.Enterprisecoding.DIOrnek.Domain.Imp {
    public sealed class Modul : NinjectModule {
        public override void Load() {
            Bind<IGunlukDeposu>().To<GunlukDeposu>().InSingletonScope();
            Bind<IOgrenciDeposu>().To<OgrenciDeposu>().InSingletonScope();
            Bind<IVeriDeposu>().To<VeriDeposu>().InSingletonScope();
 
        }
    }
}

   Bu defa her şey gerçek olduğu için işimiz daha kolay. Singleton bağlamı ile ilgili arayüzlere tür bağlaması yaptım. Geliştirme sürecinde testlerimi normal şekilde yaptım.

   Tüm bunlar ardından geriye dönüp baktığımızda elimizde sahte sınıflar üzerinden geliştirdiğimiz bir arayüzümüz ve Domain’imizin gerçek implemantasyonu var; fakat henüz bu ikisi birleşik değil. İşin güzel tarafı birleştirmek için yapmamız gereken tek şey konsol uygulamasında artık yeni modülü yüklemek;

kernel.Load("DIOrnek.Domain.Imp.dll");

   Bu kadar basit… Hatta daha önceden sizlerle paylaştığım yöntemlerle gidecek olursak, bu şekilde hard-coded bir yükleme yerine dinamik olarak ilerleyebilmemiz de mümkün. Önemli olan konsol projenizde hiçbir şekilde implemantasyon projelerine dair bir referans bulunmaması. Yani konsol uygulamanızın iş mantığını (nasıl’ı) bilmemesi… Gevşek bağlı bir tasarımın meyvesi için tam da bu noktada, bir implemantasyon başka bir implemantasyona gerçerken alınacaktır. Örneğimizi basit tutmak adına kodu hard-coded şekilde sade yazmayı tercih ettim.

   Gördüğünüz gibi uygulamamı gevşek bağlı olarak tasarlamış olmak bana aynı anda tüm ekip üyelerimi kullanabilmeyi sağladı. Bu da projenin daha kısa sürede tamamlanması anlamına gelecektir. Daha esnek olma konusuna geldiğimizde ise… örneğimizden devam edelim;

   Varsayalım ki uygulamanızı sattınız. Hatta o kadar başarılı oldu ki onlarca, yüzlerce müşteriniz var. Bir sorununuz var; başta ön görmediğiniz kadar yoğun kullanılıyor ve performansının arttırılması şart. Bu noktada tek yapacağınız daha önceden text dosyaları ile işlem yapan versiyonu veritabanı ile işlem yapan ile değiştirmek. Aynı Moq versiyonu yerine Imp versiyonunu kullanmak gibi. Hiçbir farkı yok. Hemen Enterprisecoding.DIOrnek.Domain.DB adıyla yeni bir proje açıp işe koyulabilirsiniz. İşiniz bittiğinde tek yapmanız gereken daha önce olduğu gibi yeni modülün yüklenmesini sağlamak.

 

Pro Tip;

   Yukarıda sizlerle paylaştığım bu tasarım ile ilgili gördüğüm en önemli yanlışlık domain’in doğru ve ayrılmış şekilde tasarlanmamış olmasıdır. Domain’inizin iş modelini doğru yansıtmaması er ya da geç duvara toslamanıza neden olacaktır. Bu çarpanın şiddeti ise projenizin büyüklüğü ile doğru orantılı olacaktır.

   Domain’inizin doğru şekilde ayrılmamış olması, iş mantığı kodları/kütüphaneleri ile çok içli-dışlı olması (yani bağımlı olması) ileryen zamanlarda az önce örneklediğim implemantasyon geçişlerinde kesinlikle problem olacaktır. Asla unutmayın; domain implemantasyonu sırasında kullandığınız bir pdf kütüphanesine ait sınıfın Domain modelinizde bir/birkaç fonksiyonda parametre olarak kullanılması sizin bir başka pdf kütüphanesine geçişinizi imkansız hale getirebilir.

 

Kaynak Kod;

Download   Bu makalemde paylaştığım solution kodlarını Github respository’si üzerinden inceleyebilir & indirebilirsiniz : https://github.com/fatihboy/DIOrnek

Fatih Boy

Ankara'da yaşayan Fatih, bir kamu kurumunda danışman olarak çalışmaktadır. ALM süreçleri, kurumsal veri yolu sistemleri, kurumsal altyapı ve yazılım geliştirme konularında destek vermektedir. Boş zamanlarında açık kaynak kodlu projeler geliştirmeyi ve bilgisini yazdığı makalelerle paylaşmayı seven Fatih, aynı zamanda Visual C# ve Visual Studio teknolojileri konusundan Microsoft tarafından altı yıl üst üste MVP (En Değerli Profesyonel) ödülüne layık görülmüştür. İş hayatı boyunca masaüstü uygulamaları, web teknolojileri, akıllı istemciler gibi konularda Asp.Net, Php, C#, Java programlama dilleri ve MySql, MsSql ve Oracle gibi veritabanı yönetim yazılımları ile çalışmıştır. İngilizce ve Türkçe olarak yayınlanan makalelerini gerek İngilizce bloğunda, gerekse de Türkçe bloğunda bulabileceğiniz gibi web sitesinden de açık kaynak kodlu geliştirdiği yazılımlarına ulaşabilirsiniz. vCard - Twitter - Facebook - Google+

Bir Cevap Yazın

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