Caner Tosuner

Leave your code better than you found it

PostSharp Kullanarak Request Response Loglama

Çok küçük projeleri saymazsak Log tutmak bir projede olmazsa olmazlardandır. Hele ki o proeje bir enterprise proje ise müşteri size burdan kuş bile geçse onun log'unu görmek istiyorum gibi bir cümle dahi kurabilir.Bu tür bir projede müşterinin loglamanızı isteyeceği şeylerin başında yapılan request ve response'lar gelir. Metota gelen request ve response'u loglamak için Aspect Oriented yazımızda da bahsettiğim üzre AOP paradigmasından yararlanarak request response log'larını tutabiliriz. Bunun için Postsharp kullanıcaz.

Postsharp ile Excetion Handling yazısında da bahsettiğimiz gibi OnMethodBoundaryAspect class ile sahip olduğumuz bazı metotlar vardı. Exception Handling için OnException metodunu kullanmıştık. Request Response log için OnEntry ve OnExit diye 2 metot mevcut. İsimlerinden de anlaşıldığı üzre bu metotlar ilgili metota giriş ve metottan çıkış anında bir takım işlemler yapmamızı sağlar.

*PostSharp kurulumu vs bilgileri için şu yazıyı inceleyebilirsiniz.

Case'imiz şöyle olsun TracingAspect adında OnMethodBoundaryAspect den inherit olan bir class tanımlayalım ve OnEntry & OnExit metotlarını override edelim ve bu metotlar içerisinde gelen request response'u JSON formatında loglayalım.

    [Serializable]
    public class TracingAspect : OnMethodBoundaryAspect
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        public override void OnEntry(MethodExecutionArgs args)
        {
            var jsonRequest = serializer.Serialize(args.Arguments);
            LogMessage(jsonRequest);
        }

        public override void OnExit(MethodExecutionArgs args)
        {
            var jsonResponse = serializer.Serialize(args.ReturnValue);
            LogMessage(jsonResponse);
        }

        private void LogMessage(string message)
        {
            Console.Write(message);
        }
    }

 

Şimdi ise EFT işlemi yapan bir metot yazalım ve bu metot parametre olarak TransferRequest objesi alsın ve response olarak TransferResponse adında bir model dönsün.

 class Program
    {
        static void Main(string[] args)
        {
            var request = new TransferRequest
            {
                Amount = 540,
                ReceiverIBAN = "TR33 0006 1005 1978 6457 8413 26",
                SenderIBAN = "TR33 0006 1005 1978 6457 8413 26"
            };

            var resp = MoneyTransfer(request);
        }

        [TracingAspect] //Aspect'i ekliyoruz
        public static TransferResponse MoneyTransfer(TransferRequest request)
        {
            var resp = new TransferResponse
            {
                IsSuccess = true,
                Message = "Transfer İşleminiz Gerçekleşti."
            };
            return resp;
        }

        public class TransferRequest
        {
            public decimal Amount { get; set; }
            public string SenderIBAN { get; set; }
            public string ReceiverIBAN { get; set; }
        }

        public class TransferResponse
        {
            public bool IsSuccess { get; set; }
            public string Message { get; set; }
        }
    }

Main fonksiyon içerisinde MoneyTransfer metoduna istekte bulunduğumuzda Metota girerken OnEntry'ye, metotdan çıkarken de OnExit'e düşecektir ve LogMessage fonsiyonuna aşağıdaki gibi message parametlerini gönderecektir.

OnEntry => 

  • [{"Amount":540,"SenderIBAN":"TR33 0006 1005 1978 6457 8413 26","ReceiverIBAN":"TR33 0006 1005 1978 6457 8413 26"}]

OnExit => 

  • {"IsSuccess":true,"Message":"Transfer İşleminiz Gerçekleşti."}
     

