İçeriğe geç →

Aspect Oriented Programming (AOP) – Giriş ve Örnek Bir Proje

Merhaba arkadaşlar, bu makalemde Aspect Oriented Programming (AOP) nedir ve birbirleri ile kesişen ilgiler (Cross-Cutting) nelerdir gibi kavramları açıklayarak AOP serisine bir giriş yapmak istiyorum.

Uzun zamandır giriş yapmayı düşündüğüm bir konuydu aslında AOP fakat fırsat bulup bir türlü başlayamamıştım. Umarım keyifli bir makale serisi çıkar. 🙂

aop

AOP Nedir ve Neden İhtiyaç Duyarız?

Aspect Oriented Programming yaklaşımı, temelinde birbirleri ile kesişen ilgilerin (Cross-Cutting Concerns) ayrılması üzerinedir. Peki nedir bu birbirleri ile kesişen ilgiler diyecek olursak:

Geliştirdiğimiz modüllere baktığımızda aslında her business işleminde ortak olarak gerçekleştirdiğimiz bazı işlemler vardır. Örneğin kullanıcının yetkisi var mı?, Gelen isteği logla, Exception Handling işlemini yap gibi ve sonrasında ilgili modülün business kısmını gerçekleştirmekteyiz. İşte bu işlemler bizim birbirleri ile kesişen ilgilerimiz (Cross-Cutting-Concerns) oluyor.

Bu modüle birde kodsal açıdan örnek vermek gerekirse:

Örnek metot üzerinde de gördüğümüz üzere genelde business işlemi sırasında yaptığımız temel kontrol ve işlemler. AOP ise bize birbirleri ile kesişen bu ilgilerin birbirlerinden farklı olsada ayrılmaları gerektiğini söylemektedir.

Aspect Oriented Programming’in odak noktası ise konuların ayrımıdır (Separation of Concerns). Object Oriented Programming’de de olduğu üzere oluşturmuş olduğumuz sınıfları, her sınıfın kendi sorumluluğunu yerine getirmesi (Single Responsibility) gibi prensiplere uyarak geliştirdiğimizde oluşan kodun okunabilirliğini (Readability), tekrar kullanabilirliğini (Reusability) ve genişletilebilirliğini (Extensibility) sağlamaktayız. AOP ise bize bu anlamda kodun okunabilirliğini ve tekrar kullanılabilirliğini en üst seviyede sağlamayı amaçlamaktadır konuların ayrımı (Separation of Concerns) altında.

Object Oriented Programming’de soyutlamaya (Abstraction) giderekte kodun tekrar kullanılabilirliğini, okunabilirliğini ve genişletilebilirliğini ve sınıfların birbirine sıkı sıkıya bağlı (Tightly Coupled) olmamasını sağlayabiliriz ama bir noktaya kadar. O nokta geldiğinde ise AOP kesişen ilgilerimizi ele alarak onları tamamen bağımsız (Independent) hale getirmektedir. Bu sayede her bir kesişen ilgimiz, kendi içerisinde bağımsız olarak geliştirilebilir ve tekrar kullanılabilir hale gelmektedir.

Yukarıdaki örnek üzerine AOP uygulayarak yeni bir örnek vermek gerekirse:

Kesişen ilgilerimizi (Cross-Cutting-Concerns) AOP ile ayırdıktan sonraki hali gerçekten şaka gibi değil mi? 🙂 Okunabilirlik ve tekrar kullanılabilirliği nasılda değiştiğini netçe görebiliyoruz. Açıkçası bu şekilde yapılar geliştirmek, insana kod yazarken de ayrı bir zevk veriyor doğrusu.

Evet, işin terminoloji kısımlarında her şey güzel. Fakat biz bu kesişen ilgilerimizi nasıl ayıracağız sorununa gelirsek, bu konuda yardımımıza Interceptor yapıları çıkmakta. Interceptor’ler bize metot çağrıldığında, öncesinde ve sonrasında araya girerek belirli işlemleri yapabilmemizi sağlayan yapılardır.

