Merhabalar,
Bir önceki yazımızda Autofac’in IoC container olarak nasıl kullanılacağından örnek bir proje üzerinden bahsetmiştik. Bir önceki yazıya buradan kaynak kodlara ise buradan erişebilirsiniz.
Şimdi ise bir önceki proje de oluşturduğumuz yapı üzerinden Autofac ile nasıl Aspect Oriented Programming yaparız konusunu inceleyeceğiz.
Projelerimiz her geçen gün büyümektedir. Ne kadar OOP gibi prensipleri uygulamaya çalışsakta proje büyüdükçe kodların anlaşılabilirliği ve projenin bakımı zorlaşmaktadır. İşte tam bu noktada Aspect Oriented Programming bize yardımcı olmaktadır. Aspect Oriented Programming karmaşıklığı azaltmaya, okunabilirliği ve modularity’i artırmaya yarayan bir yaklaşım biçimidir. Modularity’i arttırmak demek birbirleri ile kesişen ilgilerin (Cross-Cutting Concerns) ayrılmasına denilebilir. Şimdi de karşımıza başka bir kavram daha çıktı, Cross-Cutting Concerns.

Cross-Cutting Concerns ifadesinin türkçe anlamı kesişen ilgilerdir. Yukarıda fotoğraf üzerinden konuyu anlatmak çok daha sağlıklı ve anlaşılır olacaktır. Yukarıdaki proje yapısı katmanlar olarak bizim demo projemize benzemektedir. Hemen sağ tarafta ise cross cutting concern başlığı altında bir fotoğraf yer almaktadır. Burada security, caching, logging gibi ifadeler yer almaktadır. Bunların hepsi birer ilgi yani concern’dür. Kesişen ilgi denmesi ise dikkat ederseniz eğer bu ifadeler dik olarak yazılmış yani tüm katmanları kapsamıştır. Yani bu concerns’lerimiz projeyi dik kesmiştir. Dik kesmek demek bu fonksiyonların projenin genelinde kullanılabilmesine denilmektedir. Yani örnek olarak biz her yerde loglama yapabiliriz. Mesela product sınıfımızı düşünelim biz burada ürün eklerken ekleyen kişinin yetkisi olup olmadığını kontrol edebiliriz(security), hata olması durumunda hatayı yine bu sınıfta yakalayabiliriz(exception-handling), eklenen ürün istenilen kurallara sahip mi kontrolü yapabiliriz(validation) ve bu işlemleri kayıt altına alabiliriz(logging). Aspect Oriented Programming’in çıkış noktası ise buradan gelmektedir. Yani Aspect Oriented Programming bu kesişen ilgilerin birbirinden ayrılması gerektiğini ifade etmektedir. Yani Aspect Oriented Programming bizlere sen bu kesişen ilgileri her yerde yazmada bir yerde yaz her yerde kullan demektedir. Projemizin pek çok yerinde bu fonksiyonları kullanabileceğimizden DRY(Dont Repeat Yourself) prensibi gereği yeniden kullanılabilirliğin artması için bu ilgilerimizi birbirinden ayırıp soyut bir yapı elde etmeliyiz. Şimdi bunu nasıl uygulayacağımıza gelelim. İlk olarak business katmanımıza aşağıdaki paketleri ekliyoruz.

Burada ki ilk iki paketi önceki proje de kullanmıştık. İlk ikisi Autofac’i IoC container olarak projeye ekleyip .Net ile entegre etmeye yaramaktadır. Son paket olan DynamicProxy ise Aspect Oriented Programming’i etkinleştirerek interception olanağı sağlayacaktır. Interception kavramına daha sonra tekrar değineceğiz.
Business katmanı içerisinde CrossCuttingConcerns adında bir klasör oluşturup içine Validation adında bir klasör daha ekliyoruz daha sonra eklenen ürünün kurallara uygun olup olmadığını tespit etmek için ProductValidator adında bir sınıf oluşturuyoruz.