Görüldüğü üzre Postsharp sayesinde request ve response log tutma işlemini çok kolay bir şekilde halledebiliyoruz ve aynı zamanda AOP bizlere reusable aspect'ler yazın diyor bizde TracingAspect'i uyulamada istediğimiz her hangi bir yerde kullanıp log tutma işlemini tek bir yerden yönetebiliriz.

PostSharp Kullanarak Exception Handling ve Log İşlemi

Daha önce Aspect-Oriented Programming yazımızda AOP'den bahsetmiştik ve AOP'nin .Net tarafında uyarlanabilmesini sağlayan tool'lar ve bu tool'ların en yaygın olanı Postsharp olduğunu söylemiştik. Bu yazıda Postsharp kullanarak uygulamamızda meydana gelecek olan exception'ları yakalamayı sağlayan bir geliştirme yapacağız.

Başlarken

Öncelikle VS'yu açıp yeni bir proje oluşturuyoruz.

  • File > New > Project > Console Application

İsim olarak ben "PostSharpExcHand" dedim siz istediğiniz bir ismi verebilrisiniz, sonrasında Ok'e tıklayıp projeyi oluşturuyoruz.

Daha sonra Postsharp'ı projemize ekleme işlemi var. Bunun için;

  • Tools > NuGet Package Manager > Manage NuGet Packages for Solution

Search kısmına "PostSharp" yazıp çıkan sonuçlar arasından postsharp'ı bulup indiriyoruz sornasında solution'da bulunan hangi projeye eklemek istiyorsak onu seçip yüklemi işlemini bitiriyoruz.

Aspect Tanımlama

AOP yazımızda aspect'lerden bahsetmiştik. Projemizde kullanacağımız reusable modülleri aspectler halinde tanımlayıp kullanmak istediğimiz yerlerde aynı metot üstünde attribute tanımlarmış gibi eklediğimizden bahsetmiştik. Bu yazıda Exception anında çalışacak olan aspect'i tanımlicaz. Bunun için projeye ExceptionHandlerAspect adında bir class ekleyelim. Postsharp ile birlikte gelen OnMethodBoundaryAspect adında bir class var ve bu class'tan inherit olan class'lar aşağıda da görüldüğü gibi override edebileceği bazı metodlara sahip oluyorlar.

