Web Api FluentValidation Kullanımı

Özellikle server-side geliştirme yapan kişiler için validation olmazsa olmazlardan biridir. Yazmış olduğunuz api'a client'lar belli başlı parametreler göndererek request'te bulunurlar ve sürekli bir veri alış verişi söz konusudur ve server-side geliştirici olarak asla gönderilen input'a güvenmememiz gerekir. Metotlara gönderilen parametreleri belli başlı bazı güvenlik adımlarından&validasyonlardan geçirdikten sonra işlemlere devam ediyor veya etmiyor olmamız gerekir. FluentValidation kütüphanesi ile bu gibi durumlar için belli validation-rule'lar oluşturarak unexpected-input dediğimiz istenmeyen parametrelere karşı önlemler alabiliriz.

Örnek üzerinden ilerleyecek olursa; Bir Web Api projemiz olsun ve bu proje için Para Gönderme (Eft & Havale) işlemi yapan bir modül yazalım. MoneyTransferRequest adında bir model'imiz olsun ve bu model üzerinden para göndermek için gerekli olan bilgiler kullanıcıdan alalım. MoneyTransferRequest model için FluentValidation kütüphanesini kullanarak gerekli validation işlemlerini tanımlayalım validation-failed olduğunda bu durumdan client'ı hata mesajları göndererek bilgilendirelim, yani validationMessage'ı response olarak client'a return edelim. 

İlk adım olarak vs'de açtığımız Web Api projemize nuget'ten FluentValidation.WebApi kütüphanesini indirip kuralım

MoneyTransferRequest.cs class'ımız aşağıdaki gibi olacaktır.

    public class MoneyTransferRequest
    {
        public decimal Amount { get; set; }
        public string SenderIBAN { get; set; }
        public string ReceiverIBAN { get; set; }
        public DateTime TransactionDate { get; set; }
    }

Şimdi ise sırada MoneyTransferRequest için yazacağımız Validator class'ı var. Bu class AbstractValidator<T> class'ından inherit olmak zorundadır ve request modelimizin property'leri için geçerli olan rule'ları burada tanımlayacağız.

    public class MoneyTransferRequestValidator  : AbstractValidator<MoneyTransferRequest>
    {
        public MoneyTransferRequestValidator()
        {
            RuleFor(x => x.Amount).GreaterThan(0).WithMessage("Amount cannot be less than or equal to 0.");

            RuleFor(x => x.SenderIBAN).NotEmpty().WithMessage("The Sender IBAN cannot be blank.").Length(16, 26).WithMessage("The Sender IBAN must be at least 16 characters long and at most 26 characters long.");

            RuleFor(x => x.ReceiverIBAN).NotEmpty().WithMessage("The Receiver IBAN cannot be blank.").Length(16, 26).WithMessage("The Receiver IBAN must be at least 16 characters long and at most 26 characters long.");

            RuleFor(x => x.TransactionDate).GreaterThanOrEqualTo(DateTime.Today).WithMessage("Transaction Date cannot be any past date.");
        }
    }

Tanımlamış olduğumuz MoneyTransferRequestValidator class'ını MoneyTransferRequest class'ı için olduğunu belirten tanımlamayı aşağıda olduğu gibi attribute gibi class ismi üzerinde belirtiyoruz.

    [Validator(typeof(MoneyTransferRequestValidator))]
    public class MoneyTransferRequest
    {
        public decimal Amount { get; set; }
        public string SenderIBAN { get; set; }
        public string ReceiverIBAN { get; set; }
        public DateTime TransactionDate { get; set; }
    }

FluentValidationModelValidatorProvider'ı WebApiConfig class'ı içerisinde aşağıdaki enable ederek validator için config işlemlerimizi tamamlamış oluyoruz.

public static class WebApiConfig  
{
    public static void Register(HttpConfiguration config)
    {
        FluentValidationModelValidatorProvider.Configure(config);
    }
}

Yapılan request'leri tek bir yerden handle edip valid bir işlem mi değil mi kontrolü için custom ActionFilter'ımızı tanımlayalım. Bu actionFilter validation'dan success alınamaz ise yani MoneyTransferRequest modeli için tanımladığımız validasyon kuralları sağlanmaz ise client'a yeni bir response dönüp içerisine validationMessage'ları set ediyor olacağız.

public class ValidateModelStateFilter : ActionFilterAttribute  
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
        }
    }
}

Bu action filter için register işlemini aşağıdaki gibi yapıyoruz.

        public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
                config.Filters.Add(new ValidateModelStateFilter());//register ettik
                FluentValidationModelValidatorProvider.Configure(config);
            }
        }

Client'a döndüğümüz response'lar için bir base response tanımlayalım.

    public class BaseResponse
    {
        public BaseResponse(object content, List<string> modelStateErrors)
        {
            this.Result = content;
            this.Errors = modelStateErrors;
        }
        public List<string> Errors { get; set; }

        public object Result { get; set; }
    }