Interceptor yapılarını oluşturabilmek için Reflection sınıfından bolca yararlanacağız. 🙂 İşin aslı, bu yapıları kolaylıkla kullanabilmek için bir çok 3rd party uygulama bulunmakta. Örneğin: Castle Windsor, Enterprise Library gibi kütüphaneler. Ben en azından giriş makalemde işin mutfağında neler olup bitiyoru sizlere göstermeden hazır kütüphaneler aracılığı ile bu yapıları kullanmak istemiyorum. Bu yüzden işin eğlenceli kısmı olan kendi interceptor’ümüzü oluşturacağız. Bu mutfağın bir kokusunu alalım ve neler olup bitiyor bir bilelim en azından. 🙂

Şimdi işin en eğlenceli kısmına geçme vakti. Örneğimizde bizim kesişen ilgilerimiz aşağıdaki modüller olsun:

  • Caching
  • Loging

Bu modüllerimiz’i birbirlerinden bağımsız concern’lara ayırarak aspect’ler aracılığı ile metotların çağırımında, nasıl araya girerek işlemlerimizi gerçekleştirebileceğimizi görelim.

AOPGiris isimli bir konsol uygulaması oluşturuyorum. Konsol uygulamasının içerisine Aspect isimli bir klasör oluşturuyorum ve Aspect’lerimizi burada geliştireceğiz. IAspect isimli bir interface ekliyorum ve bu interface’i implemente ederek aspect modlarımızı oluşturacağız.

Şimdi ise IAspect interface’ini implemente ederek, aspect modlarımızı oluşturacağız. Bu modlar ise bize metot çağrımında öncesinde veya sonrasında araya girebilmemizi sağlayacaklar.

IAfterAspect metot işlendikten sonra çalışacak olan aspect modudur ve geriye herhangi bir işlem değeri dönmektedir.

IAfterVoidAspect ise imzasından da anlaşılacağı üzere metot işlendikten sonra çalışıp geriye değer dönmemektedir.

IBeforeAspect ise metot işlenmeden önce çalışacak olan aspect modumuzdur.

IBeforeVoidAspect ise metot işlenmeden önce çalışıp geriye değer dönmeyecek olan aspect modumuzdur. Şimdi sıra geldi kesişen ilgilerimizin aspectlerini oluşturmak için türeceğimiz abstract sınıfımızı oluşturmaya.

AspectBase sınıfını, Attribute soyut sınıfından türetiyoruz ve oluşturmuş olduğumuz IAspect arayüzünüde implemente ediyoruz. Alt yapımız hazır olduğuna göre şimdi modüllerimiz’i oluşturmaya başlayabiliriz.

İlgili aspect’leri oluşturmadan önce modül işlemleri sırasında bize unique key’ler oluşturabilme gibi işlemler için ilgili metot’un ismini, çağırım sırasında gönderilen parametreleri verecek olan yardımcı sınıf amacı ile AspectContext isimli bir sınıf oluşturuyorum.

Lazy sınıfı aracılığı ile pratik olarak Singleton desenini uyguluyorum ve bize modül işlemleri sırasında gerekli olacak property’leri tanımlıyorum. Aspect klasörünün içerisine Attributes isimli yeni bir klasör oluşturuyorum ve ilk modülümüz olan caching işlemleri için kullanacak olduğumuz CacheAttribute sınıfını kodlamaya başlıyorum.

IBeforeAspect ve IAfterVoidAspect kullanarak oluşturmuş olduğum CacheAttribute’ü çağırım öncesi metot execute edilmeden araya girerek cache kontrolü yapar ve varsa cache üzerinden getirerek geriye değeri döner. OnAfter’da ise ilgili işlem execute edildikten sonra veri cache’e eklenir veya güncellenir. Cache işlemimiz bu kadar 🙂 Şimdi sıra Loging işlemine. Attributes klasörü içerisinde şimdide LogAttribute sınıfı oluşturuyorum ve kodlamaya devam ediyoruz.

IBeforeVoidAspect ve IAfterVoidAspect kullanarak oluşturmuş olduğum LogAttribute sınıfıda öncesinde loglama işlemini gerçekleştiriyoruz ve geriye herhangi bir değer dönmemize gerek olmadığı için IBeforeVoidAspect türünü implemente ettik. OnAfter’da ise ilgili veriyi işlem sonrası loglama yapabiliriz.