Bu sınıfta validasyon işlemini yapmak için Fluent Validation kütüphanesini kullanabiliriz. Bu sınıfı Fluent Validation kütüphanesine ait olan AbstractValidator sınıfından türetip tip olarak da doğrulayacağımız sınıfı yani product sınıfını veriyoruz. Daha sonra constructor içinde olması gereken kuralları RuleFor ifadesi ile yazıyoruz. Burada Fluent Validation kütüphanesinin bize sağladığı hazır metotları kullanabileceğimiz gibi kendi isteğimizi göre bir metot yazıp RuleFor içinde bunu da belirtebiliriz tıpkı StartWithA metodu gibi. Sıra geldi bu sınıfı kullanmaya. İlk önce InterceptionAttribute sınıfını oluşturuyoruz. Bu sınıf bizim metot çalışma anında araya girip yapmak istediğimiz işlemleri gerçekleştirmeye yarayacaktır.

InterceptionAttribute sınıfı IInterceptor sınıfını miras almaktadır. Bu sınıf bize Autofac’in sağladığı DynamicProxy paketinden gelen bir sınıftır. IInterceptor aracılığıyla interception yani araya girme işlemini gerçekleştireceğiz. Peki interceptor nedir? Metot çağrıldığı zaman istenildiğinde araya girerek cross cutting concerns’leri çalıştırıp yönetmemizi sağlar. Mesela biz bir metotda o metodun ilk çağrıldığı zaman bir işlem yapmak isteyebiliriz yada metot ortasında veya metot çalışmasını bitirdiği zaman bazı işlemler yapmak isteyebiliriz işte bu interceptor ile istediğimiz zaman metotlara müdahale edip araya girerek yapmak istediğimiz işlemleri gerçekleştirebiliriz. Bu sınıfın Intercept adında implement edilmesi gereken bir metodu bulunmaktadır. Bu metodu implement edip virtual ifadesini ekliyoruz yani diyoruzki biz InterceptionAttribute sınıfını miras aldığımız zaman içerisindeki Intercept metodunu kendimize göre özelleştireceğiz. Intercept metodunun IInvocation adında bir parametre aldığına dikkat etmişsinizdir. Buradaki invocation’ı kullanarak araya hangi metot için girdiysek o metodu çalıştıracağımız zaman invocation.Proceed() diyeceğiz. Metot da görüldüğü gibi bir try catch bulunmaktadır. Bu metodun çalışması için invocation.Proceed() dememiz gereklidir. Metodun çalışma işlemi try bloğunun içindedir. Try dan önce OnBefore tanımlanmıştır. Metot çalışmadan bir işlem gerçekleştirmek istiyorsak bu sınıfı miras aldıktan sonra OnBefore metodunu override edebiliriz. Biz eğer metot çalışmadan yani invocation.Proceed() işleminden önce tıpkı product validation gibi bir şey yapmak istiyorsak virtual olarak tanımlanmış OnBefore metodunu kullanabiliriz. Hata anında catch içinde tanımlanan OnException metodunu, başarılı durumda OnSucces metodunu, metot tamamlandığında ise OnAfter metodunu override edip bu durumlarda yapmak istediğimiz işlemi metodun içerisine yazabiliriz.
InterceptionAttribute sınıfını IInterceptor sınıfına ek olarak Attribute sınıfınıda miras almaktadır. Bu sınıf bize sınıflarımızı attribute olarak metot üzerinde tanımlamamıza olanak sağlayacaktır. Biz biraz sonra tanımlayacağımız ValidationAspect sınıfını InterceptionAttribute sınıfından türeteceğimiz için ValidationAspect sınıfımızı attribute olarak kullanılabileceğiz.
Şimdi de ValidatorAspect adında bir sınıf oluşturuyoruz.