Şimdi ise custom ResponseHandler'ımızı tanımlicaz. Bu handler her bir response'u kontrol ederek yukarıda tanımlamış olduğumuz BaseResponse'a convert edicek ve client'a bu response modeli dönecek. 

    public class ResponseWrappingHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var response = await base.SendAsync(request, cancellationToken);

            return BuildApiResponse(request, response);
        }

        private HttpResponseMessage BuildApiResponse(HttpRequestMessage request, HttpResponseMessage response)
        {
            object content;
            var modelStateErrors = new List<string>();

            if (response.TryGetContentValue(out content) && !response.IsSuccessStatusCode)
            {
                var error = content as HttpError;
                if (error != null)
                {
                    content = null; 

                    if (error.ModelState != null)
                    {
                        var httpErrorObject = response.Content.ReadAsStringAsync().Result;

                        var anonymousErrorObject = new { message = "", ModelState = new Dictionary<string, string[]>() };

                        var deserializedErrorObject = JsonConvert.DeserializeAnonymousType(httpErrorObject, anonymousErrorObject);

                        var modelStateValues = deserializedErrorObject.ModelState.Select(kvp => string.Join(". ", kvp.Value));

                        for (var i = 0; i < modelStateValues.Count(); i++)
                        {
                            modelStateErrors.Add(modelStateValues.ElementAt(i));
                        }
                    }
                }
            }

            var newResponse = request.CreateResponse(response.StatusCode, new BaseResponse(content, modelStateErrors));

            foreach (var header in response.Headers) 
            {
                newResponse.Headers.Add(header.Key, header.Value);
            }

            return newResponse;
        }
    }

ResponseHandler'ı da api için register ediyoruz ve WebApiConfig class'ının son hali aşağıdaki gibi olacaktır.

        public static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            config.Filters.Add(new ValidateModelStateFilter());
            config.MessageHandlers.Add(new ResponseWrappingHandler());
            FluentValidationModelValidatorProvider.Configure(config);
        }

 Artık Api'ı test edebiliriz. Ben genelde bu tarz işlemler için postman rest client'ı kullanıyorum. Aşağıdaki gibi hatalı parametreler ile bir request'te bulunduğumuzda nasıl response alıyoruz inceleyelim.

 

Görüldüğü üzre Amount, SenderIBAN ve TransactionDate alanlarını hatalı girdiğimizde yukarıdaki gibi validation message'larının döndüğü bir response alıyoruz. ReceiverIBAN alanı validasyona takılmadığından bu alan ile ilgili herhangi bir mesaj almadık.

Özetle yazımızın başında da belirttiğim gibi Validasyon oldukça önemli bir konudur ve client hatalı bir input gönderirse anlaşılması kolay bir response oluşturarak ilgili validasyon mesajını client'a return etmek işimizi oldukça kolaylaştıracaktır. Yapmış olduğumuz bu geliştirme ile birlikte otomatik bir biçimde FluentValidation tarafından fırlatılan validasyon mesajları tam response dönme anında handle edilip client'a döndürmektedir.

ServiceLocator Design Pattern

Design pattern'leri spagetti kodlara veya tekrar eden kod bloklarına çözümler olarak ortaya çıkmışlardır. ServiceLocator design pattern ile bağımsız bir şekilde geliştirilebilen, test edilebilen loosely coupled modüller inşa edebilirsiniz. 

ServiceLocator'ı kısaca tanımlamak gerekirse projede kullanılan service instance'larını tek bir yerden add ve get yönetimini yapma işlemlerinden sorumludur diyebiliriz.

İmplemente ederken bağımlılığı interface ve propertilerden veya constructor seviyesinde inject edilebilirsiniz.

Örnek verecek olursak birden fazla service instance'ınızın bulunduğu bir proje var ve bir ara katman web service projesi ile bu servislerin metotlarını dışarıya açtınız diyelim. Her bir metot call işleminde service instance oluşturmak yerine app-start veya başka bir anda instance'ları ServiceLocator'a register edip sonrasında ihtiyaç duyulan yerlerde burada bulunan instance'lar üzerinden service'leri kullanmak diyebiliriz.

ServiceLocator'ın temel yapı taşlarını aşağıdaki gibi sıralayabiliriz;

  1. ServiceLocator , İhtiyaç duyulan service instance'ının yaratılmasından sorumludur, eğer yaratılmışsa da serviceRepository görevi görür diyebiliriz,
  2. Initializer ,  Runtime'da service instance'ının yaratılmasından sorumludur. Instance bir kere oluşturulduğunda ServiceLocator içerisinde store edilir,
  3. Client , Service consumer'larını veya service client'larını temsil eder,
  4. Service(s) , Service contract'larını ve onların implementasyonunu temsil eder.

 

ServiceLocator Patter'nin Implementasyonu

İlk olarak VS kullanarak bir tane WCF projesi oluşturalım ve sonrasında CustomerService ve ProductService adında 2 tane service oluşturalım. Bu iki service'in contract interface'leride aşağıdaki gibi tanımlayalım.

        [ServiceContract]
        public interface ICustomerService
        {
            [OperationContract]
            string GetName();
        }

        [ServiceContract]
        public interface IProductService
        {
            [OperationContract]
            string GetName();
        }

Sırada bu contract'ları service'lere implement etme var.

        public class ProductService : IProductService
        {
            public string GetName()
            {
                return "Fitbit";
            }
        }

        public class CustomerService : ICustomerService
        {
            public string GetName()
            {
                return "John travolta";
            }
        }

Şimdi ise sırada ServiceLocator class'ını yaratmak var. Bu class static olacak ve içerisinde service instance'larını tutan bir Dictionary bulunduracak. Bu Dictionary için get ve set operasyonları kullanılmak üzre ServiceLocator class içerisinde RegisterService ve GetService adında 2 adet metot tanımlayacağız. RegisterService instance'ları dictionary'e eklemek için kullanacağımız metot. GetService ise dictionary'de bulunan service instance'ını return eden metot olacak.

    public static class ServiceLocator
    {
        private static readonly Dictionary<Type, object> registeredServices = new Dictionary<Type, object>();

        public static T GetService<T>()
        {
            return (T)registeredServices[typeof(T)];
        }

        public static void RegisterService<T>(T service)
        {
            registeredServices[typeof(T)] = service;
        }

        public static int Count
        {
            get { return registeredServices.Count; }
        }
    }

