Caner Tosuner

Leave your code better than you found it

Web Api MemoryCache Kullanımı

Daha önce Web Api ile ilgili yazdığımız yazılarda çeşitli konulara değinmiştik ve bunlardan bir tanesi de yazısıydı. Bu yazımızda yine web api için cache'den bahsediyor olacağız anack bu sefer output cache değilde uygumala içerisinde kendimiz manuel olarak memory'e atıp daha sonra kullanmak istediğimiz de alıp modify edebilmemizi sağlayan veya direkt olarak output cache de olduğu gibi alıp client'a dönmemizi sağlayan yapı MemoryCache den bahsediyor olacağız. MemoryCache .Net 4.0 ile birlikte System.Runtime.Caching.dll içerisinde sunulmuş bir yapı olarak karşımıza çıkıyor. Projemize bu dll'i referans olarak eklemek için solution'da bulunan references'a sağ tıklayıp add reference deyip Assemblies => Framework kategorisine tıkalyıp gelen listeden System.Runtime.Caching.dll'ini seçip ekliyoruz.

Referansımızı ekeldikten sonra projemize MemoryCacheManager adında adında bir class ekleyelim. Case şöyle olsun, ProductController.cs adında bir controller ve içerisinde GetAllProducts ve GetProductById adında iki metot tanımlayalım. İlk metotda geriye döndüğümüz product listesini ICache den implement olan MemoryCacheManager class'ını kullanarak MemoryCache'e atalım ve GetProductById metoduna Id parametresi ile request'te bulunulduğunda cache'den okuyup geriye product objesini dönelim.

ICache.cs

    public interface ICache
    {
        bool Contains(string key);//key varmı yokmu diye control ettiğimiz metot
        void Add<T>(string key, T source);//cache key'i ile birlikte cache model'i alıp cache'e ekleyen metot
        T Get<T>(string key);//key parametresi alarak cache'de ki data yı return eden metot
        void Remove(string key);//key parametresine göre mevcut cache'i silen metot
    }

 

MemoryCacheManager.cs

    public class MemoryCacheManager : ICache
    {
        ObjectCache cache;

        public MemoryCacheManager()
        {
            cache = MemoryCache.Default;
        }

        public void Add<T>(string key, T source)
        {
            //60 dakika boyunca cache'de tutacak
            var policy = new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(60) };
            cache.Add(key, source, policy);
        }

        public bool Contains(string key)
        {
            return cache.Contains(key);
        }

        public T Get<T>(string key)
        {
            return (T)cache.Get(key);
        }

        public void Remove(string key)
        {
            cache.Remove(key);
        }
    }

 

Product.cs

public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
    }

 

ProductController.cs

    public class ProductController : ApiController
    {
        [HttpGet]
        public Product[] GetAllProducts()
        {
            Product[] products = new Product[]
            {
                new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 },
                new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M },
                new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M }
            };

            //ürünleri cache'e atıyoruz
            var cm = new MemoryCacheManager();

            string cacheKey = "products";
            if (cm.Contains(cacheKey))//varmı diye kontrol ediyoruz, eğer varsa mevcutu sil yeni listeyi ekle
                cm.Remove(cacheKey);
            cm.Add(cacheKey, products);

            return products;
        }

        [HttpGet]
        public Product GetProductById(int Id)
        {
            var cm = new MemoryCacheManager();
            string cacheKey = "products";
            if (cm.Contains(cacheKey))//varmı diye kontrol ediyoruz
                return cm.Get<Product[]>(cacheKey).FirstOrDefault(p => p.Id == Id);
            return null;
        }
    }

Projemizi run edip browser aracılığıyla önce GetAllProducts metoduna request'te bulunuyoruz ve product listesini return etmeden önce MemoryCacheManager'ın Add metodunu kullanarak geriye product listesini return etmeden önce listeyi alıp "products" key'i ile cache'e atıyor ve sonrasında return ediyor. Product listesini aldıktan sonra Id=2 olan ürün için GetProductById metoduna request'te bulunuyoruz ve ilk olarak MemoryCacheManager içerisine gidip "products" key'i ile mcache atılmış olan product listesini bulup alıyor ve sonrasında Id=2 olan product'ı bulup geriye döndürüyor.

Manuel olarak MemoryCache entegrasyonu bu şekilde yapabilirsiniz veya ihtiyaca göre farklı kullanımlarda uygulayabilirsiniz. Cache önemlidir arkadaşlar, doğru yerde kullanıldığında çok can kurtarır :)

Web Api Projelerinde Versiyonlama

