Caner Tosuner

Leave your code better than you found it

Server Side Bir Projede Olmazsa Olmazlar || Olursa Güzel Olurlar

Server-side geliştirme yaparken projemizde olursa olmazsa olmaz OR güzel olur diyebileceğimiz bazı modüller&özellikler vardır ve bu özellikler bir çok kitap yazarı, ünlü blogger veya eğitmenler tarafından kabul görmüş özelliklerdir. Yazının başlığına bakacak olursak "Olmazsa Olmazlar" şeklinde ancak yazılım denen şey tabi ki "it depends on the business" duruma göre,ihtiyaca göre geliştirilen bir şey ve "Olmazsa Olmaz" dan kasıt bu zamana kadar hem kendi okuduğum çeşitli makalelerde vurgulanan hemde geliştirmiş olduğum çeşitli projelerde deneyimlediklerimden yola çıkarak 5 farklı önemli özellik var ki bu özellikler gerçekten kurumsal veya büyük çaplı bir projede farkında olarak veya olmadan size oldukça fazla fayda sağlamaktadır.

Bu 5 başlığı sırasıyla yazacak olursak;

  1. Security,
  2. Exception Handling,
  3. Logging,
  4. Response Consistency,
  5. Development Environment

 

1- Security

Bazı bilgiler vardır uygulamanız için veya uygulamayı kullanan kişiler için çok ama çok önemlidir. Örneğin; tckn bilgisi, banka hesap numarası, userId bilgisi, username-password bilgisi vs. bu gibi bilgilere hiç kimsenin ulaşmasını istemeyiz. Günümüzde fiddler, wireshark vb. tool'ları kullanarak client-server arasındaki gidip gelen http paketleri rahatlıkla dinlenmekte ve kötü niyetli bazı kişiler araya girerek giden data'da bulunan tckn,accounNumber,amount vs gibi alanları değiştirip bir çeşit dolandırıcılık yapabilmekteler. Genel de çözüm olarak "ya https yaparız abi hiç bişey olmaz.." şeklinde cümleler kurulup service'i https olarak dışarıya açarlar ve bu tehlikeyi önlediklerini sanarlar ancak artık https bile çeşitli yollar denenerek decrypte edilebilmekte.

Security'den kasıt aslında token based authentication vs. değilde request&response gelip giderken data için uygulanabilecek security. Tabiki de service'e gelecek olan client'ları belli authentication ve authorization kontrollerinden geçirdikten sonra içeriye almak gibi bir çok yöntem mevcut ancak data-trasnfer sırasında belli bazı önlemler alarak projenizin güvenliğini biraz daha atırabilirsiniz. Bunun için çeşitli yöntemler bulunmakta. Property bazında istenilen değeri şifleme veya maskeleme. Örneğin; UserLoginRequest adında bir modeliniz var ve içerisinde bulunan UserName, Password string alanlarını şifreleyebilirsiniz. Diğer bir yöntem ise endpoint'e gelen request ve endpoint'ten çıkan response modellerinizin tamamını şifreleyerek data transferini sağlayabilirsiniz ki bu daha base-oriented bir çözüm gibi duruyor. Örnek bir response örneği aşağıdaki gibi olabilir.

public class BaseResponse
{
	public object Data {get;set;} // şifrelenmiş bir şekilde endpoint'in return ettiği response Data içerisinde gönderilebilir
	public bool IsCrypted {get;set;} her endpoint şifrelenmiş şekilde response dönmeyebilir, client'ı bilgilendirmek adına bir bool alan tutabilirsiniz
}

Not: Checksum'da unutulmaması gereken diğer bir yöntemdir. Checksum kullanarak gelip giden data uzunluğu belli bir algoritma ile şifrelenip çift taraflı kontrol uygulanarak da güvenlik sağlamak işinizi baya bi kolaylaştırabilir.

 

2- Exception Handling

"Hatasız kod olmaz.."