Bu sınıf InterceptionAttribute sınıfını miras alıyor. Bu miras alınan sınıf attribute sınıfından türediği için ValidationAspect sınıfını metot üzerinde attribute olarak kullanabileceğiz. Aspect Oriented Programming uygularken birkaç yöntem bulunmaktadır ama ben attribute kullanımının daha açıklayıcı olacağını düşündüm.
ValidationAspect sınıfı vasıtasıyla ProductService sınıfının Add metodu çağrıldığı zaman araya girip doğrulama işlemini gerçekleştireceğiz. Yani metot çalışmadan bu doğrulama işlemini gerçekleştireceğimiz için InterceptionAttribute sınıfının OnBefore metodunu override ediyoruz. ValidationAspect sınıfı kendisine gönderilen type türünde validator nesnesini constructor içerisinde alıyor. Daha sonra OnBefore metodu içerisinde bu gönderilen type kullanılarak instance oluşturuluyor. Biz burada type olarak yukarıda oluşturduğumuz ProductValidator’u gönderdik. Daha sonra metodun parametreleri alınarak oluşturulan instance vasıtasıyla Fluent Validation kütüphanesinin Validate metodu ile kontrol yapılıyor. Eğer ProductValidator sınıfında tanımladığımız kurala uymayan bir ürün eklersek Fluent Validation kütüphanesine ait olan ValidationException sınıfı devreye girerek ilgili hatayı fırlatıyor.
Son olarak autofac konfigurasyonu yaptığımız sınıfta interception işlemini devreye alacak kodu yazıyoruz.

DataModule sınıfında işaretlenen kısımda ilk önce o an çalışan assembly döndürülür. Daha sonra bu assembly nesnesi kullanılarak EnableInterfaceInterceptors ifadesi ile araya girme işlemini aktifleştir diyoruz. Son olarak ProxyGenerationOptions sınıfına AspectSelector adında bir sınıf veriyoruz. ProxyGenerationOptions sınıfı kendisine verilen aspectleri kayıt etmeye yaramaktadır.

ProxyGenerationOptions sınıfı kendisine verilen aspectleri kayıt etmeye yaramaktadır demiştik. Kendisine aspect verme işini ise yukarıdaki AspectSelector sınıfı ile yapıyoruz. Bu sınıfın yaptığı iş method üzerindeki attribute’ları(yani tanımladığımız aspectleri) okuyup bunları ProxyGenerationOptions sınıfına göndermektir. Bu işlemleri bir kez yapmak yeterlidir. Alt yapıyı oluşturduktan sonra artık hangi concern için işlem yapacaksak onun aspect sınıfını oluşturmak yeterli olacaktır. Şimdi bu işlemler sonucu ValidationAspect’imizi kullanalım.

İşte kullanımı bu kadar kolay ve anlaşılır. Biz ValidationAspect sınıfımızı oluşturduk ve bu sınıfta ProductValidator sınıfımızda oluşturduğumuz kurallara göre product nesnemizi doğruladık. ValidationAspect sınıfı InterceptionAttribute sınıfını miras aldığı için hem [ValidationAspect(typeof(ProductValidator))] ifadesi ile attribute olarak kodumunu yazabildik hem de bu sınıf içerisindeki OnBefore metodu sayesinde add metodu çalışmadan araya girme işlemini gerçekleştirdik. Artık bir ürün ekleyeceğimiz zaman _productDal.Add metodu çalışmadan önce ValidationAspect sınıfı çalışacak ve eğer kurala uygun ürün eklemişsek normal seyirde devam edip ilgili ürünü ekleyecek eğer kurala uymayan bir ürün eklersek Add metodu çalışmayıp ilgili hatayı fırlatılacak.
Şimdi de projelerimizin vazgeçilmesi olan logging mekanizmasını aspect olarak kullanalım. Biz burada Serilog kütüphanesini kullanacağız bunun için aşağıdaki paketi projeye ekliyoruz.