Bu aşamaya kadar oldukça basit bir ServiceLocator pattern ile projemizi oluşturduk. Şimdi ise sırada oluşturduğumuz bu Locator'ı proje içerisinde kullanmak var.

    static void Main(string[] args)
       {
           ICustomerService customerService = new CustomerService();//service instance aldık

           IProductService productService = new ProductService();//service instance aldık

           ServiceLocator.RegisterService(customerService);//service locator'da bulunan dictionary içerisine attık

           ServiceLocator.RegisterService(productService);//service locator'da bulunan dictionary içerisine attık

           ICustomerService customerClientService = ServiceLocator.GetService<ICustomerService>();//dictionary içerisinde bulunan service instance'ını aldık

           string msg = customerClientService.GetName();

           Console.WriteLine(msg);
       }

 

ServiceLocator pattern'i objelerinizi bağımlılıklarından ayırmak istediğinizde veya compile-time'da bağımlılıkları bilinmeyen objeler oluşturmanız gerektiğinde güzel bir seçenek olabilir. 

ConcurrentStack ve ConcurrentQueue

Thread-safe collection ilk olarak .Net framework 4.0 ile System.Collections.Concurrent namespace'i altında hayatımıza girdi. System.Collections.Concurrent namespace'i altında thread-safe geliştirmeler yapmada kullanabilmek için ConcurrentStack ve ConcurrentQueue adında 2 tane tip bulunmaktadır.

ConcurrentStack

ConcurrentStack LIFO (last in first out) prensibine göre çalışan bir data stracture dır. Generic ConcurrentStack<T> thread safe olan Stack<T>'nin karşılığı olarak düşünebiliriz ve .Net Framework 4.0 ile hayatımıza girmiştir.

Bu class ile kullanılabilecek metotlar ; 

  1. Push(T element) T tipindeki objeyi stack'e eklemek için kullanılır,
  2. PushRange T tipinde ki array'leri stack'e eklemek için kullanılır,
  3. TryPop(out T) stack'te bulunan ilk item'i döner, başarılı ise true değilse false döner
  4. TryPeek(out T) stack'te bulunan bir sonraki item'ı almak için kullanılır ancak bu item'ı stack'ten silmez, başarılı ise true değilse false döner,
  5. TryPopRange TryPop metodu ile aynı çalışır sadece geriye tek bir obje değilde bir array döner.

 ConcurrentStack<T>'nin instance oluşturma ve Push metodu kullanımı aşağıdaki gibidir.

            var concurrentStack = new ConcurrentStack<int>();

            for (var i = 0; i < 10; i++)
            {
                concurrentStack.Push(i);
            }

Concurrent stack'te bulunan bir obje için get işlemi yapmak istediğimizde ise TryPop(out T) metodunu kullanıyoruz. 

            int outData;
            bool isSuccess = concurrentStack.TryPop(out outData);

Full kullanım örneği olarak aşağıda bulunan  metot 1'den başlayıp 100'e kadar olan sayıları Concurrent stack'e atıp sonrasında stack'ten okuma işlemini yapıp ekrana sayıları display etmektedir..

        public static void Main()
        {
            var concurrentStack = new ConcurrentStack<int>();

            for (var i = 1; i < 100; i++)
            {
                concurrentStack.Push(i);
            }

            while (concurrentStack.Count > 0)
            {
                int stackData;

                bool success = concurrentStack.TryPop(out stackData);

                if (success)
                {
                    Console.WriteLine(stackData);
                }
            }
        }

Projeyi çalıştırdığınızda 99'dan başlayıp 0'a kadar olan sayıları display edecektir.

ConcurrentStack aynı zamanda ToArray() property'sini destekler ve stack'i array'a convert etmemize olanak sağlar.

var integerArray = concurrentStack.ToArray();

Stack'in içerisinde item var mı yok mu diye kontrol etmek istediğimizde ise IsEmpty property'sini kullanarabiliriz.

if(!concurrentQueue.IsEmpty)
{
    ;//todo
}

 

ConcurrentQueue

ConcurrentQueue FIFO (first in first out) prensibine göre çalışan bir data stracture dır. Generic ConcurrentQueue<T> thread safe olan Queue<T>'nin karşılığı olarak düşünebiliriz ve yinde ConcurrentQueue de ConcurrentStack gibi .Net Framework 4.0 ile hayatımıza girmiştir.

 ConcurrentQueue<T>'nin bazı önemli metotlarını aşağıdakiler olarak sıralayabiliriz.

  1. Enqueue(T element)  T tipindeki objeyi queue'ye eklemek için kullanılır,
  2. TryPeek(out T) queue'da yada kuyrukta bulunan bir sonraki item'ı almak için kullanılır ancak bu item'ı queue'dan silmez, başarılı ise true değilse false döner,
  3. TryDequeue(out T) bu metot kuyruktaki ilk objeyi almak için kullanılır. TryPeek(out T) metodunun tersine objeyi aldıktan sonra kuyruktan siler, başarılı ise true değilse false döner,

Aşağıda bulunan code-snippet'ı nasıl integer değerler saklamak için ConcurrentQueue instance oluşturulur göstermektedir.

var concurrentQueue = new ConcurrentQueue<int>();

Kuyruğa integer değer atmak için ise aşağıdaki gibi Enqueue metodu kullanılabilir.

concurrentQueue.Enqueue(100);