Exception handling her bir proje için oldukça önemli bir konudur. Hatasız kul olmayacağı gibi hatasız kod da olmaz. Exception fırlatabileceğiniz düşündüğümüz yerleri zaten önceden tedbirini alıp handle ediyoruzdur ama asıl önemli olan beklenmedik hataları nasıl handle edeceğimizdir. En yaygın hatalardan biri olan null reference exception hemen hemen bütün projelerde başımıza gelmiş bir exception türüdür ve projeniz bu hatayı fırlattığında client'a http500 internal server error dönecektir. Peki bu tür hataları handle etmek için ne yapmak gerekir ?? Bir çok projede olduğu gibi her yere try catch koyarak kodunuzu spaghetti code haline getirip yönetilmesi zor bir proje haline getirmek heralde yapılabilecek en kolay çözüm. Öyle projeler var ki denk geldiğim adam try catch e bile güvenmeyip iç içe 2 try catch yazmış.

Bu ve benzeri durumlardan sakınmak gerekir. Open-source dünya ile birlikte exception handling için bir çok kütüphaneler-yöntemler mevcut. Projeniz için her yerde kullanabileceğiniz küçük küçük interceptor'lar yazarak exception handling'inizi tek bir yerden yönetebilir ve projenize reuseable fonksiyonaliteler kazandırabilirsiniz. Aspect-Oriented Programming anlayışı aslında en güzel örnek Nuget'te bulunan çeşitli kütüphaneler ile bir kaç dakika içerisinde bu modülleri projenize ekleyebilirsiniz.

Bu gibi yapılar ile çok kolay şekilde projenizi yönetebilir ve tek bir yerden kontrol edilebilir kodlar geliştirebilirsiniz. Böylelikle yarın bir gün exception handling ile ilgili değişiklikler yapmak istediğinizde bu işlem saatlerinizi almak yerine bir kaç dakikada yapabileceğiniz bir geliştirme olacaktır.

 

3- Logging

"Log, uğrunda ölen olmadıktan sonra log değildir.."

Log bir projede her şeydir. Küçük çaplı projeleri baz almazsak log yapısı büyük çapta olan projeler için oldukça hayati önem taşır. Öyle bir an gelir ki log'a attığınız küçücük bir byte size dünyaları kazandırır. Tabi log işlemi yaparken kayda değer şeyler logluyor olmak oldukça önemlidir. Özellikle dışa bağımlı çalıştığınız projelerde yani external bir service call işlemi olan yapılarda gelip giden request & response'ları context halinde logluyor olmak oldukça kolaylık sağlar. Gerek db ye gerekse file log yaparken bütün bu işemleri uçtan uca logluyor olmak ve aralarında ilişkileri bir unique identifier kullanarak loglamak gerekir. Örnek verecek olursak "getAccountList" adında geliştirdiğiniz bir endpoint var ve client'lar bu endpoint'e request atarak hesap listesini alıyor sizde client bu endpoint'e geldiğinde arka tarafta başka bir endpoint'e gidip hesap listesi ile ilgili farklı service call işlemleri yapıyorsunuz. Geliştirmiş olduğunuz log yapısından beklenen, belli bir context olarak uçtan uca hem client'ın request'ini hemde sizin server-side da yapmış olduğunuz diğer servis çağrımlarını logluyor olması gerekir. Bu size şunu kazandırır production'da olan bir müşteri şöyle bir şikayetle müşteri hizmetlerine şikayet bırakabilir "ben saat 11:38'den hesaplarım sayfasını görüntüleyemiyorum, sayfa boş geliyor..." bu gibi durumlarda hemen müşteri numarasından yola çıkarak log'ları kontrol ettiğinizde sorunun sizin geliştirdiğiniz service katmanından değilde dışa bağlandığınız service'den kaynaklandığını kolaylıkla görebilirsiniz. Bu logları internal ve external olarak ayırmakta fayda olabilir zira proje müdürü sizden ilerleyen zamanlarda yüzdelik olarak success ve fail oranlarını isteyebilir yani aslında tutmuş olduğunuz log'lardan yola çıkılarak proje yöneticileri analiz dahi yapmak isteyebilirler. Loglama ile ilgili örnek yazıları buradan göz atabilirsiniz.