Yine aynı mantıkla CrossCuttingConcerns klasörü içinde Logging adında bir bir klasör oluşturup bu klasör içinde de LogAspect adında sınıf oluşturuyoruz. Bu sınıf tıpkı ValidationAspect de yaptığımız gibi InterceptionAttribute sınıfından türemektedir.

Yukarıdaki sınıf metot çalışmaya başladığında, hata verdiğinde, başarılı çalıştığında ve çalışmayı bitirdiği zaman bu işlemleri kayıt altına alıyor. Buradaki kayıt altına alınan veriler hiçbir şey ifade etmiyor yani burada sadece örnek olması için metot başladı, bitti gibi ifadeler kullandık. Gerçek hayat projelerinde kullanacağımız zaman bu kısımları amaca uygun olacak şekilde özelleştirmeliyiz.
Burada Serilog için bazı konfigurasyonlar yaptık. Projeye serilog kütüphanesini eklerken Gençay Yıldız’ın bir makalesinden faydalandım. Makaleye buradan ulaşabilirsiniz. İlk olarak appsettings.json dosyası içerisinde Serilog konfigurasyon kodlarımızı yazıyoruz.

Bu dosyada Using içerisinde Serilog.Sinks.Console ve Serilog.Sinks.File ifadelerini yazıyoruz. Bu ifadeler loglama yapacağımız platformları belirtmektedir. Biz burada konsola ve bir txt dosyasına yazacağımız için bu ifadeleri ekliyoruz. Burada Serilog.Sinks.Console ve Serilog.Sinks.File paketlerinide projeye eklememiz gerekmektedir. Daha sonra minimum level ile log level belirtiyoruz. Burada information projenin normal çalışması sırasında ne olduğunu belirtirken error ise hata durumlarını açıklamaktadır. Biz şuanlık sadece error ve information durumlarında kayıt altına alınması gerektiğini söylüyoruz. Write to ile kayıt atacağımız platformları söyleyip file için yazılmasını istediğimiz dosyanın yolunu belirtiyoruz. Daha sonra konfigurasyon dosyasını istediğimiz kullanımlara göre özelleştirip tamamlıyoruz. Şimdi de ProductService sınıfında bunu kullanalım.

GetAll metodunun üzerine LogAspect’i attribute olarak ekliyoruz ve projemizi ayağa kaldırıp GetAll metodunu çalıştırıyoruz. Daha sonra belirttiğimiz dizinde bir log.txt dosyası oluşuyor. İçeriği ise bizim LogAspect sınıfında belirttiğimiz işlemleri içeriyor.

İşte bu şekilde de istediğimiz işlemleri bir attribute vasıtasıyla kayıt altına almış olduk.
Biz projemizde cross cutting concerns olarak logging ve validation kullandık. Transaction, cache, performance, security, exception-handling gibi concerns’lerde yazmamız mümkün. Sonuçta mantık artık aynı olmuş oldu.
Sonuç olarak bir business metodunda bahsettiğimiz concerns’leri teker teker yazıp kod kalabalığına sebep olup okunabilirliği azaltmak yerine bunları metotlarımızdan soyutlayıp ayrı bir sınıfta tanımladık daha sonra bu sınıfları attribute olarak tanımladık. Böylece business metodumuz çok daha yalın olmakla beraber cross cutting concerns’lerimiz birbirinden ayrıldı dolayısıyla kodumuzu tekrar etmeyip yeniden kullanılabilirliği arttırdık. Aspect Oriented Programming’in amacı olan kesişen ilgileri ayırmayı başardık.
Bu yazımızda Aspect Oriented Programming nedir? Cross cutting concerns nedir? Autofac ile Aspect Oriented Programming nasıl uygulanır ve bu tekniğin bize faydaları nelerdir gibi konuları inceledik. Projenin kaynak kodlarına buradan ulaşabilirsiniz. Umarım faydalı bir yazı olmuştur. Bir sonraki yazılarda görüşmek üzere.
İyi çalışmalar.