Full kulalnıma bakacak olursak aşağıda bulunan  metot 1'den başlayıp 100'e kadar olan sayıları ConcurrentQueue'ye atıp sonrasında ConcurrentQueue'den okuma işlemini yapıp ekrana sayıları display etmektedir..

	public static void Main()
	{
		    var concurrentQueue = new ConcurrentQueue<int>();

            for (var i = 1; i < 100; i++)
            {
                concurrentQueue.Enqueue(i);
            }

            int queueData;

            while (concurrentQueue.TryDequeue(out queueData))
            {
                Console.WriteLine(queueData);
            }
	}

Projeyi çalıştırdığınızda 1'den başlayıp 100'e kadar olan sayıları ekrana display edecektir.

Hem ConcurrentStack hem de ConcurrentQueue class'ları thread safe'dirler ve internal olarak locking ve synchronization konularını yönetebilirler.

ConcurrentQueue aynı zamanda ToArray() property'sini destekler ve queue'yu array'a convert etmemize olanak sağlar.

var integerArray = concurrentQueue.ToArray();

Queue'nun içerisi boş mu dolu mu diye kontrol etmek istediğimizde ise IsEmpty property'sini kullanarabiliriz.

while(!concurrentQueue.IsEmpty)
{
     int result;

     concurrentQueue.TryDequeue(out result);

     Console.WriteLine(result);
}

Thread safe programlama ile uğraşıyorsanız ConcurrentStack ve ConcurrentQueue oldukça faydalı sınıflardır. Özellikle mesajlaşma yapıları & web service vs. gibi geliştirmeler gerektiren yerlerde biz developer'lara oldukça kolaylıklar sunmaktadır, unutmamakta fayda var :)

Kısaca Agile Nedir Neler Gerektirir

Agile aşağı Agile yukarı... Herkes bir agile'dır gidiyor ama malesef çoğu kişi için agile bir kaç cümle veya kelimeden ibaret.

Bir çok Yönetici & Patron için çabucak at koşturur gibi işi yapıp bitirme,

Bir çok çalışan için ise agile jira'da bulunan bug'ları çözmekten ve scrum yapmaktan ibaret..

Malesef Türkiye'de biraz gerilerde ancak güzel haberlerde gelmiyor değil, öyle ki bazı kurumsal bankalar dahi yazılım departmanı olmadığı halde kendi iç birimlerinde agile'ı test etmeye başladığı söyleniyor ve Türkiye'de bulunan çok büyük bir bankanın da bütün birimleriyle agile'a %100 geçtiğiyle ilgili geçenlerde bir haber okumuştum. Tabi bunlar agile doğru anlaşıldığı zaman güzel haberler oluyor.

Sektörde bulunan yazılım firmalarında ki bir çok development takımı kendi içlerinde agile yaptıklarını söylüyorlar ancak işin aslını konuşmaya çalıştığınızda anlıyorsunuz ki sadece agile'a ait bir kaç işi yaptıkları ortaya çıkıyor.

Scrum için ise aslında yapılan şey iş yoğunluğa göre bazı sabahlar proje müdürünün ekibi toplayıp sadece kendisinin konuşması...

 

Peki Agile olmak neleri gerektirir ?

Daha önce okuduğum ve bir çok kişininde referans alarak uygulamaya çalıştığı "What Do We Know About Scientific Software Development’s Agile Practices ?" makalesinde belirtilen 35 Agile Best Practice'i baz alarak takım olarak ne yapıp ne yapmıyorsunuz, ne kadar agile'sınız yorumlayabilirsiniz. 

  1. Önceliklendirmeler projede en yetkili kişi olan product owner tarafından (ekibin fikrini alarak) yapılmalı,
  2. Development sürecindeki sorunlar scrum master tarafından scrum'larda çözüm aranmalı,
  3. Spring backlog oluşturmak için Spring planlama toplantıları yapılmalı,
  4. Efor verirken Planning Poker oyunu gibi eğlenceli bir seçiminiz olmalı,
  5. Kısa ve Orta vadede koşan sprint'leriniz olmalı,
  6. Product owner ve ekip arasında sürekli bir communication olmalı,
  7. Mevcut konuları konuşmak için kısa daily meeting'ler düzenlenmeli,
  8. Self-organization yani kendiliğinden organize olabilen bir ekip uluşturulabilmeli,
  9. Grafikler ile sprint süreci gözlemlenmeli,
  10. Sprint sonlarında review toplantıları düzenlenmeli,
  11. Retrospective yaparak geçmiş sprintle ilgili değerlendirmelerde bulunulmalı,
  12. Short & Fast product release'ler çıkılmalı,
  13. User storie'ler yazılmalı,
  14. Takıma dedike bir şekilde çalışabileceği açık çalışma alanları yaratılmalı,
  15. Sürdürülebilir bir çalışma ortamı sunulmalı,
  16. Proje ivmesi ölçülebilmeli,
  17. Ekipte bulunanlar her konuda fikrini söyleyebilmeli,
  18. Müşteriye ihtiyaç duyulduğunda hemen ulaşılabiliyor olmalı,
  19. Ekip olarak kod standartları belirlenmeli,
  20. İlk önce unit-test'ler yazılmalı,
  21. Prod'a alınan kod'un bir pair-programming ürünü olduğu unutulmamalı,
  22. Geliştirmeler branch'lere ayrılarak yapılmalı ve test sonuçlarına göre merge işlemi yapılmalı,
  23. Sürekli olarak yeni şeyler entegre edilebilecek altyapıya sahip bir altyapı,
  24. Repository yönetimini iyi yapıp release günü geldiğinde sorunsuz bir şekilde release çıkılabilmeli,
  25. Collective bir biçimde developer'lar atayın bir sorumluluğu tek bir kişiye yüklemekten kaçının,
  26. Basit bir tasarıma desenine sahip olmalı,
  27. Bir sistem metforu seçilmeli,
  28. Design&Development için Class-responsibility-collaboration kartlarını kullanmaya çalışın,
  29. Riski azaltmak için çözümler üretilmeli,
  30. İhtiyaç duyulmayan hiç bir fonksiyonalite önceden entegre edilmemeli,
  31. Ne zaman-Nerde olduğuna bakılmaksızın refactor edilebilmeli,
  32. Bütün kod'un unit-test'i yazılmış olmalı,
  33. Proje prod'a alınmadan önce bütün unit-test'lerden geçmeli,
  34. Yeni bir bug bulunduğunda testler anında oluşturulmalı,
  35. Acceptence-test'ler sıklıkla uygulanmalı ve skor yayınlanmalı.

 