Not: Log için çok çeşitli open source kaynaklar bulunmakta. Geliştirmelerinizi loosely coupled olacak şekilde yapıyor olursanız ilerde log yapınızla ilgili kolay değişiklik yapabilir, farklı log yapılarına kolay geçişler sağlayabilirsiniz.

 

4- Response Consistency

Response tutarsızlığı herhalde Türkiye de geliştirilen server-side projelerde ki en büyük sorunlardan biri olabilir. Bir çok defa dış servislere bağlı proje geliştirmiş biri olarak aynı namespace altında olup farklı farklı response'lar dönen durumlarla epeyce bir karşılaştım. Peki projenizde bulunan end-point'lerin döndükleri response'ların tutarlı olması neden önemlidir ? Bu sorunun cevabı hem server-side'da bulunan proje açısından hemde client açısından büyük öneme sahiptir.

Server-side açısından; öncelikle uygulamada exception-handling, logging, caching vs. gibi özellikleri geliştirirken endpoint'lerinizin return ettikleri response'ların bir BaseResponse.cs'den türüyor olması tercih edildiğinde bu gibi operasyonlar için yapılması gereken geliştirmelere harcanan eforu hemen hemen yarı yarıya azaltabilirsiniz. Yapmış olduğunuz BaseResponse.cs içerisinde Error case'leri için de bir property'iniz olması gerekir çünkü client'a farklı akışları olan error-exception mesajları dönebilirsiniz. 

Örnek BaseResponse.cs

public class BaseResponse
{
	public object Data {get;set;} // endpoint'in return ettiği response
	public ErrorModel Error {get;set;} // endpoint'in return ettiği error model
}

Client açısından; yukarıda ki BaseResponse örneğinden yola çıkacak olursak client tarafta geliştirilen network-layer da gelen response açılarak endpoint'in gönderdiği Data içerisinde bulunan gerçek response alınabilir ve sonrasında alt katmanlara gönderilip işlenebilir ve bunu yapabiliyor olmak client yazan arkadaşlar bilirler ki büyük bir nimettir. Servisten hata geldiğinde ise ErrorModel tipinde olan Error property'imiz içerisinde client'a gelen hata mesajı yine network katmanında alınıp base'den hata yönetimi yapılabilen bir layer'a gönderilip gerek pop-up ile hata mesajını gösterme gerekse kullanıcının o hata türünü aldığında uygulamada nasıl bir akışa yönlendirilmesi gibi işlemler yapılmasına kolaylık sağlanabilir.

 

5- Farklı Development Environment

"Dev de çalışıyor ama Test ortamında çalışmıyor..."

Development environment oldukça önemli bir diğer konudur. Farklı ortamlarda geliştirme yapıyor olmak biz developer'lar için eksik konfigurasyon vs gibi durumlarda başımızı ağrıtan bir durum olsada aslında çok önemli bir konudur. Bilindiği üzre Production seviyesinde geliştirme yapmak mümkün değil ama geliştirmekte olduğumuz ürünü prod'a aldığımızda sorunsuz çalışması istenir. Projenizi geliştirirken o an geliştirdiğiniz ortam, test işlemlerinin yapıldığı ortam ve production ortamlarının her birinde farklı konfigurasyonların olduğu server'lar sql'ler vs. bulunur. Geliştirme yaparken bu ortamların yönetimi ve konfigürasyonu oldukça basit ve ortamlar arası geçiş sorunsuz 1-2 hareketle geçilebiliyor olması beklenir. Peki bu development ortamları nelerdir ?