Bu metotları kullanarak log,exception handling gibi bir çok ihtiyaca çözüm üretebilirsiniz. Biz bu yazıda exceptionHandling işlemi yapacağımız için oluşturduğumuz ExceptionHandlerAspect class'ını OnExceptionAspect class'ından türetiyoruz ve bu metotla birlikte gelen OnException metodunu override ediyoruz.

   [Serializable]
    public class ExceptionHandlerAspect  : OnExceptionAspect
    {
        public override void OnException(MethodExecutionArgs args)
        {
            string msg =  string.Format("{0}: Error running {1}. {2}{3}",DateTime.Now, args.Method.Name, args.Exception.Message, args.Exception.StackTrace);
            Debug.WriteLine(msg);
            args.FlowBehavior = FlowBehavior.Continue;
        }

Yukarıda görüldüğü gibi uygulamada exception meydana geldiğinde OnException metoduna düşecektir ve burda ihtiyaca göre log atma vs. gibi işlemleri yapabliriz.

Tanımladığımız Aspect'i Kullanma

Şimdi ise sırada tanımladığımız Aspect'i projede kullanma var. Bunun için uygulamada Exception olabilieceğini düşündüğümüz metotların başına attribute olarak ExceptionHandlerAspect'i ekleyebilriz.

    class Program
    {
        static void Main(string[] args)
        {
            ThrowSampleException();
        }
 
        [ExceptionHandlerAspect]//Attribute olarak Aspect'i ekledik
        private static void ThrowSampleException()
        {
            throw new NotImplementedException ();
        }
    }

Görüldüğü üzre yukarıda bulunan ThrowSampleException()  içerisinde exception alındığında üzerinde tanımlı bulunan ExceptionHandlerAspect'inden dolayı bu class içerisine gidip OnException metoduna düşecektir ve ilgili exception açıklamasını alıp artık canınız ne isterse onu yapabilirsiniz :) Log atabilirsiniz, çalışmasını istediğiniz ayrı bir process olabilir onu çalıştırabilirsiniz vs. vs.

ThrowSampleException IL Kodları

ExceptionHandlerAspect ThrowSampleException metoduna ne kazandırdı diye IL kodlarına bakacak olursak aslında try/catch kullandı ve metot içerisinde bulunan kodları try bloğunun içerisine alarak meydana gelecek olan exception'lar için catch bloğunda bunları handle eden kodları yazdı.

private static void ThrowSampleException()
{
    try
    {
        throw new NotImplementedException();
    }
    catch (NotImplementedException exception)
    {
        MethodExecutionArgs methodExecutionArgs = new MethodExecutionArgs(null, null);
        MethodExecutionArgs arg_1F_0 = methodExecutionArgs;
        MethodBase m = Program.<>z__Aspects.m2;
        arg_1F_0.Method = m;
        methodExecutionArgs.Exception = exception;
        Program.<>z__Aspects.a0.OnException(methodExecutionArgs);
        switch (methodExecutionArgs.FlowBehavior)
        {
        case FlowBehavior.Default:
        case FlowBehavior.RethrowException:
            IL_55:
            throw;
        case FlowBehavior.Continue:
            methodExecutionArgs.Exception = null;
            return;
        case FlowBehavior.Return:
            methodExecutionArgs.Exception = null;
            return;
        case FlowBehavior.ThrowException:
            throw methodExecutionArgs.Exception;
        }
        goto IL_55;
    }
}

Reusability

Evet arkadaşlar aslında AOP kullanarak exception handling yapmamızda ki en büyük amaç bu Reusability. Tanımlamış olduğumuz aspect'i artık projenin her yerinde kullanabiliriz ve bu durum bizi çookk ama çok büyük bir iş yükünden kurtarmış olup spaghetti code'lar yazmamıza engel olmuştur.

Son olarak; AOP candır.. :)

Aspect Oriented Programming Nedir

Aspect Oriented Programming (AOP) tükçesi "Cephe Yönelimli Programlama" bir programlama paradigmasıdır. İsim olarak bazılarımıza yabancı geliyor olabilir çünkü çok yeni bir kavram değil ve gelişen yazılım teknolojileri ve AOP nin daha kolay ve verimli implement edilmesini sağlayacak "PostSharp" gibi tool'ların çıkmasıyla birlikte epey bir önemli hale gelir oldu AOP.

Biz yazılımcılar daha iyi kodlar yazmak için hep kullandığımız bir cümle var "Separation of Concern". AOP'nin çıkış noktası aslında buna dayanıyor diyebiliriz. AOP birbiriyle kesişen ilgilerin (Cross-Cutting Concerns) ayrılması üzerinedir. Uygulama genelinde kullanılacak olan yapıları (logging,exception hand., cache, etc.) core tarafta yazdığımız koddan ayırarak bir çeşit ayrı küçük programcıklar şeklinde yazıp projede kullanmayı hedefler diyebiliriz.

Örnek olarak 70.000 kişinin çalıştığı çok büyük bir holding için uygulama geliştiriyoruz geriye bütün çalışan listesini dönen bir metod yazıyor olalım ve klasik her uygulamada olması gereken belli başlı şeyler vardır; Cache,ExceptionHandling, Logging gibi bizde metodumuzda bunları yapıyor olalım;

 public IEnumerable<Employee> GetEmployeeList()
        {
			//Request'i yapan kişinin yetkisi varmı yokmu kontrol et
			//metoda girerken request'i log'la
			
            try
            {
                var resultList = DbQuery("Select * from Employee"); // database de ki tabloya sorgu attığımız varsayalım ve 70 bin kayıt gelsin
				
                //geriye dönen sonuçları cache'e at bir sonrakine cache'den ver

                return resultList;
            }
            catch (Exception ex)
            {
                // meydana gelen Exception'ı handle edip log'la ve client'a gidecek olan response'u modify et  
                throw;
            }
			
			//metoddan çıkarken response'u log'la
        }
}