Agile hakkında söylenecek oldukca fazla şey bulunuyor ancak kısa bir özetle yukarıda ki rule'ları sıralamış olduk. İyi bir agile-team oluştururken bu kurallardan faydalanmak çöğunlukla iyi olacaktır ama belirttiğim gibi liste genişletilebilir de.

Caching With Postsharp

Cache bir proje için olmazsa olmazlardan biridir diyebiliriz ve ihtiyaç duyulduğunda çok can kurtarır. Öyleki çok fazla değişmesini düşünmediğiniz verileri cache de saklayarak reqeust response time konusunda projenize oldukça fazla performans kazandırabilirsiniz. Daha önceki web api için cache ve aspect oriented - postsharp konularından bahsetmiştik. WCF Service projenize Postsharp ile basit bir server-side cache yapısı kurabilirsin.

İlk adım olarak cache de tutacağımız CacheModel adlı model'i tanımlayalım.

public class CacheModel
{
    public DateTime CacheTimeOutInfo { get; set; }//cache'in ne zaman timeout olacağını belirten time bilgisi
    public object CacheData { get; set; }//cache'de tutulacak object
}

Şimdi ise CacheManager'a implement edeceğimiz ICache interface'i tanımlayalım ve içerisine cache metotlarımızı yazalım.

public interface ICache
{
   bool Contains(string key);//key varmı yokmu diye control ettiğimiz metot
   void Add(string key, CacheModel cacheData);//cache key'i ile birlikte cache model'i alıp cache'e ekleyen metot
   object Get(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
   void RemoveAll();//bütün cache'i silen metot
}

Aşağıda yazacağımız class'ta ise üstte yazdığımız ICache'i implement eden cache yönetimini yapacağımız CacheManager class'ını oluşturacağızz.

    public class CacheManager : ICache
    {
        public bool Contains(string key)
        {
            var isExist = HttpRuntime.Cache[key];
            if (isExist != null)
            {
                var obj = GetCacheModel(key);
                if (obj.CacheTimeOutInfo > DateTime.Now)
                    return true;
                Remove(key);
            }
            return false;
        }

        public void Add(string key, CacheModel cacheData)
        {
            if (Contains(key))
                Remove(key);
            HttpRuntime.Cache[key] = cacheData;
        }

        public object Get(string key)
        {
            var obj = GetCacheModel(key);
            return obj.CacheData;
        }

        private CacheModel GetCacheModel(string key)
        {
            return (CacheModel)HttpRuntime.Cache[key];
        }

        public void Remove(string key)
        {
            HttpRuntime.Cache.Remove(key);
        }

        public void RemoveAll()
        {
            var enumerator = HttpRuntime.Cache.GetEnumerator();
            while (enumerator.MoveNext())
            {
                HttpContext.Current.Cache.Remove((string)enumerator.Key);
            }
        }
    }

Sırada Postsharp kullanarak oluşturacağmız CacheAspect adında interceptor'ı yazama var. Bunun için nuget'ten postsharp'ı indiriyoruz ve aşağıdaki gibi CacheAspect adındaki interceptor'ımızı oluşturuyoruz.

    [Serializable]
    public class CacheAspect : OnMethodBoundaryAspect
    {
        //Metottan alacağımız cache süresi
        public int MethodCacheDurationInMinute = 0;

        public override void OnEntry(MethodExecutionArgs args)
        {
                 //cache key oluşturma
                var cacheKey = CreateMD5(GenerateCacheKey(args));

                var cacheManager = new CacheManager();

                var isCacheKeyExist = cacheManager.Contains(cacheKey);

                if (isCacheKeyExist)
                { 
                    //eğer request geldiğinde cache key cache de varsa client'a cahe de bulunan veriyi dön
                    args.ReturnValue = cacheManager.Get(cacheKey);
                    args.FlowBehavior = FlowBehavior.Return;
                }
                else
                {
                    //cache de yoksa normal akışta ilerlemesine devam eder
                    args.MethodExecutionTag = cacheKey;
                }
        }

        public override void OnSuccess(MethodExecutionArgs args)
        {
                //başarılı bir şekilde metottan çıktıysa cache'e at

                    var cacheKey = (string)args.MethodExecutionTag;

                    var cacheManager = new CacheManager();
                    var cacheModel = new CacheModel
                    {
                        CacheData = args.ReturnValue,
                        CacheTimeOutInfo = DateTime.Now.AddMinutes(MethodCacheDurationInMinute)
                    };
                    cacheManager.Add(cacheKey, cacheModel);
        }