Development
Development ortamı ismindende anlaşıldığı üzre developer'ların geliştirme yaptığı ortam. Bu ortam developer istediğini yapmakta özgürdür diyebiliriz. Developer kritik kararlar alıp anlık değişiklikleri projede deneyebilir çünkü bu ortam onun kendi bireysel ortamıdır diyebiliriz.

Integration
Development ortamında bireysel olarak yapılan geliştirmelerin ortak bir branch'e commit'lenip bütün geliştirmelerin ortak bir alanda gözlemlendiği alan diyebiliriz. Kısaca bu ortamın amacı yapılan işlerin stage'ing den önce combine edilip developer'ların saptayabildiği bir hata varsa hemen fix edilip ürünü görücüye çıkartma şeklinde tanımlayabiliriz.

Staging
Staging Production'dan önceki ortamdır ve sahip olduğu özellikler itibari ile production'ın ile bire bir aynı olması beklenir. Örneğin benzer özelliklere sahip sunucular, sql server'lar, canlıda olan database'in bir gün öncesinin verilerine sahip bir database vs. gibi. Staging ortamına bir çok yerde Pre-Production da denilmekte.

Production
Son adım olarak da Production ortamı artık geliştirmiş olduğunuz projeyi canlıya aldığınız ve son kullanıcının erişimine açtığınız yerdir. Prod'a gelene kadar ki ortamlarda yapılan geliştirmeler sorunsuz-planlı bir şekilde yapıldığı taktirde testler sonucunda ürün prod'a alınır.

 

Yazının başında da belirtiğim gibi "it depends on the business" yani işe bağlı projeye bağlı. Olursa güzel olur diyebileceğimiz bu modülleri projelerinizde farklı şekillerde düşünebilir entegre edebilirsiniz.

Fluent NHibernate Nedir ve Kullanımı

ORM Nedir ?

ORM (object relational mappers) uygulamalarınızda CRUD (Create, Read, Update, and Delete) işlemleri için basit bir şekilde data'ya erişimlerimizi sağlayan yapılardır. Çeşitli ORM framework'leri uzun zamandan beri data-model arasındaki veri alış verişini sağlamak için kullanılmakta ve ORM ler sql scriptleri execute etmeden CRUD işlemlerini doğrudan yapabilmemiz için kod yazmamıza olanak sağlarlar. Özetle, ORM kullanarak uygulamalarımızdaki data-model ilişkisini(database modellemesini) object-model ilişkisine dönüştürebiliriz.

Neden Fluent NHibernate ?

Klasık NHinernate, database de bulunan her bir tablo için class mapping bilgilerini .hbm uzantılı  XML formatındaki dosyalarda saklıyordu ve aslına bakılırsa biraz zahmetli bir işlemdi. Fluent NHibernate'de ise artık .hbm uzantılı mapping bilgilerini içeren XML formatındaki dosyalar kaldırıldı ve bu işlem code-behind'a taşındı ve diğer ORM'lere göre daha hızlı olduğu kabul edilir.

Kısaca özelliklerini sıralamak gerekirse;

  • Easy data-access and data-mapping,
  • Compile-time name- and type-safety,
  • IntelliSense to show you which fluent methods are available at any point,
  • Automapper impl.

Fluent NHibernate ile kolayca Linq sorguları yapabilir ve böylelikle Fluent bir Api oluşturmada kullanabiliriz. Şimdi ise projemize nasıl Fluent NHibernate entegre edip geliştirme yapabiliriz bunu incelicez.

Fluent NHibernate Kurulum

İlk olarak Visual Studio üzerinde yeni bir Console Application projesi açalım. Daha sonra Nuget üzerinden Fluent NHibernate referanslarını projemize indirip kuralım. Install-Package FluentNHibernate

Database Tablo Oluşturulması