Aspectler bu kadar. Şimdi sıra geldi asıl sıkıntılı kısıma 🙂 Araya girmemizi sağlayacak olan proxy sınıfını tasarlayacağız. Transparent Proxy sınıfı bizim gerçek sınıfımızı temsil ediyor olacak ve ilgili metot’u invoke etmeden önce veya sonrasında istediğimiz işlemleri yapabiliyor olacağız. Bu işlemi yapan hazır kütüphaneler olduğundan bahsetmiştik, EL içindeki Unity veya Castle Windsor gibi.

Bu kütüphanelerde gerçek nesnelerimizin çalışma zamanında birer Transparent Proxy’lerini oluşturarak araya girme işlemlerini gerçekleştirmektedirler. Bu işlemide kendimiz handle edeceğimiz için başlayalım generic bir TransparentProxy sınıfını kodlamaya.

TransparentProxy sınıfımız’da hazır durumda. İlgili bilgileri metot yorum satırlarında bulabilirsiniz. Artık ilgili sınıflarımız için RealProxy aracılığı ile temsilcilik yapacak olan Generic Transparent Proxy sınıfımız hazır ve kod içerisinde de görebileceğiniz gibi aspect’lerimizide implemente etmiş durumdayız.

Şimdi kodlamış olduğumuz aspect’leri test edebilmemiz için bir ProductService isminde sınıf oluşturuyorum ve içerisine GetProduct metodunu oluşturarak aspect’lerimizi işaretliyorum.

ProductService sınıfı için yeni bir örneği alındığında in memory olarak bir dictionary üzerinde Product nesnelerini tutuyorum. GetProduct metodunda ise gördüğümüz gibi sadece dictionary üzerinden nesneyi geri return ediyorum. Caching ve Loging işlemlerini bizim için aspect’lerimiz halledecek. Ne kadarda hoş duruyor değil mi? 🙂

Şimdi konsol uygulamamız üzerinde TransparentProxy sınıfımız aracılığı ile çağırarak bir bakalım aspect’lerimiz nasıl çalışacaklar?

ve sonuç…

aspect_result

Gördüğümüz gibi GetProduct metot’u için eklemiş olduğumuz Cache ve Log aspect’leri çalışmış bulunmaktadır. Özünde yapmış olduğumuz işlemde GetProduct için business kodlarının karmaşıklaşmasını kesişen ilgilerimizi ayırarak sağlamış bulunmaktayız. Kod satırları artık okunabilirliği yüksek bir seviyede ve kesişen ilgilerimiz tekrar kullanılabilir haldedir.

Aktaracaklarım şimdilik bu kadar. Bu başlık altında metotlar invoke olurken mutfakta neler olup bitiyor, Interceptor’ler bu işlemi nasıl işliyor gibi sorularıda görmüş olduk aslında. AOP serisinin ilerleyen makalelerinde ise 3rd party kütüphaneler aracılığı bu işlemleri nasıl gerçekleştirebiliriz konularınada değineceğim. Çünkü her zaman kendimiz custom olarak yazabilme ve işleyebilme olanaklarına sahip olamayabiliriz. Bunun en büyük sebeplerinden birisi ise: standardizasyon. Eğer büyük bir ekipte çalışıyorsanız bazı konuların önemli olduğu gibi en büyük unsurlardan biriside standardizasyondur.

Bunun için gelecek makalelerimde bu kütüphaneler nasıl kullanılıyor konularınada değiniyor olacağım. Örnek projeyi ekte bulabilirsiniz.

AOPGiris

 

Bu makale toplam (10992) kez okunmuştur.

44
0



Kategori: Aspect Oriented Programming (AOP)