        private static string GenerateCacheKey(MethodExecutionArgs args)
        {
            var generatedKey = string.Empty;

            if (args.Method.DeclaringType != null)
                generatedKey = args.Method.DeclaringType.Name + "-" + args.Method.Name + (args.Arguments.Any() ? "-" + args.Arguments.Aggregate((first, second) => first + "-" + second) : "");
            generatedKey += SerializeObjectToJson(args.Arguments);

            return generatedKey;
        }

        public static string CreateMD5(string input)
        {
            using (var md5 = System.Security.Cryptography.MD5.Create())
            {
                var inputBytes = Encoding.ASCII.GetBytes(input);
                var hashBytes = md5.ComputeHash(inputBytes);

                var sb = new StringBuilder();
                foreach (var t in hashBytes)
                {
                    sb.Append(t.ToString("X2"));
                }
                return sb.ToString();
            }
        }
    }

Artık sıra cache'i projemizde kullanmaya geldi. yazdığımız interceptor'ı projede hangi metodu cache'e atmak istiyorsak metodun başına attribute olarak ekleyip kaç dakikalık cache'de tutacağını belirteceğiz.

        [CacheAspect(MethodCacheDurationInMinute = 120)] //120 dakikalığına cachelemesini söylüyoruz
        public List<FooModel> Foo(FooRequest reqModel)
        {
            var fooManager=new FooManager();
            return fooManager.GetFooList();
        }

 

Kullanım olarak oldukç basit ve projede hangi metotta istersek aynı yukarıda olduğu gibi attribute olarak projemize ekleyebiliriz. Yazının başında da belirttiğim gibi cache bir çok zaman hayat kurtarır ve yabana atılmaması gereken bir konudur.

 

Builder Design Pattern

Design pattern'leri spagetti kodlara veya tekrar eden kod bloklarına çözümler olarak ortaya çıkmışlardı. Builder Design Pattern creational patterns kategorisi altında bulunan patternlerden birisidir ve kısaca basit objeler kullanarak complex objeler inşa etmede kullanılır diyebiliriz. 

 

Builder'ı DTO yani data transfer object class'larını örnek olarak düşünürsek, bu objeler birden fazla objenin birleşmesinden oluşurlar ve genelde complex objelerdir. Builder pattern'nini de base'de bir obje var ve bu objeye ait alt property'leri oluştururken her bir özellik için ayrı build metotları yazmamız gerekir. Builder design pattern'i bu gibi durumlarda kullanabilecegimiz bir pattern olarak dusunebiliriz. Başka bir deyişle; runtime da yaratılması gereken complex objelerinizin olduğu bir projeniz varsa bu pattern'i kullanmak güzel bir seçim olabilir veya adım adım obje yaratma işlemlerini yönetmek istediğiniz bir durum var ise yine bu pattern'i kullanabiliriz.

Builder pattern'i 4 ana yapıdan oluşur;

  • Product  : Build edilen obje,
  • Builder   : Product'ı build eden abstract yapı,
  • Concrete Builder : Builder nesnesinin abstract yapıyı kullanarak aynı yapıdaki farklı ürünlerin build edilmesini sağlayan yapı,
  • Director  : Kendisine verilen Builder'ı property'lerini kullanarak ürünü inşa eden yapı.

Örnek bir proje üzerinden Builder Design Pattern'i implement etmeye çalışalım. Apple fabrikasında Iphone modelleri üretimi yapan bir senaryo yazalım ve bunu pattern'imizi uygulayarak yapmaya çalışalım.

Öncelikle ürün class'ımızı oluşturalım. Iphone'un farklı modellerini (6, 6S, SE, vs.) ürettiğimizi düşünelim ve constractor seviyesinde hangi modeli ürettiğimiz bilgisini verelim.

    //Product'ımız   
    public class Iphone
    {
        string phoneType;
        public Iphone(string phoneType)
        {
            this.phoneType = phoneType;
        }
    }

Şimdi ise Builder gibi davranan abstract class'ımızı oluşturalım. Bu class üretimde kullanacağımız abstract üyeler içerecektir. Icerisinde BuildOS ve BuildDevice adında iki abstract metot ve hangi iphone modelini üreteceği bilgisinin olacağı Iphone tipinde olan obje olacaktır.

    //abstract Builder nesnemiz   
    public abstract class IphoneBuilder
    {
        public abstract void BuildOS();

        public abstract void BuildDevice();

        Iphone IphoneType { get; }
    }

Sırada Builder class'larını geliştirme var. 6sBuilder ve SeBuilder adında iki tane iphone modeli için geliştirme yapalım.

SeBuilder

    // SE modeli için kullanacağımız Concrete Builder class'ımız
    public class SeBuilder : IphoneBuilder
    {
        Iphone device;

        public SeBuilder()
        {
            device = new Iphone("Se");
        }

        public override void BuildOS()
        {
            //TODO
        }

        public override void BuildDevice()
        {
            //TODO
        }

        public Iphone IphoneType
        {
            get { return device; }
        }
    }

_6sBuilder

   // 6S modeli için kullanacağımız Concrete Builder class'ımız
   public class _6SBuilder : IphoneBuilder
    {
        Iphone device;

        public _6SBuilder()
        {
            device = new Iphone("6S");
        }

        public override void BuildOS()
        {
            //TODO
        }

        public override void BuildDevice()
        {
            //TODO
        }

        public Iphone IphoneType
        {
            get { return device; }
        }
    }

Şimdi sırada üretim var. Manufacturer adında üretici class'ımızı oluşturalım.