Fluent NHibernate kullanabilmek için öncelikle bir database'e ihtiyacımız var. SQL üzerinde FNH_Db adında adında bir database'imiz olsun ve bu db'ye ait aşağıda bulunduğu gibi User adında bir tablo oluşturalım.

CREATE TABLE dbo.Users  
(
    Id int PRIMARY KEY NOT NULL,  
    Name varchar(25) NOT NULL,  
    SurName varchar(25) NOT NULL
)  

User Tablosuna Karşılık Gelen User.cs Oluşturulması

Yukarıda tanımlamış olduğumuz User tablosuna karşılık gelen User.cs aşağıdaki gibi oluşturuyoruz.

public class User
   {
       public virtual int Id { get; set; }
       public virtual string Name { get; set; }
       public virtual string SurName { get; set; }
   }

Mapping Class'ının Oluşturulması

Database tablomuz ve buna karşılık gelen class'ımız hazır ancak halen User objesinde bulunan hangi alan tabloda bulunan hangi alana karşılık geliyor bunu belirtmedik. Bunun için Fluent NHibernate'e ait olan ClassMap adında bir class bulunmakta ve gerekli mapping işlemlerini yapacağımız class ClassMap<T>'den inherit olmuş şekilde oluşturulacak. Burda ki T bizim User class'ımız oluyor.

UserMap.cs adındaki mapping class'ı aşağıdaki gibi olacak şekilde tanımlıyoruz.

   public class UserMap : ClassMap<User>
   {
       public UserMap()
       {
           Id(x => x.Id);
           Map(x => x.Name);
           Map(x => x.SurName);
           Table("User");
       }
   }

DB Connection Sağlanması

Şimdiki işlem ise Fluent Nhibernate kullanarak yukarıda tanımlamış olduğumuz FNH_Db isimli database'e bağlanmak. Bunun için OpenSession adında bir metot oluşturalım.

       public static ISession OpenSession()
       {
           string connectionString = "FNH_Db conn string";
           var sessionFactory = Fluently.Configure()
               .Database(MsSqlConfiguration.MsSql2012
                .ConnectionString(connectionString).ShowSql()
               )
               .Mappings(m =>m.FluentMappings.AddFromAssemblyOf<User>())
               .ExposeConfiguration(cfg => new SchemaExport(cfg)
               .Create(false, false))
               .BuildSessionFactory();
           return sessionFactory.OpenSession();
       }

Tablo'da Bulunan Verileri Alma

Aşağıda yer alan kod bloğu ise db de bulunan tablodaki verileri query etmemizi sağlar.

using (var session = OpenSession())
     {
          var users = session.Query<User>().ToList();
     }

Yukarıda bulunan geliştirmeleri yaptığınızda db bulunan bir proje geliştirmede basitçe connection sağlayarak data-model ilişkisini kurabilirsiniz.  

Not: Yazıyı basit tutmak adına herhangi bir design pattern den bahsetmedim ancak Db olan yerde Crud işlemleri vardır, Crud olan yerde Ado.Net veya ORM vardır, ORM varsa Fluent NHibernate vardır ve Fluent NHibernate de Repository Design Pattern ile birlikte fıstıklı baklava gibi gider :) 

Fluent NHibernate ile ilgili daha detaylı bilgi almak ve geliştirmeleri takip edebilmek için bu linke göz atabilirsiniz.

IComparable Interface'ini Kullanarak Sıralama İşlemi

Primitive type'lar için sıralama işlemini bir şekilde list.Sort() vs gibi metotlar kullanarak yapabiliyoruz. Peki kendi yazdığımız objeler için bunu nasıl yaparız ? .Net tarafında iki objeyi belli field&property'lere göre sıralamamızı veya compare etmemizi sağlayan IComparable<T> interface'i bulunmakta. Bu interface ile List<T> tipidne tanımladığımız değişkenimize bizim belirttiğimiz kritere göre sıralama özelliği kazandırmış oluyoruz. Örnek bir case üzerinden gidelim; Rectangle adında bir objemiz olsun içerisinde Rectangle objeleri bulunan bir array'imiz olsun bu arrayde bulunan objeleri alanı en küçük olandan büyük olana doğru sıralayalım. İlk olarak Rectangle objemizi aşağıdaki gibi tanımlayalım.

    public class Rectangle 
    {
        public double Length { get; set; }
        public double Breadth { get; set; }

        public double Area
        {
            get
            {
                return Length * Breadth;
            }
        }
    }