Yazmış olduğunuz Api projelerinde versiyonlama yapmak oldukça önemlidir. Düzenli bir versiyonlama yaparak hem mevcut hemde yeni versiyonu kullanacak olan client'lar için tutarlı response'lar sağlayabilirsiniz. Örneğin v1.0 için çalışan listesi dönen bir api metodu yazdınız ve mevcutta kullanan kullanıcılar var. production'da. Sonra v2.0 de dediler ki çalışan isimlerinin yanında departman kodu da yazsın (Caner Tosuner, IT gibi). Her ne kadar kullanıcılar v2.0 geliştirmesini yapacak olsalarda belli bir süre daha eski versiyon için destek veriyor olmak gerekir çünkü bütün herkes aynı anda v2.0'a geçemeyebilir veya v1.0 da kalmak isteyebilir. İşte bu gibi durumları en iyi şekilde yönetilmemiz için versiyonlamayı son derece iyi yapmamız gerekir.

Web Api için versiyonlama yapmanın ortak kabul görmüş bir kaç yol bulunmakta;

  • URL de versiyon bilgisini query string olarak geçebiliriz,
  • Request Header'a "appVersion" gibi bir key/value ekleyebiliriz,
  • Genelde media type için kullanılsada AcceptHeader'a versiyon bilgisini ekleyebiliriz,
  • Her request'te BaseRequest'te içerisinde bir versiyon bilgisini alma.

Yukarıda sıralanan 4 yol da versiyonlama için çözüm ancak WebApi'ın son sürümüyle birlikte tercih edilmemeye başlandı hatta ve hatta yanlış çözüm olarak söyleyenler bile mevcut.

WebApi için versiyonlamayı Api metodlarının başına attribute olarak ekleyeceğimiz Route class'ı bulunmakta. Bu class'ı kullanarak kısaca hangi url bilgisi ile o metod çağrılabilir onu belirtmiş oluyoruz.

Burda dikkat etmemiz gereken konuların başında elimizden geldiğince mevcut URL'i bozmamaız gerekiyor.

Şimdi gelelim örneğimize.

Yukarıda bahsettiğimiz örnek üzerinden ilerleyelim ve EmployeeController adında bir ApiController'ımız var ve içerisinde GetEmployeeList() adında bir metod olsun. Bu metod öncelikle v1.0 için geriye çalışan adı ve soyadı bilgilerini içeren bir string array dönsün.

        [HttpGet]
        [Route("api/v1/Employee/{GetEmployeeList}")]
        public IHttpActionResult GetEmployeeList()
        {
            var list = new string[] { "İlhan Mansız", "Tümer Metin" };

            return Ok(list);
        }

Yukarıda ki gibi v1.0 için kullanıcılar bu metodu kullanmakta. GetEmployeeList() metoduna request'te bulunmak için url "http://localhost/api/v1/Employee/GetEmployeeList".

Ancak v2.0 da ad soyad bilgisinin yanında bir de departman adı istenmekte İlhan Mansız, Pazarlama gibi.

Şimdide v2.0 metodunu yazalım.

        [HttpGet]
        [Route("api/v2/Employee/{GetEmployeeList}")]
        public IHttpActionResult GetEmployeeListV2()
        {
            var list = new string[] { "İlhan Mansız, Pazarlama", "Tümer Metin, Satış" };

            return Ok(list);
        }

v2.0 metodunuda yazdık. Bu metoda request'te bulunmak için url "http://localhost/api/v2/Employee/GetEmployeeList".

Yukarıda da bahsettiğmiz gibi en dikkat etmemiz gerekn konuların başında client'ın yapacağı request'i ve api url'ini elimizden geldiğinde aynı tutmak ve bütün versiyonlarda url için değişen tek şeyin versiyon numarası olduğunu belirtmek. Böylelikle service tarafı için ayrımı url de bulunan versiyon numarasından alarak yapabiliriz.

Route attribute'ü kullanmadan versiyonlama işlemini farklı controller'lar kullanarak ta yapabilirsiniz. Üstteki örneğimizde olduğu gibi GetEmployeeList() meetodlarını içeren 2 farklı EmployeeV1Controller ve EmployeeV2Controller adında controller'lar tanımlayabiliriz. Url route işini de WebApiConfig.cs class'ı içerisine yine yukarıdaki url'leri kullanarak gerekli controller'lar da bulunan metodlara yönlendirmeleri yapabilirsiniz. Örneğin şu şekilde;

config.Routes.MapHttpRoute(
            name: "ApiV1_Route",
            routeTemplate: "api/v1/{controller}/{id}",
            defaults: new { controller = "EmployeeV1Controller", action = "GetEmployeeList", id = RouteParameter.Optional }
            );