13 Yorum

  1. Merhaba Gökhan,

    Bu güzel makale için teşekkürler.

    “public class ProductService” sınıfı IProductService sınıfından implement edilmediği için yeni başlayan arkadaşlar sorunu tespit edemeyebilir.

    Ekte verdiğin kaynak kodlarda sorun yok. Orada implement..

    Başarılar.

  2. Tayfun Tayfun

    Makale için teşekkürler .AOP konusunda çok detaylı makaleler bulamazken ilaç gibi geldi. 3rd party uygulamalara postsharp kullanımı konusunda uygulamalı anlatım olarak makalenin devamı gelir mi acaba ? 🙂

    • Merhaba teşekkür ederim öncelikle. Evet bir sonraki aop serisinde 3rd party uygulamalar ile nasıl intercept yapılabileceğini hakkında yazılar yazmayı düşünüyorum ama bu aralar biraz zaman sıkıntısı çekiyorum. 🙂

      • Tayfun Tayfun

        o zaman bol bol vakit diliyorum size. beklemedeyiz 🙂

  3. Mehmet Baran Mehmet Baran

    Aop hakkında okuduğum en anlaşılır yazıydı. Teşekkürler

  4. Yunus Emre Yunus Emre

    Benim kullanacağım sınıf bir abstract sınıftan türediği için sizin verdiğiniz örnekteki Proxy sınıfında bir takım değişiklikler yapmam gerekti fakat çok hakim olmadığım için doğru mu yaptım ya da daha pratik bir değişiklik ile çözülür müydü emin değilim.
    Değişiklikten sonra kod çalıştı ama production’a çıktığımızda gelecek çoklu isteklerde de doğru çalışır mı emin olamıyorum gerçi static bir metot kalmadı ama yine de göz atabilirseniz çok sevinirim.

    public class TransparentProxy : RealProxy where T : BaseBusiness, TI // BaseBusiness abstract classını ekledim ve new constraintini kaldırdım.
    {
    private T _creator;
    private MethodRequest _methodRequest; //Abstract classın zorunlu tuttuğu property bu. Bunu constructorda almak gerekli.

    public TransparentProxy(MethodRequest methodRequest) : base(typeof(TI))
    {
    _methodRequest = methodRequest;
    _creator = (T)Activator.CreateInstance(typeof(T), methodRequest);
    }

    public TI GenerateProxy() //Bu alan static tanımlanmıştı fakat constructorda alan aldığım için bunu değiştirdim.
    {
    var instance = new TransparentProxy(_methodRequest);
    return (TI)instance.GetTransparentProxy();
    }

    ……
    //Bundan sonra geri kalan tüm kod aynı sadece sizin new ile çağırdığınız aşağıdaki satırda ben new yerine _creator kullandım

    result = methodCallMessage.MethodBase.Invoke(_creator, methodCallMessage.InArgs);
    }

    Proxy’i çağırdığım kod;

    public UserController()
    {
    _userBusiness = new TransparentProxy(methodRequest).GenerateProxy();
    }

    • Merhaba şuan bir problem yaratacak durum görünmüyor fakat, performans testlerine bir bakmak gerek static constructor kullanılmadığı için herhangi bir etkisi olacak mı. Şuan herhangi bir test etmeden yorum yapamayacağım. Eğer performans concern’ünüz var ise de, runtime ao yerine compiletime ao tercih etmenizi önerebilirim. Bu noktada ise lightweight olarak Fody veya PostSharp’ı tavsiye edebilirim.

  5. Yasin Yasin

    Merhaba makale çok güzel. Bu kodu transactionscope için nasıl kullanabiliriz. Postsharp dışarıdan parametre olarak transactionscope alarak işlemi devam ettiriyor. kodunuzda nasıl bir değişiklik yapmamız gerekiyor. Teşekkürler

    • Merhaba, tam yapmak istediğiniz nedir acaba? TransactionScope yerine, bir attribute kullanarak mı decorate etmek istiyorsunuz? Buradan anladığım bu sanırım. Eğer öyle ise, TransparentProxy içerisindeki Invoke method’una implemente edebilirsiniz veya constructor’ından inject edebilirsiniz. Teşekkürler.

  6. Emre Akarsu Emre Akarsu

    Merhaba,

    Güzel anlatım için teşekkürler. Bir şey sormak istiyorum. Benim product service constructor umda parametre olması durumunda nasıl bir yöntem izleyebilirim.

    Teşekkürler

    • Merhaba, herhangi bir DI container ile, ITransparentProxy’leri de inject ettirebilirsiniz veya her ne kadar bir antipattern olsada service locator pattern’i ile.

Bir cevap yazın

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

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.