   //Director class'ımız
   public class Manufacturer
    {
        public void Build(IphoneBuilder iphoneBuilder)
        {
            iphoneBuilder.BuildDevice();

            iphoneBuilder.BuildOS();
        }
    }

Sıra yaptığımız implementasyonu kullanmaya geldi. 

	public static void Main()
	{
		 Manufacturer manufacturer = new Manufacturer();

         _6SBuilder _6s = new _6SBuilder();

         manufacturer.Build(_6s);
	}

Main fonksiyon içerisinde Manufacturer'ı kullanarak _6s modelini build ettik. Builder pattern'nini elimizden geldiğince projemize entegre ettik ve diğer iphone ürünleri için de aynı akışı kullanarak ürünlerimizi Build edebiliriz.

Builder pattern'i factory ve abstract factory pattern'leri ile birçok yönden benzerlikler göstermektedir. Hangisini kullanmalıyım diye tereddütte kaldığınız durumlarda, complex objeler yaratıyorsanız Builder'ı, tek bir obje yaratmak istiyorsanız da factory veya abstract factory design pattern'i tercih edebilirsiniz.

Quartz .Net Nedir Nasıl Kullanılır ?

Quartz.Net, geliştirdiğiniz web tabanlı projelerde scheduled task yani zamana dayalı planlı görevler çalıştırmak istediğinizde çok kolay bir şekilde implement edip kullanabileceğiniz open source bir kütüphanedir. Aslında bir çok kişi bu tarz işlemleri yapmak için bir console uygulaması geliştirir ve windows server'da task schedular'a bu uygulamayı ne zaman çalıştıracağını söyler ve iş biter. Ancak gerek sunucuya erişimin kısıtlı olduğu durumlarda, gerekse ikinci bir uygulama geliştirmenin yol açtığı efor düşündüğünde quartz.net daha avantajlı gibi görünüyor.

Kullanım olarak iki aşamadan oluşuyor diyebiliriz,

  1. Çalışacak olan Job'ı belirleme,
  2. Hangi zaman aralıklarında çalışacağı bilgisi set edilir.

Quartz 3 ana bileşenden oluşur, job, trigger, schedular. Job yapılacak olan iştir. Trigger job'ın ne zaman, ne şekilde tetikleneceği emrini veren & tetikleyen yapıdır. Job ve Trigger ikisi birlikte schedular'a register olurlar ve kısaca çalışma şekli olarak; job schedular üzerinden trigger tarafından çalıştırılır diyebiliriz.

Örnek bir case üzerinden ilerleyelim; bir banka web service projemiz olsun ve müşterilere her gün saat 13:30 da push notification yollayan bir kampanyalar job'ı geliştirelim.

1. Quartz.Net Kurulumu

Kurulum için projenize sağ tıklayıp Manage Nuget Packages dedikten sonra aşağıdaki gibi nuget'ten projeyi install edebiliriz.

 

2. Job'ı Oluşturma

Bunun için ilk olarak IJob interface'inden implement olan CampaignPushJob adında bir class oluşturalım. IJob ile birlikte class'ımız Execute adında bir metoda sahip olur ve job çalışırken aslında bu metot içerisindeki akış çalışır diyebiliriz.

    public class CampaignPushJob : IJob
    {
        public void Execute(IJobExecutionContext context)
        {
            var userRepository = new UserRepository();
            var userList = userRepository.GetPNUserList();

            var pushNotText = "Size özel konut kredisi teklifleri.";

            var pnService = new PnService();
            pnService.SendPush(pushNotText, userList);
        }
    }

Örnekte PnService adında bir service'imiz olduğunu varsayalım ve SendPush metodu ile kullanıcılara notification gönderiliyor olsun.

3. Schedular Oluşturma

Bu adımda CampaignPushJobScheduler adında içerisinde ne zaman ve neyi çalıştıracağı bilgisini verdiğimiz schedular'ımızı yazıyoruz. JobBuilder.Create<CampaignPushJob>().Build() satırında da görüldüğü gibi bizden bir adet IJob interface'ini implement etmiş class istemekte. Bizde bir üstte yazdığımız CampaignPushJob'ını buraya set ediyoruz. Ne zaman çalıştıracağı bilgisini ise TriggerBuilder'ı initialize ederken set edeceğiz. Örneğimizde her gün saat 13:30 da çalışmasını istediğimiz bir job çalıştırmak istediğimiz için TriggerBuilder içerisinde aşağıdaki gibi bir tanımlama yaptık.

    public class CampaignPushJobScheduler
    {
        public static void Start()
        {
            IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
            scheduler.Start();

            IJobDetail job = JobBuilder.Create<CampaignPushJob>().Build();

            ITrigger trigger = TriggerBuilder.Create()
                .WithDailyTimeIntervalSchedule
                  (s =>
                     s
                    .OnEveryDay() //hergün çalışacağı bilgisi
                    .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(13, 30)) //hergün hangi saatte çalışacağı bilgisi
                  )
                .Build();

            scheduler.ScheduleJob(job, trigger);
        }
    }

4. Schedular Start Etme

Bu adıma kadar job, schedular ve trigger bilgilerimizi oluşturduk ve artık uygulama ayağa kalktıktan sonra schedular'ı start etmemiz gerekiyor. Geliştirmekte olduğumuz proje bir Asp.Net projesi olduğundan bu işlemi kolayca Global.asax dosyası içerisinde bulunan Application_Start metodunda yapabiliriz. Tek yapmamız gereken CampaignPushJobScheduler'ın içerisinde bulunan Start metodunu çağırmak.

    public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            CampaignPushJobScheduler.Start();
        }
    }