config.Routes.MapHttpRoute(
            name: "Apiv2_Route",
            routeTemplate: "api/v2/{controller}/{id}",
            defaults: new { controller = "EmployeeV2Controller", action = "GetEmployeeList", id = RouteParameter.Optional }
            );

Yine "http://localhost/api/v1/Employee/GetEmployeeList" metoduna istekte bulunduğumuzda yukarıda yaptığımız config'den dolayı EmployeeV1Controller'ında bulunan GetEmployeeList metoduna yönlendirecektir. v2 için ise EmployeeV2Controller'ında bulunan GetEmployeeList metoduna yönlendirecektir.

 

Versiyonlama büyük çapta olan projeler için oldukça önemlidir. Production'a uygulamanızı çıktıktan sonraki zaman içerisinde tekrardan update'ler çıkmaya devam edeceksinizdir ve versiyonlama yaparak bu işlemleri yürütüyor olmak hem kolaylıklar sağlar hem de projenizi daha yönetilebilir hale getirir.

C# Custom Exception Oluşturma

Exception oluşturmak.. kulağa saçma geliyor dimi :) Heralde yazmış olduğumuz uygulamada bir exception meydana gelmesi en istemediğimiz şey dir. Ama bazen bilinçli olarak uygulamamızda exception fırlatmak isteyebiliriz (tabi handle edilmiş exception).

Exception uygulama çalışırken runtime da meydana gelen hatadır. Exception handling ise runtime da meydana gelen bu exception'ları yazılım tarafında ele alma tekniğidir diyebiliriz. Uygulamanızda bir exception meydana geldiğinde .Net kütüphanesinin direkt olarak fırlattığı stack trace açıklaması veya tuhaf exception mesajını kulanıcıya/client'a göndermek istemeyiz. Bu gibi durumlar için Custom Exception kullanımı devreye giriyor. Custom Exception kullanarak daha yönetilebilir ve daha anlamlı hata mesajları tanımlayabilir client'a dönebiliriz veya Log işlemlerinde handle ederken exception türüne göre daha farklı loglama işlemleri yapmak isteyebiliriz.

.Net tarafında bütün exception'ların base'i System.Exception class'ı dır ve custom tanımlanan exception class'larıda direkt veya dolaylı olarak bu class'tan inherit olmaktadır. Bizde yazacağımız custom exception class'ını System.Exception class'ından inherit edicez.

 

Custom Exception Tanımlama

Login işlemlerinde hata exception fırlatmak için kullanılacak LoginException adında bir custom exception tanımlayalım.

public class LoginException : System.Exception
    {
       //todo
    }

Şimdi uygulama içerisinde kullanımı için gerekli constructor tanımlamalarını yapalım.

   public class LoginException : System.Exception
    {
        public LoginException()
            : base()
        { }

        public LoginException(String message)
            : base(message)

        { }

        public LoginException(String message, Exception innerException)
            : base(message, innerException)
        { }

        protected LoginException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        { }
    }

Yukarıda görüldüğü üzre LoginException'nın constructor'larını tanımaldık ve System.Exception class'ı için yapılacak constructor yönlendirmelerini yaptık, böylece LoginException class'ını kullanıma hazır hale getirdik.

 

Custom Exception Kullanımı

Yazmış olduğumuz LoginException'ı kullanırken aşağıdaki gibi try/catch içerisinde handle edip kullanabilir veya Postsharp Exception Handling, Logging yazısında da olduğu gibi üst veya ayrı katmanlarda exception'ı handle edebiliriz.

 void Login(string userName, string password)
        {
            try
            {
                if (userName != "canert" && password != "qwerty123")
                    throw new LoginException("Invalid login operation");
            }
            catch (LoginException loginException)
            {
                Console.WriteLine(loginException.Message);
            }
        }

İhtiyaç dahilinde çok daha farklı case'lerde kullanılmak üzre Custom Exception'lar tanımlayabilirsiniz. Exception fırlatmak düz mantıkla düşünüldüğünde tuhaf gelebilir ancak çoğu projelerde hayat kurtarır ve yazmış olduğunuz kodları tek bir yerden yönetebilme veya reusability gibi konuları projelerinizde uygulayabilmek için tercih edebilirsiniz ancak bu exception'ları handle etmeyi unutmamalıyız.

Repository Design Pattern