Yukarıda bulunan metodu incelediğimizde ne kadar eksik olduğunu görebiliyoruz. Yorum satırlarında yazan işlemler için geliştirmeler yapmamız gerekmekte ancak bu geliştirmeyi nasıl yapacağız  ? CheckUserAuth(), LogRequest(), LogException(), LogResponse(), ModifyResponse() gibi metodlar yazıp bu metodları ilgili yerlerde her metodda yazmak herhalde ilk akla gelen çözüm ancak AOP bize daha farklı şekilde yapmamız gerektiğini söylüyor. Bunları ayrı modüller olarak tasarlayıp daha kullanılabilir, okunabilir ve SOLID prensiplerine uygun geliştirmeler yapmamız gerektiğini söyler.

Peki birbirleri ile çakışan ilgileri birbirlerinden nasıl ayıracağız ? İşte bu noktada karşımıza interceptor çıkmakta.

Interceptor

Interceptor’ belirli noktalarda metot çağrımları sırasında araya girerek çakışan ilgilerimizi işletmemizi ve yönetmemizi sağlamakta. Buda metotların çalışmasından önce veya sonra bir takım işlemleri gerçekleştirebilmemeizi sağlar ve AOP nin yapısı tamamiyle bunun üzerine kurulu desek yanlış olmaz heralde. Interceptor'u implemente etme olayına girmicem çünkü yukarıda da bahsettiğim gibi .Net tarafında Nuget üzerinden indirip kullanabileceğimiz Postsharp kütüphanesi bu işi diplerine kadar yapmakta ve bizlere sadece attribute tanımlamaları yapmayı bırakmakta. 

Şimdi yukarıda yazmış olduğumuz kodu gelin birde AOP standartlarına uygun şekilde yazalım.

        [UserAuthAspect]
        [LoggingAspect]
        [AppCacheAspect(25000)]
        [ExceptionAspect]
        public IEnumerable<Employee> GetEmployeeList()
        {
            var resultList = DbQuery("Select * from Employee");
            return resultList;
        }

[UserAuthAspect] [LoggingAspect] [AppCacheAspect] [ExceptionAspect] attribute'lerini tanımladık ve AOP nin dediği gibi Cross-Cutting yani kesişen yerleri Aspect'ler kullanarak attribute seviyesinde kullanılabilir hale getirdik.Yazmış olduğumuz 2. metot ile 1. metot arasındaki satır sayısı farkına baktığımızda dağlar kadar fark var ve en önemlisi daha okunabilir bir kod yazmış olduk.  

Aspect-Oriented Programming'in Sağladıkları

  1. İçi içe yazılmış ve sürekli tekrar eden kodlardan kurtulabiliyoruz,
  2. Daha temiz ve anlaşılır kodlar yazabiliyoruz,
  3. Yazmış olduğumuz kodları daha abstract hale getirerek modülerliğini arttırıyoruz,
  4. Bakım ve geliştirme maliyetlerini azaltıyoruz,
  5. Uygulamamızı daha yönetilebilir ve daha esnek hale getirebiliyoruz.

Görüldüğü üzre AOP yaklaşımı geliştirdiğimiz uygulamalar için bizlere bir çok faydalar sunmakta ve Postsharp gibi çeşitli tool'lar ile birlikte projenize AOP'ye uygun hale getirmek dahada kolay hale gelmiş durumda. Bundan sonraki AOP ile ilgili yazılarda Postsharp kullanarak Cache, Logging, ExceptionHandling gibi örnekler ile deva ediyor olacağız.