Şimdi ise üsttede belirtiğimiz gibi dikdörtgen objelerini küçük olandan büyük olana doğru array'de sıralayalım. Bu işlem yapabilmenin bir yolu IComparable interface'ini kullanmak. Rectangle objemizi bu interface'den implement etmek. Implementasyon sonucunda Rectangle class'ı CompareTo adında bir metota sahip olur. Bu metot aracılığıyla compare işlemi yaparak array'imizi sıralayabiliriz. Rectangle class'ımızın implementasyon sonrasındaki görünümü aşağıdaki gibi olacaktır.

    public class Rectangle : IComparable<Rectangle>
    {
        public double Length { get; set; }
        public double Breadth { get; set; }

        public double Area
        {
            get
            {
                return Length * Breadth;
            }
        }

        public int CompareTo(Rectangle other)
        {
            if (this.Area == other.Area)
            {
                return this.Area.CompareTo(other.Area);
            }
            return other.Area.CompareTo(this.Area);
        }
        public override string ToString()
        {
            return this.Area.ToString();
        }
    }

 Şimdi ise sırada yazdığımız bu kodu test etme işlemi var. 

        public static void Main()
        {
            var list = new List<Rectangle>
            {
                new Rectangle() {Length = 7, Breadth = 3},
                new Rectangle() {Length = 5, Breadth = 1},
                new Rectangle() {Length = 9, Breadth = 4},
                new Rectangle() {Length = 2, Breadth = 7},
                new Rectangle() {Length = 3, Breadth = 5}
            };

            list.Sort();//Sort metodu ile array'i belirtmiş olduğumuz kritere göre sıralıyoruz

            foreach (var element in list)
            {
                Console.WriteLine(element);
            }
        }

Projeyi çalıştırdığımızda aşağıdaki gibi bir ekran elde etmiş olacağız.

36
21
15
14
5

 

Servisten Gelmeli || Client Yapmalı

Servisten mi gelmeli yoksa client mı yapmalı ?.. 

Hem server tarafta hem de client tarafta geliştirme yapan biri olarak kolayca söyleyebilirim ki bu iki soru yazılımla uğraşan kişilerin sıkça dile getirdiği sorulardan dır. Özellikle 10-12 kişilik development ekiplerinin bulunduğu projelerde client ve server tarafını geliştiren kişilerin genelde scrum'larda birbirlerine karşı dile getirirler bu cümleleri. Jira'da yeni bir bug açılır analist arkadaş genelde platform belirtilmişse direkt olarak bug'ı o platformu geliştiren kişiye assign eder. Sonrasında o arkadaş ilgili bug'ın altına server-side geliştirmesini yapan arkadaşlardan birini mention'layarak şu sihirli yorumu yazar "servisten gelmeli veya servis yapmalı" veya bunun tam tersi bug server-side geliştirme yapan kişiye açılır o yorum yazarak "client yapmalı" vs şeklinde bir cümle yazar. İşte taht kavgaları tamda o anda başlar.

Aslında bu gereksiz kavganın yaşanmasındaki sebeplerin başında gerekli geliştirme hem client hem de server tarafta da yapılabiliyordur ancak developer geliştirmeyi üzerine almak istemediğinden diğer tarafa atmak ister. Bir kaç ördenk case den yola çıkarak bu soruna çözüm bulabiliriz aslında. 