Çoğu yazılım uygulamasının belli mimari kurallar çerçevisinde farklı katmanlara ayrılarak geliştirilmesi beklenir ve buna n-tier architecture denir. N-tier architecture presentation tier, middle tier yada data tier gibi çeşitli katmanlardan oluşabilir. Presentation katmanında uygulamanızın UI tarafını ilgilendiren arayüzler bulunur, Middle katmanda business logic'i ilgilendiren geliştirmeler yapılır ve aynı zamanda database ile etkileşimde bu katmanda bulunur. Data katmanı ise uygulamanızdaki SQL Server, Oracle gibi database context'lerini içeren katmandır. Bu katman belkide uygulamadaki en önemli katmandır sebebi ise database. Data bizim için en önemli şey ve database'ler ile olan veri alış verişi işin en güvenlikli ve generic olması gereken yerlerin başında gelir.

Database varsa CRUD işlemi (Create, Read, Update, Delete) heralde olmazsa olmazdır ve data katmanı ile doğrudan business logic'in bulunduğu middle katman haberleşsin istenilmez ve bu durum bazen uygulamanızın büyüklüğüne göre çeşitli sorunlarda çıkartabilir. Repository pattern bu ihtiyacı karşılayan pattern olarak karşımıza çıkıyor ve temelde yaptığı iş ise business logic ile data access arasında arabulucu görevi görme.

Repository Pattern Faydaları

  • Maintainability (sonradan bakım kolaylılığı) arttırır,
  • Unit-test yapabilmemizi kolaylaştırır,
  • Esnek bir mimari uygulamamızı sağlar,
  • Yeni gelecek modüller veya istenilen değişiklikleri kolayca entegre edebilmeyi sağlar,
  • Domain driven development'ın önünü açar.

 

Repository Pattern Uygulaması

Şimdi örnek bir proje üzerinden Repository Pattern nasıl uygulanır görelim. Projeyi geliştirirken projenizde EntityFramework kurulu ve SampleDbContext adında bir database context'inizin olduğunu varsayarak ilerliyor olacağız.

1.Adım - IRepository adında CRUD işlemleri yapabilmemizi sağlayacak Generic bir interface metodlarını tanımlama.

public interface IRepository<T>  where T : class
    {
        IEnumerable<t> SelectAll();
        T SelectByID(object id);
        void Insert(T obj);
        void Update(T obj);
        void Delete(object id);
        void Save();
    }

2.Adım - BaseRepository adında Generic IRepository interface'inden implement olan class'ı oluşturma.

public abstract class BaseRepository<T> : IRepository<T> where T : class
    {
        private SampleDbContext db = null;
        private DbSet<t> table = null;
        public BaseRepository()
        {
            this.db = new SampleDbContext();
            table = db.Set<T>();
        }
        public BaseRepository(SampleDbContext db)
        {
            this.db = db;
            table = db.Set<T>();
        }
        public IEnumerable<T> SelectAll()
        {
            return table.ToList();
        }
        public T SelectByID(object id)
        {
            return table.Find(id);
        }
        public void Insert(T obj)
        {
            table.Add(obj);
        }
        public void Update(T obj)
        {
            table.Attach(obj);
            db.Entry(obj).State = EntityState.Modified;
        }
        public void Delete(object id)
        {
            T existing = table.Find(id);
            table.Remove(existing);
        }
        public void Save()
        {
            db.SaveChanges();
        }
    }

3.Adım - Employee adında örnek bir Entity tanımlama

public class Employee
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

 

4.Adım - BaseRepository den inherit olan Employee entity si için EmployeeRepository class'ını tanımlama

  public class EmployeeRepository : BaseRepository<Employee>
    {

    }

5.Adım - Yazmış olduğumuz EmployeeRepository'yi kullanmak

  void Main(string[] args)
        {
            var empRepository = new EmployeeRepository();
            empRepository.SelectAll();
        }

Projemiz hazır. Bundan sonra tanımlayacağınız entity'ler için EmployeeRepository de olduğu gibi Generic olarak yazmış olduğumuz BaseRepository'den türeyen Repository'ler tanımlayarak projeyi geliştirmeye devam edebiliriz.

Not : Bir rivayete göre Repository Design Pattern, Design Pattern ailesi içerisinde en çok kullanılan pattern olduğu söylenir.

 

PostSharp Kullanarak Metot Parametre Null Kontrolü

Daha önceki Aspect Oriented Programming'den ve Postsharp tool'undan bahsetmiştik. Postsharp ile Log, Exception Handling ve metot bazlı Authentication kontrolü gibi işlemleri yapabildiğimizden bahsetmiştik. Bu yazımızda ise Postsharp kullanarak metot bazlı parametre kontrolü (null mı değil i vs. gibi) nasıl yapabiliriz inceleyeceğiz.