Quartz.Net implement etme işlemimiz bu kadar. Artık web projemiz bir kere ayağa kalktıktan sonra set ettiğimiz değerler doğrultusunda çalışacaktır.

Quartz schedular işlemler için .Net tarafında kullanılabilecek en yaygın kütüphanelerden bir tanesidir. Örnekte her ne kadar az kullanmış olsak da trigger time özelliğiyle ilgili kullanıcılara bir çok seçenek sunmakta. Bunlarla ilgili ayrıntılı bilgiyi bu linkte bulabilirsiniz.

SOLID Yazılım Süreçlerini Yavaşlatıyor Mu ?

Geçtiğimiz haftalarda  SOLID yazı serisini sonlandırdık ve sırasıyla

konularından bahsettik.

İlk defa SOLID'i araştırırken bir çok blog yazlım forum sitesi vs. dolaştım ve yapılan yorumları okuma fırsatım olmuştu. Bu yorumlardan en çok yaygın olanı kabaca şöyle bir şey "ya tamamda ne gerek var bunca şeye ?.. SOLID hem beni hemde yazdığım kodu yavaşlatıyor..." vs. şeklinde yorumlar yapılmıştı. Doğrusu en başlarda bende buna benzer cümleler kurmadım değil :)

SOLID prensiplerinin asıl amacı Separation of Concerns veya türkçe deyimiyle farklı kavramların, işlerin birbirinden ayrılması ilkesini High Cohesion & Low Coupling yoluyla geliştirmek. Prensipleri anlattığımız ilk yazıda şuna benzer bir cümle kullanmıştım "asıl amaç sonradan kolay müdahale edilebilir, yeni modüllerin kolay entegre edilebildiği core code'a çok fazla dokunmadan yazılımlar geliştirmek..." ve aslında bu cümle 5 temel prensiple bizlere anlatılıyor. İlk bakışta bu prensiplerin uygulaması zahmetli gibi geliyor olabilir ancak bunu ileriye dönük iyi bir yatırım olarak görmeliyiz ve yazılım geliştirme sürecini while(true) {develop}şeklinde sonsuz bir döngü olarak bakmalıyız.

SOLID prensiplerine uyalım derken bazı istenmeyen sonuçlarortaya çıkarmamızda mümkün;

  • Çok fazla abstraction ve interface,
  • İstenmeyen Concrete class'lar,
  • Gerektiğidnen fazla layer oluşabilme itimali,
  • Kalıtım ağacıdnaki derinliği gereknden fazla artırma,
  • Yo-yo problemi,
  • vs...

SOLID prensiplerinin gelişi güzel uygulanması bu ve benzeri durumların olmasına neden oalbilir ancak geliştirme yaparken arada bir geriye gelip geniş açıdan bakmakta fayda var çünkü bazen oop nin dibine vurmaya çalışırken farkında olmadan saçmalamışta olabiliriz :) . Tabi burda da devreye iyi deneyime sahip, çeşitli projelerde görev almış vizyon sahibi bir takım liderine gerek var ki code-review sırasında nokta atışı yaparak yanlış veya gereksiz olan yerleri saptayıp düzeltebilme fırsatınız olsun.

SOLID yazılım süreçlerini yavaşlatıyor mu ? sorusuna geri dönecek olursak, yukarıda da bahsettiğimiz üzre çok separation of concern'ü düşünerek yapacağımız işleri çoklu katmanalra ayırarak böyle bir duruma sebebiyet verme ihtimalimiz azda olsa var. Diğer bir deyişle tek bir metot çağrısı yaparak çözebileceğimiz bir sorunu birden fazla instance alarak metot çağrısı yapmaya zorlandığımız durumlar ortaya çıkabilir ve bu da geliştirme süresini uzatabilir. Ancak SOLID prensiplerini hangi projeye uygulanması gerektiğine karar vermekte bir o kadar önemli çünkü small diyebileceğimiz projeler için dibine kadar SOLID kastırmak çokta şart değildir hani. Bunun yerine enterprise veya işlevselliği çok olan büüyk projerlerde SOLID prensiplerini uygulamaya çalışmalıyız ve unutmamalıyız ki SOLID'e uygun geliştirmeler yaparak günü kurtarmıyoruz bunu ileriye dönük bir yatırım olarak görmeliyiz ve asıl faydalarını projenin ilerleyen fazlarında görüyor olacağız.

Ben kendi projelerimde nasıl ilerliyorum diye soracak olursak, mümkün olduğunda separation of concern'e odaklanmaya çalışıyorum ve ona uygun ilerleyecek sade-temiz bir design pattern seçmeye çalışırım. Çok fazla abstraction'dan dolayı performans sorunları olacağını düşündüğüm yerler varsa da yaygın kullanılan bir runtime performans tool'u kullanarak testler yapıp sorunu saptamaya odaklanırım.

 

Sonuç olarak SOLID yazılım süreçlerini yavaşlatıyor mu sorusuna bence en uygun cevap, SOLID'i ne kadar doğru zamanda doğru yerde kullandığınızla ilgili. Eğer prensiplere tamamiyle hakimseniz ve doğru projede uyguladığınızı düşünüyorsanız kesinlikle yazılım süreçlerini yavaşlatmıyor aksine hızlandırıyor. Şuan için farkında olmasanız bile ileriye dönük size birçok katkı sağlayacağı noktalar olacaktır. Eğer çok gereksiz bir projede hiç ihtiyaç yokken SOLID'in dibine vurmakta tavsiye edilen bir durum olmayacaktır, çok büyük bir ihtimalle geliştirme sırasında gereksiz zaman kaybına yol açacaktır.

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.