SOA gerçeklerinden yola çıkacak olursak yönetimin service tarafından yapılabiliyor olması bir projede oldukça önemlidir. Örneğin bir mobil proje geliştiriyorsunuz ve username password yanlış olduğunda kullanıcıya gösterdiğiniz bir uyarı metni var "Girmiş olduğunuz bilgiler hatalıdır." şeklinde. Bu geliştirmeyi client tarafta yaptığınızı düşünelim ve uygulama marketteyken müşteri dedi ki "ya biz şu metni Girmiş olduğunuz bilgiler hatalıdır. Lütfen tekrar deneyiniz." olarak değiştirelim ve bunu hemen 1-2 saat içerisinde yapalım. Proje müdür OK verdi ve geliştirmeyi yapmak için hem ios hem android projelerinde metni değiştirdiniz tekrardan markete submit ettiniz. Uygulamanın market onay sürecinden geçmesi özellikle ios tarafında bazen 1 hafta dahi sürebiliyor. Ne oldu peki müşteri 1-2 saatte halledelim dedi ancak siz 1 haftada halledebildiniz. Eğer client'cı arkadaşların dediği gibi "geliştirmeyi servis yapsın" cümlesini dinleyerek yola çıksaydık ve geliştme service tarafında yapılmış olsaydı hemen ilgili metin değişikliğini db'de bulunan tablodan güncelleyip dakikalar içerisinde isteği yerine getirmiş olacaktık. 

Tabi client'ın her dediği yapıldığında da bir süre sonra servisci arkadaşlarda şu cümleyi ister sitemez duyabiliyoruz "e uygulamayı biz yazıyoz zaten client napıyo ki.." Bu gibi durumlar içinde aslında çok arada kalınan bir case ise ve client'ın yapması daha kolay bir durum ise client'ta yapmak daha doğru olacaktır. Örnek olarak karşılaştığım bir case olmuştu. Uygulamada ürün detay sayfasının bir bölümünün arka planında blur şekilde ürün görselinni gösterilmesi isteniyordu ve ürün resmi dış servis sağlayıcıdan dikdörtgen şeklinde geliyordu. Bunun için client tarafı bu geliştirmeyi servis yapacak demiş ve servis tarafını geliştiren ekibe yani bize şöyle bir geliştirme ticket'ı açıldı "Gelen dikdörtgen resim alınacak resmin en ortada bulunan kısmından 200x200 boyutlarında bulunan kısmı alnıp client'a o şekilde gönderilecek..." ve 2 gün sonra sürüm çıkmayı planlıyoruz.. WTF oluyosun ilk okuduğunda ama geliştirmede bu. Muhakak yapılır ancak ne gerek var o kadar uğraşmaya diyor insan. Daha önceleri client geliştirmesi yapıtığımdan biliyorum ki o resmi client tarafta alıp arka plana yerleştirip ortalamak max 15 dkkalık bir iş olsa gerek. Hemen takım liderine durumu anlattım ve hakiketen client tarafı 15-20 dkka içerisinde istenilen geliştirmeyi yaptı. Burda şunu sorabilirsiniz, e yarın 300x300 olacak şekilde isterlerse ne olacak ? Olabilir tabikide ancak 15 dkka nere 1-2 gün nere. Bu gibi durumlarda ekip olarak çoğunluğa göre karar alınıp en hızlı çözüm hangisi ise o uygulanmaya çalışılır.

Sonuç olarak; serviste mi gelmeli yoksa client mı yapmalı diye tekrar soracak olursak "it depends on the business" diyorum ben yani işe göre değişir ve her ne kadar herşeyi servisten yönetmemiz gereksede gerçek olan o ana göre mantıklı olan çözüm ney ise o taraf yapıyor olmalıdır. Gerçekten iki tarafıda en iyi şekilde bilen bir kişiye danışarak veya iki taraf bir araya gelip istişare yaptıktan sonra en efektif çözüm bulunacaktır.

 

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.