Server side geliştirmesi yapmış arkadaşlar bilirlerki metotlarda gelen objeler için validation kontrolü yapmak bazen bizi çok yorabilir veya tahmin ettiğimizden daha fazla zaman aldırabilir. Postsharp ın OnEntry() metodunu kullarak gelen parametler için null mı değil mi diye kolayca kontrol edebiliriz.

Bunun için ilk olarak NullParamsControllingAspect adında OnMethodBoundaryAspect ten inherit olan class'ı oluşturalım ve içeriği aşağıdaki gibi olacaktır. Metoda yollanan null parametleri tespit edip string message olarak client'a ArgumentException fıraltarak bilgilendirme mesajını dönecektir.

[Serializable]
    public class NullParameterControllerAspect: OnMethodBoundaryAspect
    {
        public override void OnEntry(MethodExecutionArgs args)
        {
            var messages = new StringBuilder();

            for (int i = 0; i < args.Arguments.Count; i++)
            {
                if (args.Arguments.GetArgument(i) == null)
                {
                    messages.AppendFormat(args.Method.Name +" metodunda bulunan \"{0}\" parametresi null olamaz.\n", args.Method.GetParameters()[i].Name);
                }
            }

            Console.WriteLine(messages);

            if (messages.Length > 0)
            {
                throw new ArgumentException(messages.ToString());
            }
        }

Yukarıda görüldüğü gibi OnEntry() metodunda gelen parametreyi alıp null mı diye kontrol ediyoruz.

Test için aşağıdaki gibi Prgram.cs class'ını oluşturalım. TransferRequest adında bir class'ımız olsun ve bu class'ı null set ederek ve MoneyTransfer metodunu çağıralım.

 class Program
    {
        static void Main(string[] args)
        {
            TransferRequest req_null = null;
            MoneyTransfer(req_null, "Test");//null obje kullanarak metot çağırımı
        }

        [NullParameterControllerAspect]
        public static void MoneyTransfer(TransferRequest request, string param)
        {
            //transfer işlemleri
        }

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

 Projenizi çalıştırdığınızda ekran çıktısı aşağıdaki gibi olacaktır. 

Dependency Inversion Principle

SOLID prensipleri yazı serisinde son prensip olan SOLID'in "D" si Dependency Inversion prensibine gelmiş bulunuyoruz. Türkçe anlamını her ne kadar beğenmiyor olsam da atalarımız "Bağlılığı Tersine Çevirme Prensibi" olarak çevirmişler.

Bu prensip bizlere OOP yaparken şu 2 kurala uymamız gerektiğini söylüyor;

  • Üst seviye (High-Level) sınıflar alt seviye (Low-Level) sınıflara bağlı olmamalıdır, aralarındaki ilişki abstraction veya interface kullanarak sağlanmalıdır,
  • Abstraction detaylara bağlı olmamalıdır, aksine detaylar abstraction'lara bağlı olmalıdır.

Birçok projede malesef üst seviye sınıflar alt seviye sınıflara bağlıdır ve bu sınıflarda bir değişiklik yapmak istediğimizde başımıza onlarca iş açabilmektedir çünkü alt sınıfta yapılan bu değişiklik üstü sınıfıda etkileyebilir ve üst sınıfların etkilenmesi de projenizdeki bütün yapının etkilenmesi neden olabilir. Bu durum aynı zamanda reusability yani tekrar kullanılabilirlik durumuna engeller. İşte bu karmaşayı ortadan kaldırmak için Dependency Inversion prensibi ortaya çıkmıştır ve üsttede belirttiğim gibi modulleri birbirinden soyutlamamız gerekir. SOLID Nedir makalesinde verdiğim örnekte olduğu gibi gözlüğünüz var ve camlarını değiştirmek istediniz gittiniz gözlükçüye adam dediki "Bu camların değişmesi için gözlüğünde değişmesi gerek..." saçma dimi :) işte bazen bu prensibe uymazsak gözlük örneğinde olduğu gibi enteresan sorunlar başımıza gelebilir. 

Örnek bir case üzerinden ilerleyelim. LogManager adında bir class'ımız olsun ve bu class'ın Log() isminde bir metodu ve bu metod çağrıldığında FileLogger() objesine ait olan Log() metodu çağrılsın ve FileLog işlemini yapsın.

	public class FileLogger  
	{
		public string Message { get; set; }
		public void Log()
		{
			//File Log
		}
	}

	public class DBLogger  
	{
		public string Message { get; set; }		
		public void Log()
		{
			//Database Log
		}
	}

	public class LogManager  
	{
		private FileLogger _file;
		private DBLogger _db;
		
		public LogManager()
		{
			_file = new FileLogger();
			_db = new DBLogger();
		}
	
		public void Log()
		{
			_file.Log();
			_db.Log();
		}
	}

Yukarıda görüldüğü gibi LogManager üst seviye class'ımız ve tam da Dependency Inversion prensibine ters olarak FileLogger ve DBLogger class'larına yani alt seviye class'lara bağlı. DIP bize bu gibi alt-üst seviye sınıf ilişkilerini abstraction veya interface'ler kullanarak kurmamızı söylüyor ancak durum şuan bunun tam tersi ve yarın bir gün yöneticiniz geldi dedi ki "bundan sonra uygulama Log'ları EventLog'a da yazdırılacak". Bunun için gidip aynen File-DB Logger class'larında olduğu gibi EventLogger adında bir class tanımlayıp LogManager() içerisinde aynı işlemleri yapmak heralde istediğimiz bir çözüm değildir ki bu LogManger class'ına extra bir nesneye daha bağlı hale getirir. Hedefimiz LogManager class'ını olabildiğince nesne bağımsız hale getirmek.

 

Bunun için ilk olarak ILogger adında bir interface tanımlayalım ve FileLogger & DBLogger class'larını bu interface'den implement edelim.

	public interface ILogger  
	{
		void Log();
	}

	public class FileLogger : ILogger
	{
		public string Message { get; set; }
		public void Log()
		{
			//File Log
		}
	}

	public class DBLogger : ILogger
	{
		public string Message { get; set; }
		
		public void Log()
		{
			//Database Log
		}
	}

Ve son olarak da LogManager class'ını sadece ILogger interface'ine bağlı hale getirmek kalıyor. Böylelikle ILogger'den implement olmuş bütün class'lar LogManager tarafından kullanılabilecektir.

	public class LogManager 
	{
		private ILogger _logger;
		public LogManager(ILogger logger)
		{
			_logger = logger;
		}
	
		public void Log()
		{
			_logger.Log();
		}
	}

Bu refactoring işlemlerini yaptıktan sonra artık LogManager class'ı Dependency Inversion prensibine uygun hale gelmiştir yani alt seviye sınıflara bağlı değildir aradaki ilişki interface kullanarak sağlanmıştır. 

LogManager class'ının kullanım şekli ise aşağıdaki gibidir.

	public static void Main()
	{
		var dbLogger = new DBLogger();
		dbLogger.Message = "Test 123";
		
		var manager = new LogManager(dbLogger);
		manager.Log();
	}

Özetle; yaptığımız refactoring işlemiyle birlikte DIP'nin söylediği gibi high-level ve low-level sınıfları abstraction'lara bağlı hale getirdik.

Bu yazımızla beraber SOLID prensiplerinin sonuna gelmiş bulunuyoruz. Umarım faydalı bir yazı serisi olmuştur, soru, yorum veya eleştirilere açığımdır :) hope to keep in touch

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.

Code Review Nedir, Neden Önemlidir

Kod kalitesini artırmak için bir çok yol izlenebilir; unit test, continuous integration, continuous deployment, agile methodology etc. ancak en önemli yollardan biri code review dır. Bazen kaliteli diyebileceğimiz kodlar yazmaya çalışsak bile öyle anlar geliyor ki proje müdürünüz bir anda gelip bu feature'u bugün yetiştirmemiz gerekiyor deyip spaghetti code yazma süreciniz başlıyor. Bu gibi durumlar hep olacak ancak daha sonrasında geriye dönüp yazmış olduğunuz kodu review-refactoring etmemiz gerekmekte. Yada normal akışında geliştirme yapıyor olsak bile teknik olarak bilgili birisi (genelde takım liderleri) yapmış olduğumuz geliştirmelere bir göz atıp ondan sonra master branch'ine commit/checkin yapıyor olmamız gerekir.

Code Review yapmanın bir çok avantajı var;

  • Kod kalitesini arttırır,
  • Bug bulmamızı kolaylaştırır,
  • Developer kendisi olmasa bile bir başkası review işlemi yapacağından geliştirme yaparken daha dikkatli olmasını sağlar,
  • Review işlemi yapan kişi daha önce hiç karşılaşmadı şekilde olumlu geliştirmelerle karşılaşabilir ve onun gelişimine de katkısı olur,
  • Yazılım standartlarına bağlı kalmaya yönlendirir.

Gibi gibi birçok faydasından bahsedebiliriz. 

Başlangıçta biraz garip gelebilir ancak iyi bir flow bulduğumuzda hem yazılım kalitesi, hem ekip çalışması ve ekibin moral-motivasyonu açısından ne kadar büyük bir değişim olduğunu fark edeceksiniz dir.

Code Review İçin İzlenebilecek Yollar/Kurallar

Code Review için belli başlı kabul edilen bazı kurallar mevcut. Bazı software tool'lar kullanarak başlanabilir ancak developer olarak bizlerin uyması gereken bazı kurallar sıralayabiliriz. 

 

Kısa Commit'ler ve Anlaşılır Commit Mesajları

İlgili branch'e commit yaparken bir seferde bütün her şeyi commit'lemekten ziyade kısa kısa paketler halinde yaptığınız geliştirmeleri commit edin ve anlaşılır commit mesajları kullanmaya çalışın. Böyle yaparak her şeyi step-by-step açıklamış olacaksınız ve hem review yapacak kişi için kolay anlaşılabilir olacak hemde kendiniz için geriye dönüp baktığınızda yaptığınız geliştirmeyi hatırlaması daha kolay olacaktır.

Kodları Review Edin Developer'ı Değil

Review yaparken başkalarının yapmış olduğu işleri incelediğinizden eleştiri yaparken bazen yapılan işe değilde kişinin kendisine eleştirilerde bulunulabiliyor. Ancak bu devloper'la üzerinde olumsuz tepebilir. Yanlış yaptıkları bir şeyi gördüğünüzde onları sürekli eleştirmek yerine nasıl daha doğru yapabilirlerdi o yönde tavsiyelerde bulunmak daha çok önemlidir. Tabi bunları söylerken kullandığınız ses tonu da oldukça önemlidir.   

Developer olarak, review eden kişi commit'inize yorumlarda bulunduğunda bunu kişisel algılamamaya çalışın tabi doğrudan kişisel bir söylemde bulunulmuyorsa.  

Muhakkak Checklist'iniz Olsun

Hem review edecek olan kişi hemde developer için checklist'inizin olması önemli daha sistematik bir review süreci için oldukça önemlidir.

  • Bugs,
  • Duplicate Code,
  • Login Pages,
  • Code Style and Formatting
  • etc..

Bu gibi checklist'ler yaparak süreci daha kolay hale getirebiliriz. 

Multiple Reviewers

Genelde bir kişi bulmak bile çok zor oluyor ancak eğer mümkünse kodunuzu birden fazla kişiye review ettirin. Eğer kodla ilgili bir anlaşmazlık var ise örneğin; a kişisi burda factory design pattern kullanman doğru olmuş dedi, B-C kişileri hayır burda abstract factory kullanmalısın dedi. Hemen bu gibi durumlarda oylama yapılıp çoğunluğa göre karar verilebilir.

Ne Zaman Review Yapılmalı ?

Aslında bu büyük oranda development ekibine bağlı.Size bir geliştirme atandı ve 4 gün içinde dev branch'inde geliştirme yaptınız ve test'e gönderdiniz, testlerden herhangi bir bug vs. çıkmayıp "Ok production'a alabiliriz.." dedikten sonra yapmış olduğunuz geliştirmeleri master branch'ine merge işleminden önce review edecek olan kişiye gönderilmesi en doğru olan zamandır diyebiliriz. Review edilmeyen kodları master'a merge etmemek gerekir.

 

Review İşleminden Sonra..

Dev ortamda yaptığınız geliştirmeleri review eden kişi eğer herhangi bir sorun görmez "OK" verir ise master branch'ine merge işlemini yapabilirsiniz.  Eğer yaptığınız geliştirmeler review eden kişiden onay almaz ise tekrardan dev branch'de istenilen refactoring işlemlerini yapıp projenin son halini tekrardan review eden kişiye assign etmek gerekir. Bu sefer reviewer'dan "OK" aldıysanız üstte de belirttiğim gibi master branch'ine geliştirmelerinizi merge edebilirsiniz.

 

Özetle Code Review yazılım kalitesini artırmak için yapılabileceklerin en başında gelir. Eğer Development ekibinin başında olan kişi iseniz büyük ihtimalle review işlemini yapan kişi siz olacaksınızdır ve takımınıza belli başlı kabul görmüş yazılım geliştirme standartlarına uymalarını sağlarsanız review süreciniz daha rahat ve keyifli geçecektir.

 

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.. :)

Interface Segregation Principle

SOLID prensipleri yazı dizisinde sırada SOLID'in "I" olan Interface Segregation (ISP) var. Bu prensip bize kısaca şunu söylüyor; "Nesneler asla ihtiyacı olmayan property/metot vs içeren interface'leri implement etmeye zorlanmamalıdır  !".

Terminolojide bu interface'ler "fat" yada "polluted" interfaces diye adlandırılır.  ISP uygulanmadığında bir den fazla sorumluluğu olan nesneler ortaya çıkar ki bu da aslında SOLID'in "S" si olan "Single Responsibility" prensibine aykırı bir şeydir. Bu sınıflar yüklenen çoklu sorumluluklardan dolayı zaman içerisinde yönetilemez hale gelirler ve projemiz patates olur çıkar. Bu tarz case'ler le karşılaşıldığında interface içerisinde bulunan kullanılmaması gereken özellikleri içeren yeni interface'ler tanımlanır ve ihtiyaca göre nesneler tarafından implement edilir. Eğer projeniz bu prensibi ihlal ediyorsa Adapter Design Pattern avantajlarını kullanarak da ilerleyebilirsiniz. İlgili nesne interface'i implement edip hiç kullanmayacağı metotlara sahip olduğunda class'ınız içerisinde "NotImplementedException" yazan metotlar olacaktır ve buda OOP açısından hiç istenen bir şey değildir.

ISP'yi örnek bir case üzerinde anlatmaya çalışalım. Bir tane FileLog ve DbLog yapan işlemleri yapan bir proje geliştireceğiz ve içerisinde ILog adında bir interface tanımlayalım ve bu interface'inde Log(), OpenConn(), CloseConn() gibi metodları olsun.

public interface ILog
{ 
    void Log(string message);

    void OpenConnection();

    void CloseConnection();
}

 

İlk olarak DBLogger sınıfını yazalım

public class DBLogger : ILog
{		
        public void Log(string message)
        {
            //Code to log data to a database
        }

        public void OpenConnection()
        {
            //Opens database connection
        }

        public void CloseConnection()
        {
           //Closes the database connection
        }
}

 

Şimdi de FileLogger class'ını yazalım. 

public class FileLogger : ILog
    {
        public void Log(string message)
        {
            //Code to log to a file           
        }
    }

FileLog işlemi yaparken Db de olduğu gibi bir Connection açıp kapama işlemi yok ve bu metotları FileLogger'da kullanmak istemiyoruz sadece ILog interface'inde tanımlı olan Log(string message) metodunu kullanmak istiyoruz. Projeyi derlediğinizde şöyle bir hata alırsınız "FileLogger class doesn't implement the interface members ILog.OpenConnection() and ILog.OpenConnection()" .  Peki hatayı görüp eksik interface'in eksik üyelerini implement edelim. Bu sefer class'ımız aşağıdaki gibi olacaktır.

public class FileLogger : ILog
    {
        public void Log(string message)
        {
             //Code to log to a file           
        }

        public void CloseConnection()
        {
            throw new NotImplementedException();
        }

        public void OpenConnection()
        {
            throw new NotImplementedException();
        }
    }

E şimdi ne oldu ? Patates ! Hiç ihtiyacımız olmasa da FileLogger class'ına 2 tane gereksiz metot kazandırdık. İşte ISP burada devreye giriyor. Yapmamız gereken şey yeni interface veya interface'ler tanımlayarak FileLogger ve DbLogger class'larının sadece ihtiyacı olan metotları içeren interface'i implemente etmesini sağlamak.

Yeni interface'ler aşağıdaki gibi olacaktır.

    public interface ILog
    {
        void Log(string message);
    }

    public interface IDBLog: ILog
    {
        void OpenConnection();

        void CloseConnection();
    }

    public interface IFileLog: ILog
    {
        void CheckFileSize();

        void GenerateFileName();
    }

 

Bu interface'leri implement eden DbLogger ve FileLogger class'larımızda aşağıdaki gibidir.

 public class FileLogger : IFileLog
    {
        public void CheckFileSize()
        {
            //Code to check log file size
        }

        public void GenerateFileName()
        {
            //Code to generate a new file name
        }
		
        public void Log(string message)
        {
            //Code to log data to the log file
        }
    }

    public class DBLogger : IDBLog
    {
        public void Log(string message)
        {
            //Code to log data to the database
        }

        public void OpenConnection()
        {
            //Code to open database connection
        }

         public void CloseConnection()
         {
            //Code to close database connection
         }
    }

Interface Segregation çok önemli bir prensiptir. Özellikle Adapter Design Pattern ile haşır neşir olan arkadaşlar için dahada fazla öneme sahiptir. İçerisinde 60-70 tane üyesi bulunan interface'ler yazmaktan da çekinin bu gibi durumlarda bir yolunu bulup interface üyelerini ayırıp gruplayıp yeni interface'ler türetmeye çalışmamız daa doğru olacaktır.