Quantcast
Channel: Burak Selim Şenyurt
Viewing all 351 articles
Browse latest View live

Simple Stack ile Bir Başka TDD Pratiği

$
0
0

Merhaba Arkadaşlar,

Code Kata pratiklerine ara vermeden devam etmekte yarar var. Bu videomuzda da Test Driven Development odaklı bir pratiği icra etmeye çalışıyoruz. Çok basit ve ilkel bir Stack sınıfını yazmaya çalışacağız. Bildiğiniz üzere Stack veri yapısı Last In First Out(LIFO) ilkesine göre çalışan bir nesne modeli. Bu code kata senaryomuz için dört farklı test senaryomuz olacak. Stack'e son giren nesnenin çekilmesi, Stack'e birden fazla nesnenin atılıp eklenme sıralarına göre çekilmeleri, boş bir stack oluşturulması halinde null döndürülmesi ve sonr olarak null bir öğenin Stack'e eklenememesi. Bu kez dinlendirici Study Soundtrack listesi eşliğinde ilerliyoruz.

Tekrardan görüşünceye dek hepinize mutlu günler dilerim.


Linkedin Bağlantılarını Elde Etmek

$
0
0

Merhaba Arkadaşlar,

Sosyal ağlar ile pek aram yok. Vakit kaybı gibi gelmese de oraya harcayacağım zamanı farklı alanlarda kullanmayı daha çok tercih ediyorum. Özellikle yeni bir şeyler öğrenmek benim epey zamanımı alıyor. Kullandığım tek sosyal platform diyebileceğim yerse Linkedin. Bu profesyonel iş ağında zaman içerisinde hatırı sayılır derecede bağlantım oluştu. Ne var ki sayı artınca ana sayfadaki akışı izlemek inanılmaz derecede zorlaştı. Hatta iş ağını geliştirirken sayının onbinler üzerine çıkması bir süre sonra bazı merak edilen sorulara cevap bulmayı zorlaştırıyor. Örneğin bağlantılarımdan kaçı İnsan Kaynakları alanında çalışıyor? Ya da İstanbul'da yaşayanlar kimler? Veya ayda ortalama 4 veya daha fazla paylaşım yapanlar hangi bağlantılarım? Peki ya yaptıkları paylaşımlarda yapay zeka ile ilgili anahtar kelimeler kullananlar? 

Herhalde nereye varmak istediğimi az çok anladınız. Bir şekilde Linkedin bağlantılarımı sorgulayabilmek istiyorum. Ancak kendi geliştireceğim uygulama üzerinden. Hal böyle olunca tüm kapılar diğer sosyal ağlarda da olduğu üzere firmanın bizlere sunduğu geliştirici API'lerine açılıyor. Bu amaç doğrultusunda yine bir Cumartesi gecesi oturdum bilgisayarımın başına ve Linkedin REST Api diyerekten google'lamaya başladım. Eh malum illa ki kendi içeriğini kullandırttığı REST tabanlı servisleri vardır diye düşündüm. Evet vardı. Sonra dokümantasyonunu okumaya başladım. Ağırlıklı olarak Android, Apple gibi platformlar için sunduğu SDK'lar öne çıkıyordu. Ben ilk başta basit tarayıcı veya Postman gibi araçları kullanarak ilerlemek istedim. Çünkü REST servisilerinin çalışır hallerini görmek için minimum kod eforu harcamak istedim.

Temel olarak yapılması gereken adımlar belliydi. Basitçe örnek bir servisi denerken OAuth 2.0 standardındaki işleyişi de daha iyi anladığımı fark ettim. Bugünün sosyal ağları ya da API sağlaycıları kendi operasyonlarını kullandırtırken güvenliğin ne kadar önemli olduğunun bilincinde hareket etmekte. Çoğunlukla güncel ve güçlü bir doğrulama(Authentication) standardı olan OAuth 2.0 kullanılıyor. Konumuza tekrar dönecek olursak; Linkedin açısından düşündüğümüzde bir REST çağrısı yapabilmenin adımlarını şöyle özetlememiz mümkün;

Amaç: Uygulamaya giriş yapmış bir Linkedin kullanıcısının temel bilgilerini elde etmek(adı, soyadı, ünvanı,id'si gibi)

  1. Öncelikle Linkedin üzerinde bir uygulama(Application) oluşturmamız gerekiyor. Bu uygulamayı tanımlandığında Linkedin bize bir Client Id ve Client Secret verecek. Ayrca bizim, OAuth 2.0 için bir Callback URL bilgisi de vermemiz lazım. 
  2. Uygulama hazır olduğunda işlemlere devam edebilmek için Linkedin'den bir Authorization Code almalıyız. Bu kod Linkedin'e giriş yapan ve söz konusu uygulamanın client id bilgisi ile gelen kişi için üretilecek. Talep sonrası akış, uygulamayı tanımlarken belirtilen Callback URL adresine doğru devam edecek ve bir Authorization Code dönecek(Querystring içerisinde code ismiyle)
  3. Bu noktada Linkedin'in herhangibir API hizmetini kullanabilmek için gerekli Access Token bilgisini almaya çalışacağız. Az önce verilen Authorization Key bilgisi işte bu aşamada devreye giriyor. Az önce kendimizi Linkedin'e doğrulatıp bir anahtar almıştık. Şimdi bu anahtarı kullanarak Linkedin'den bir Bearer Token isteyeceğiz. Token'ı alırken sadece anahtar değil, Client ID ve Client Secret bilgilerini de Linkedin'in ilgili token servisine göndereceğiz.
  4. Son adım için gerekli talep yapıldığında Linkedin bize belli bir periyot boyunca geçerli olacak Access Token bilgisini döndürecek. Biz de bu token bilgisini alıp Linkedin'in ilgili API servislerini kullanacağız.

Kabaca adımlarımız bu şekilde. Şimdi gerçek zamanlı örneğimizi yapmaya çalışalım.

Callback Sayfasının Oluşturulması

Authorization Key bilgisinin döndürüleceği sırada talebin yeniden yönlendirileceği bir adres olduğundan bahsetmiştik. Burası üzerinde Linkedin için Javascript SDK'sı kullanılan basit bir HTML sayfası da olabilir, Apple SDK'sı kullanan bir mobil uygulamada. Ben boş bir Asp.Net Core uygulaması oluşturup belli bir porttan yayın yapmasını tercih ettim. Önce komut satırından boş bir proje şablonu açtım.

dotnet new web -o LinkedinCb

Startup.cs içerisindeki Configure metodunu da aşağıdaki hale getirdim.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.Run(async (context) =>
    {
        var authorization_code=string.Empty;
        if(context.Request.QueryString.HasValue)
        {
            authorization_code=context.Request.Query["code"].ToString();
        }
        context.Response.ContentType="text/html";
        await context.Response.WriteAsync($"Linkedin Callback Page<br/>{authorization_code}");
    });
}

Tek yaptığım QueryString ile gelebilecek bir code değeri varsa bunu ekrana bastırmak. Birde uygulamanın 8144 portundan çalışacağını belirtmek için launchSettings.json'da aşağıdaki değişikliği yaptım(http://localhost:8144 buradaki çalışma özelinde oluşturulmuş bir adres. Pek tabii Linkedin hizmetlerini kullanacak uygulama nerede host ediliyorsa o makine üzerindeki bir konum da olabilir)

"LinkedinCb": {
      "commandName": "Project",
      "launchBrowser": true,
      "applicationUrl": "http://localhost:8144",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }

Linkedin Uygulamasının Oluşturulması

Linkedin'de şu sayfaya giderek bir uygulama oluşturmamız gerekiyor. Ben aşağıdaki ekran görüntüsünde yer alan bilgileri kullandım ama bir şirket olsaydım ve örneğin pazarlama amaçlı bir çalışma yapsaydım pek tabii gerçek firma bilgileri ile hareket edebilirdim.

Pek çok alan zorunlu. Web sitesi adresi, elektronik posta hesabı, telefon numarası vb... Gönder düğmesine bastıktan sonra ise aşağıdaki sayfaya ulaştım.

Burada verilen Client Id ve Client Secret değerleri önemli. Hatta onları çok iyi korumanız gerekiyor. OAuth 2.0 tarafında Callback URL bilgisi, biraz önce yazdığımız .Net Core uygulamasının yayınlandığı ve adres yönlendirmenin yapılacağı adres. Uygulama için varsayılan olarak r_basicprofile yetkilendirmesi söz konusu ki bu login olan kullanıcının temel bilgilerini görmek için yeterli. Ancak farklı izin seviyeleri de söz konusu(Hatta okuduğum kadarıyla çok daha geniş yetkilere sahip olabiliyoruz ama bunun için Linkedin ile partner olarak anlaşmak gerekiyor olabilir. Emin değilim araştırmak lazım)

Authorization Kodunun Çekilmesi

Artık doğrulama kodunu alabilirdim. Bunun için tarayıcıdan aşağıdaki talebi göndermem yeterliydi.

https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id={burada_uygulamanın_client_id_bilgisi_var}&
redirect_uri=http%3A%2F%2Flocalhost%3A8144&
state=zRt1poWf45A53sdfKef90pp4567

Talebin içerisinde bir kaç parametre var. response_type ile aslında Linkedin'in authorization servisinden ne istediğimizi belirtiyoruz. client_id az önce oluşturduğumuz uygulamadan geliyor. Yine uygulama için belirttiğimiz Callback adresini redirect_uri parametresi ile bildiriyoruz. Son parametre olan state değeri ise Cross Site Request Forgery saldırılarına önlem olması için verilen tahmin edilmesi zor bir değer olmalıdır. Talebi göndermeden önce .Net uygulamasını çalıştırmayı da ihmal etmedim. Çok doğal olarak talep sonrası hemen Linkedin'in Login sayfasına yönlendirildim.

Login işlemini gerçekleştirdikten sonra da aşağıdaki sayfaya yönlendirildim.

Tahmin edileceği üzere uygulamaya kullanıcının bir izin vermesi gerekiyor. Bu izni verdikten sonra artık elimde bir Authorization kodu da vardı.

Access Token Değerinin Alınması

Authorization Key değeri artık elimdeydi. Postman'i kullanarak aşağıdaki POST talebini oluşturdum. 

https://www.linkedin.com/oauth/v2/accessToken
POST
HTTP 1.1
Content-Type: application/x-www-form-urlencoded

Body Key-Value içerikleri;

grant_type=authorization_code
code=AQRf-GqTIUisUY6hP..............
redirect_uri=http%3A%2F%2Flocalhost%3A8144
client_id={linkedin_uygulamanızın_client_id_değeri}
client_secret={linkedin_uygulamanızın_client_secret_değeri}

Görüldüğü üzere Linkedin'in accesstoken adresine bir talep gidecek. Talep başarılı olmuş ve Linkedin servisi bana bir token bilgisi göndermişti.

Linkedin Üye Bilgisinin Çekilmesi

Artık elimde REST servislerini yaşam süresi dolana kadar kullanabileceğim bir Bearer Token var. Postman'den aşağıdaki talebi kullanarak kendi bilgilerime ulaşmayı başardım.

https://api.linkedin.com/v1/people/~?format=json
GET HTTP 1.1
Authorization: Bearer AQUmIOSKG0UoLxN-NnoNGqJYvgIQ6QU9.................

Aslında buraya kadar ki kurgu, geliştirdiğimiz herhangi bir uygulamaya Linkedin ile Login olan kullanıcının temel bilgilerini okuyup ekrana basmak için de yeterli.

Authorization Adımı Şart mı?

Aslında Linkedin API servislerini kullanırken Authorization adımına ille de uğramak gerekmeyebilir. Client ID ve Client Secret değerlerini kullanarak accessToken hizmetine doğrudan ulaşmakta mümkün. Ancak bu akış için Linkedin'in ile iletişime geçmek gerekiyor.

Tam Olarak İstediğimi Elde Edemedim

Aslında istediğim şeyi tam olarak elde edebilmiş değilim. Ben bağlantıda olduğum kişilerin bilgilerine de ulaşabilmek istiyordum. Bununla ilgili olarak Linkedin dokümantasyonundan yararlanarak aşağıdaki talebi denedim.

https://api.linkedin.com/v2/connections?q=viewer&projection= (elements*(to~(id,localizedFirstName,localizedLastName)))
GET HTTP 1.1
Authorization: Bearer AQUmIOSKG0UoLxN-NnoNGqJYvgIQ6QU9nj7.............

Bağlantılarımın id, ad ve soyad bilgilerini görmek istemiştim.

Volaaa..Linkedin beni acı bir şekilde geri çevirmişti. Neden kendi bağlantılarımı çekememiştim? Bir süre bilgisayarımın başında mutsuz mutsuz oturdum. Mutfağa gidip yaz sıcağına aldırmadan sıcak bir kahve yaptım. Soğumasını camın önünde beklerken saatin gece yarısını geçişisini izliyordum. Tekrar bilgisayarımın başına döndüğümde ilk gözüme çarpan şey servis versiyonunda v1 değil de v2 kullanmış olmamdı. Ama bu son derece olağandı çünkü Linkedin API tarafında versiyon değişikliğine gitmişti. Yine de v1 yaparak aynı talebi tekrar denedim. Bu sefer de bad request aldım.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><error><status>400</status><timestamp>1533423097840</timestamp><request-id>VNSLB86CIJ</request-id><error-code>0</error-code><message>Unknown field {connections} in resource {Root}</message></error>

O zaman v1 de çalışan people talebini v2 ile yapayım dedim ;) Tahmin ettiğim gibi v2 için yine yetkilendirme hatası(403 Forbidden) aldım.

Sonrasında google amcaya giderek beni mutlu edecek bir şeyler söylemesini istedim ve Stackoverflow'un şu adresteki girdisinde bir bilgi buldum. Uzun bir Linkedin formunda onları amacımla ilgili ikna etmeye çalıştım. 30 iş günü içerisinde bana döneceklerini söylediler. Muhtemelen tekrardan token alacağım tabii ama önemli değil. Servis kurgusunun değişmeyeceği ortada. Ancak Linkedin'in bildirimine göre eğer beni anlarlarsa yeni bir client id ve client secret bilgisi paylaşacaklar. Bu sebeptendir ki makalem şu an itibariyle yarım kaldı. Baştaki "To Be Continued" un anlamı da bu işte :|

To Be Continued (maybe)

Azure Üzerinde Redis Cache Kullanımı

$
0
0

Merhaba Arkadaşlar,

Bir süredir kişisel becerilerimizle alakalı olarak karşımıza çıkan T-Shaped Person isimli bir konu var(Aslında yıllardır var) Daha yakın zamanda katıldığım Scrum eğitiminde tekrardan karşıma çıkan ve hatta şuradaki yazıyla kısaca bilgilenebileceğiniz, özetle bir alanda gerçekten uzman ama bu alanla alakalı yan dallarda da bir şeyler yapabilen insan modelinden bahsediyorum.

Tek kişi için düşündüğümüzde bile çok yönlü bir birey geliyor aklımıza ama özellikle bir takımı bu tip insanlardan oluşturduğumuzda birbirlerinin açıklarını kapatabilen başarılı ekiplerin ortaya çıktığını görüyoruz. I-Shaped yerine T-Shaped olmak daha mühim bu nedenle. Özellike çevik ekiplerin başarısında önemli bir yere sahip.

İşin aslı bir çok şekil var hayatımıza girmiş olan. I, T-shaped dışında M-Shaped, Comb-Shaped, Pi-Shaped, E-Shaped diye gidiyorlar araştırabildiğim kadarıyla. Üşenmeyin siz de araştırın. Bakın burada da eneteresan bir yazı var konu ile ilgili.

Ben T-Shaped birisi olmaya çalışıyorum ama oldukça yavaş kaldığımı söyleyebilirim. Aslında kendi mesleğimle ilgilli pek çok konuda epey geriden geliyorum. Belki de sadece öğrensem ve yazmak için uğraşmasam şimdilerde daha ileri bir noktada olabilir T-Shaped forma biraz daha uyabilirdim. Ancak işin doğrusu öğrendiklerimi paylaşmayı seviyorum. En azından deneyimlerimi, konu ile ilgili başıma gelenleri aktarma fırsatı bulduğumu düşünüyorum.

Belki de bilmem kaç yüzünce kez karşılaştığınız bir konu ile karşınızdayım bu sebeple :) Özellikle NoSQL dünyasını tanıyanların yakından bildiği Redis ve Microsoft Azure platformuna konuk olacağız bu yazımıda. Amacımız bir Redis Cache hizmetini devreye almak ve basit bir .Net Core istemcisinden yararlanarak kendisiyle konuşmak. Redis, bellek tabanlı çalışan en popüler veri deoplama sistemlerinden birisi. In-Memory Data Structure Store olarak ifade ediliyor hatta. String, Hash, List, Set, Sorted Set, Bitmap, HyporLogLog gibi çeşitli veri türlerinin tutulmasına olanak sağlıyor. Ağrılıklı olarak uygulamaların performans kazanımı gerektiren vakalarında değerlendiriliyor. Bu, özellikle back-end tarafı için önemli. Veriyi bellek üzerinden yapısal olarak anlamlı şekilde tutmak ve hatta dağıtılabilir olarak sunmak büyük ölçekte istenen bir kabiliyet.

Bu açılardan düşünüldüğünde bulut bilişim hizmetlerinin de olmazsa olmaz kalemlerinden birisi olarak karşımıza çıkıyor. Öyle ki, bulut üzerine aldığımız uygulamaların veriye hızlı erişmesi gerektiği durumlarda ciddi anlamda kullanılıyor. Sık sık ihtiyaç duyulan, sürekli olarak değişime uğramayan verilerin fiziki diskten çekilmesi yerine bellekten alınması elbette performans açısından daha iyi. Lakin bunu dağıtık sistemler, ölçeklenebilirlik, veri çeşitliliği gibi noktalardan düşündüğümüzde Redis gibi çözlümlere yönelmemiz gerekiyor.

Artık kurulumundan, duruma göre ölçeklendirme planlarının oluşturulmasına kadar pek çok yönetsel işlevin bulut sistemleri tarafından sağlanıyor olduğunu da görüyoruz. Microsoft Azure platformu bu anlamda bizlere önemli bir imkan sunuyor. Azure üzerindeki Redis Cache hizmetini kullanarak bu tip bir tesisatı oluşturmak son derece kolay. Nasıl mı? Haydi gelin bir "Nasıl Yapılır?" macerasına daha başlayalım.

Redis Cache Kaynağını Oluşturmak

İşe azure portal üzerinden redis cache araması yaparak başlayabiliriz(Bu aşamada Microsoft Azure aboneliğinizin olduğunu varsayıyorum)

Redis Cache öğesini bulduktan sonra tek yapmamız gereken yeni bir tane oluşturmak. Diğer pek çok hizmette olduğu gibi isim, kaynak grubu, lokasyon ve benzeri bilgileri girmemiz gerekiyor. Ben DNS adı olarak Gondor'u kullandım ve "Kullandıkça Öde" tipindeki aboneliği seçtim. Bu vakaya özel olmasını istediğim için gondor-redis-rg isimli yeni bir Resource Group belirttim. Lokasyon olarak da West Europe tarafındaki sunucu merkezini işaret ettim.

Burada da bir fiyatlandırma söz konusu elbette :) Ücretsiz bir kullanımını bulamadım ancak geliştirme amacıyla C0 Basic isimli modeli değerlendirmemiz mümkün. Tabii başka modellerde var. "View Full Pricing Details" bağlantısına basarsak diğer seçenekleri görebiliriz.

İhtiyaca yönelik olarak doğru modeli seçerek ilerlemek önemli. Gerekli bilgiler sonrası oluşturma işlemi başlatılabilir. Redis Cache bir kaç dakika içinde kullanıma hazır hale gelecektir. Başlangıç için tek Node'dan oluşan 250 MB kapasiteli, SSL desteği veren ve 256 bağlantıya kadar çıkabileceğimiz bir Redis Cache hizmeti söz konusu.

Burası bir veri kaynağı olduğu için doğal olarak Connection String bilgisine de ihtiyacımız var. Show Access Keys kısmından bu bilgilere ulaşabiliriz. 

Primary Connection string içerisindeki bilgi .Net Core tarafnda StackExchange.Redis paketi için ele alınabilir formattadır.

İstemci Uygulamanın Geliştirilmesi

Aslında portal tarafındaki hazırlıklarımız tamamlanmış durumda. Şimdi basit bir istemci geliştirerek Redis Cache ile konuşmaya çalışalım. Her zaman ki gibi bir Console uygulaması üzerinden ilerleyeceğiz. Öncesinde terminal üzerinden yapmamız gereken bir kaç hazırlık var. Console projesinin oluşturulması, Redis ile konuşmamızı sağlayacak StackExchange.Redis ve JSON serileştirme işlemlerini kolaylaştıracak Newtonsoft.json paketlerinin eklenmesi. Bunun için terminalden aşağıdaki komutları işletebiliriz.

dotnet new -o console TalkWithGondor
dotnet add package StackExchange.Redis
dotnet add package Newtonsoft.json
dotnet restore

TalkWithGondor isimli bir Console uygulaması oluşturduk. Kodlara gelince,

using System;
using Newtonsoft.Json;
using StackExchange.Redis;

namespace TalkWithGondor
{
    class Program
    {
        static void Main(string[] args)
        {
            string conStr = "gondor.redis.cache.windows.net,password=YWG04toTMb45VUTv7hcIcjbIQymuL7IaRp2Z7/5cNNU=,ssl=True,abortConnect=False";
            var connection = ConnectionMultiplexer.Connect(conStr);
            var db = connection.GetDatabase();
            var pingResponse = db.Execute("ECHO","Nabersin?");
            Console.WriteLine(pingResponse);

            db.StringSet("Motto", "Yağmurlu bir Nisan akşamıydı...");
            var mottoValue = db.StringGet("Motto");
            Console.WriteLine(mottoValue);

            Product box = new Product
            {
                Id = 10001,
                Title = "Lego head box",
                UnitPrice = 50
            };
            db.StringSet("LegoBox", JsonConvert.SerializeObject(box));
            Product productFromCache = JsonConvert.DeserializeObject<Product>(db.StringGet("LegoBox"));
            Console.WriteLine($"\t{productFromCache.Id}\t{productFromCache.Title}\t{productFromCache.UnitPrice}");
        }
    }

    class Product
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public double UnitPrice { get; set; }
    }
}

Neler yaptık kısaca inceleyelim. Redis ile kolayca haberleşebilmek için StackExchange.Redis isim alanındaki tiplerden yararlanıyoruz. ConnectionMultiplexer sınıfını kullanarak bir bağlantı açıyoruz. Connect metoduna parametre olarak Azure portalından aldığımız bağlantı bilgisini koyduğumuza dikkat edelim. Sonrasında GetDatabase metodu ile db isimli bir değişken örneklenmekte. Bu değişken üzerinden çeşitli Execute denemeleri icra etmekteyiz. Redis'in klasik PING ve ECHO gibi selamlaşma fonksiyonları var. İlk Execute işleminde ECHO komutunu kullanarak bir mesaj gönderiyoruz. Redis gönderdiğimiz mesajı bize aynen geri yollamalı. Bir nevi bağlantımızı test ettiğimizi ifade edebiliriz.

İzleyen satırda StringSet ve StringGet kullanımlarına ait örnekler var. StringSet ile tahmin edeceğiniz üzere Redis üzerinde bir key:value çifti oluşturulmasını sağlıyoruz. Veri tipi metinsel içerikten oluşmakta. StringGet ile de Motto anahtar adıyla yolladığımız içeriğin değerini getiriyoruz. Kodun son kısmında ise işimize daha çok yarayacak bir örnek yer alıyor. Bir sınıfa ait nesne örneğini Redis üzerinde nasıl tutabileceğimizi görüyoruz.

Aslında anahtar nokta içeriği JSON formatında saklamaktan ibaret. Sonuçta hangi platform olursa olsun JSON genel bir veri formatı standardı sunuyor. Örnekte yer alan Product nesne örneğinin verisini de bu şekilde tutmamız mümkün. Tabii serileştirme ve ters serileştirme noktasında JsonConvert sınıfının SerializeObject ve DeserializeObject metodlarından yararlanmaktayız. Kodlarımız görüldüğü üzere son derece basit. Zaten basit olması da gerekiyor. Neden işleri karmaşıklaştıralım ki?(Yazar burada OverEngineering'cilere atıfta bulunuyor :P )

Uygulamayı çalıştırdığımızda aşağıdaki ekran görüntüsündekine benzer sonuçlar almamız lazım.

Görüldüğü gibi ECHO ile gönderdiğimiz mesaj bize aynen geri gönderildi. Ayrıca Motto mesajının da başarılı bir şekilde aktarıldığını görmekteyiz. Benzer durum LegoBox anahtar değeri ile tutulan Product nesne örneği için de geçerli. Neredeyse her türden veriyi Redis Cache üzerine almamız mümkün.

Çalışmalar devam ettikçe Redis Cache üzerindeki harketlilikler de artacaktır. Portal üzerindeki Monitoring sekmesini kullanarak çeşitli metrikleri inceleyebiliriz(Tahminimce Amazon Web Services'ler de olduğu gibi belli eşik değerlerine ulaşıldığında devreye girecek alarm mekanizmaları da kurulabiliyordur. Araştırmam lazım) Ben yaptığımız ilk bir kaç deneme sonrası aşağıdaki sonuçlarla karşılaştım.

Bağlantı sayıları, get ve set operasyon çağrıları, cache nesnelerinin durumları vs. Diğer kaynaklarda olduğu gibi oldukça geniş bir ölçümleme seti var. Biraz kurcalamak lazım. Bu adımları başarılı bir şekilde tamamladıysanız ve Redis Cache ile ilgili başka bir şey yapmayacaksanız size tavsiyem ilgili kaynak grubunu silmeniz olacaktır. Neme lazım arka planda unutulup da ilerleyen zamanlarda fiyatlandırma konusunda bize problem çıkartmasın değil mi?

Bu yazımızda Azure tarafından sunulan Redis Cache hizmetini nasıl kullanabileceğimize dair basit bir örnek yapmaya çalıştık. İstemci tarafında sadece .Net Core değil, Python, Node.Js, Java gibi diğer platformları da kullanabiliriz. Detaylı bilgi ve diğer öğretiler için Microsoft'un resmi dokümanlarına bir bakmanızı öneririm. İşi daha da ileri götürmek için istemci uygulamanızı da Azure üzerinde host etmeyi deneyibilirsiniz. Pekala bu bir Web uygulaması ya da Web API hizmeti olabilir. Bu uygulamayı App Service olarak host edip Redis Cache'den yararlanmaya çalışabilirsiniz. Hatta dağıtık bir önbellekleme stratejisinin mimari seviyede nasıl ele alınması gerektiğine dair şu adresteki pratiğe de bakabilirsiniz. Daha gerçekçi bir vaka çalışması olacağı kesin. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

West-World'ün RabbitMQ ile Tanışmasının Zamanı Gelmişti

$
0
0

Merhaba Arkadaşlar,

Küçük bir çocukken, soğuk bir kış gününde ablamın elini tutmuş Kadıköy'deki büyük postaneye doğru yol aldığımı hatırlarım. O yaz mektuplaşmak için adresini aldığım arkadaşıma yazdıklarımı göndermek istiyordum. Hayal mayal hatırladığım anlar. İlkokul zamanlarından kalma. Üzerinden onca yıl geçmiş durumda. Gönderen belli, alıcı belli, mektup ortada, pulları üstünde, yazılanlar içinde, taşıyıcı PTT her zaman ki gibi hizmetimizde. Tabii postaneye gelen bir çok mektup daha var. Hepsinin göndericisi ve gideceği adresler de belli.

Bir postane uzun zamandır Message Broker sistemlerine benzetiliyor aslında. Mükemmel çalışan asenkron kuyruk sistemlerinin bir kısmının sırf bu işle uğraştığı söyleniyor. Apache Kafka, MSMQ ve başkaları. Ama tabii bir de pek çok platform tarafından desteklenen meşhur RabbitMQ var. RabbitMQ aslında bir posta ofisi olarak göz önüne alınıyor. Mektuplar yerine binary large objects(BLOB) gibi nesneleri taşıyabiliyor. Bu nesnelerin mutlaka göndericisi ve alıcısı(alıcıları) oluyor. Üstelik bu gönderim FIFO(First In First Out) mantığına göre yapılıyor. İlk giren mesaj ilk çıkar mantalitesinde.

Anladığım Kadarıyla

İşin teknik boyutuna girerek konuyu biraz daha açmaya çalışalım. Belli amaçlar için üretilen mesajlar olduğunu ve bu mesajların alıcılarının bulunduğunu düşünelim. Günümüzde kullandığımız herhangi bir sistemi düşünebiliriz. Özellikle mikroservislerin olduğu sistemler göz önüne alınabilir. Onlarca mikroservisin mesaj ürettiğini ve bu mesajların alıcılarının olduğunu düşünelim. Birisinin bu mesaj trafiğini yönetebilmesi gerekiyor. Hatta bir kuyruk sistemi ile ele alınması hiç fena olmaz. İşte bu tip ihtiyaçlarda genellikle Messaging Queue sistemlerinden yararlanılır ki RabbitMQ, Avanced Message Queueing Protocol'ünü baz alan gelişmiş versiyonlardan birisidir. Anladığım haliyle RabbitMQ aşağıdaki gibi bir mesajlaşma sistemini baz alır. 

Producer rolünü üstlenen taraf kuyruğa(belki de kuyruklara) atılmak üzere bir mesaj göderir. Mesaj, Exchange arabirimi tarafından karşılanır ve çeşitli kurallara göre bir veya daha fazla kuyruğa yönlendirmede bulunur. Exchange modellerinin herbirisi için ortak olan özellikler vardır. Name, Durability, Auto-Delete ve Arguments. Exchange'ler genellikle adlandırılırlar. Durability özelliğinin değerine göre mesajların disk üzerinde kalıcı olarak tutulup tutulmayacağı belirlenir. Varsayılan olarak bellek kullanılır. Auto-Delete ile işi biten mesajın kuyruktan otomatik olarak düşürülüp düşülmeyeceği ayarlanır. Arguments kısmında ise mesaja ait ek niteliklere yer verilir. Aslında Exchange türlerini aşağıdaki grafiklerle daha iyi anladığımı itiraf edebilirim(Teşekkürler Pluralsight)

Direct Exchanges. Genelde tek bir kuyruk kullanımı söz konusu ile ele alınıyor.

Fanout Exchanges modelinde, mesaj birden fazla kuyruğa kopyalanmakta. Broadcasting sistemlerinde anlamlı. Örneğin güncel oyun sonuçlarının tüm oyunculara bildirilmesi veya hava durumunun haber kanallarına yayınlanması.

Topic Exchanges'de mesajlar konularına göre farklı kuyruklara dağılabilmekte. Buna göre tüketiciler ilgili oldukları konuya ait kuyruğa düşen mesajları okuyorlar. Bir nevi sınıflandırma yapılığını ifade edebiliriz.

Header Exchanges modelinde mesaj ile ilgili birden fazla niteliğin kullanılması ve buna göre uygun kuyruğa atılması söz konusu Burada önceki modellerde kullanılan Routing-Key ele alınmamakta. Bunun yerine mesajın başlığına eklenen nitelikler öne çıkmakta. Routing-Key'lerin sadece string olabileceği düşünülürse bu model kullanılarak farklı türlere göre sınıflandırma yapılmasına da mümkün hale geliyor.

Kuyruğunda kendine göre bi takım özellikleri vardır. Name, Durable, Exclusive ve Auto-Delete. İsmi dışında kuyruğun hafızada mı yoksa kalıcı olarak disk üzerinde mi tutulacağını belirtebiliriz. Nitekim kuyruğun makinenin restart olması halinde kaybolmasını istemiyorsak Durable özelliğine true değerini atamamız gerekir. Kuyruk için açılan bağlantının durumuna göre silinip silinmeyeceği de Exclusive özelliğiyle belirlenir. Consumer'un abonelikten çıkması halinde de kuyruğun silinmesi istenebilir. Auto-Delete bu durumun ayarlanması için kullanılır. Malum kuyruğun da sonuç itibariyle bir maliyeti var. O nedenle kalıcı olması veya işi bitince silinmesi gibi kriterler büyük ölçeklere çıktığımızda ince performans ayarlarını gerektirebilir.

Consumer rolünü üstlenen taraf tahmin edileceği üzere kuyruktan ilgilendiği mesajı okur. Birden fazla mesaj tüketicisi olabilir. Hepsi aynı kuyruktan ya da birbirlerinden tamamen farklı konulardaki kuyruklardan beslenebilirler. Bu yoğurt yiğiş biraz da seçilen Exchanges stratejisine göre değişiklik gösterir. Consumer ile kuyruk arasında bir haberleşme söz konusudur. Consumer çoğu zaman bir mesajı aldıktan sonra bunu anladığına dair(acknowledgment) kuyruğa bildirimde bulunur ya da timeout gibi vakalar oluştuğunda mesajı geri çevirmek(Discard) veya yeniden kuyruğa aldırmak(Re-Queue) için dönüşler yapar. Kabaca bu durumu aşağıdaki gibi ifade resimleyebiliriz.

Benim bu Cumartesi gecesindeki tek amacım ise RabbitMQ'yu West-World üzerinde konuşlandırmak ve .Net Core ile geliştirilmiş örneklerden yararlanarak basit mesaj alış verişi için kullanmak. Sevgili dostum Bora Kaşmer konuyu uzun zaman önce şuradaki yazısında zaten ele almıştı. Ben hem bu yazıdan hem de internetteki diğer kaynaklardan yararlanarak RabbitMQ'yu Linux platformu üzerinde deneyimlemek istedim. Haydi gelin West-World'de bunun için neler yaptım sizlere kısaca anlatayım.

Başlıyorum

İlk önce West-World'e RabbitMQ'yu kurmam gerekiyordu. Uzun zamandır güncellemediğimi fark ettiğimden çalıştırılacak ilk iki terminal komut belliydi.

sudo apt-get update
sudo apt-get upgrade

Bu arada RabbitMQ'nun bir bağımlılığı bulunuyormuş. Erlang programlama diline ait platforma ihtiyaç duymaktaymış. Bu yüzden onu yükleyerek işe başladım.

wget http://packages.erlang-solutions.com/site/esl/esl-erlang/FLAVOUR_1_general/esl-erlang_20.1-1~ubuntu~xenial_amd64.deb
sudo dpkg -i esl-erlang_20.1-1\~ubuntu\~xenial_amd64.deb

Sonuçlar iç açıcıydı :P

Erlang dilinin sisteme yüklendiğini teyit etmek için komut satırından erl yazıp çalıştırmam gerektiğini öğrendim. Bu şu an için çok yabancı olduğum bir fonksiyonel programlama dili.

erl

Artık RabbitMQ kurulumuna başlayabilirdim. Linux terminalinde komutlarımı arka arkaya yazmaya başladım.

echo "deb https://dl.bintray.com/rabbitmq/debian xenial main" | sudo tee /etc/apt/sources.list.d/bintray.rabbitmq.list
wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add -
sudo apt-get update
sudo apt-get install rabbitmq-server

Kurulum başarılı bir şekilde tamamlandıktan sonra servisi etkinleştirmem gerekti.

sudo systemctl start rabbitmq-server.service
sudo systemctl enable rabbitmq-server.service

Sistem kontrolcüsünden yararlanarak RabbitMQ hizmeti yönetilebilir. Örneğin servisi durdurmak için,

sudo systemctl stop rabbitmq-server.service

komutu kullanabilir. Güncel durumunu kontrol etmek içinse, 

sudo rabbitmqctl status

komutundan yararlanılabilir.

Bu işlemlerden sonra RabbitMQ'yu web browser'dan izleyebilmek için de bir şeyler yapmak gerekiyormuş. Nitekim bir şeyler yapmadan localhost üzerinde 15672 adresine gitmek istediğimde hatayla karşılaştım. İlk iş rabbitmq için yönetim arabirimini etkinleştirmekti. 

sudo rabbitmq-plugins enable rabbitmq_management
sudo chown -R rabbitmq:rabbitmq /var/lib/rabbitmq/

Ardından Jerry isimli bir kullanıcıyı sisteme ekledim. Aslında bu kullanıcı ile web arayüzüne erişmeyi planlıyorum. Üstelik kendisini administrator rolü ile de ödüllendireceğim.

sudo rabbitmqctl add_user Jerry tom1234!
sudo rabbitmqctl set_user_tags Jerry administrator
sudo rabbitmqctl set_permissions -p / Jerry ".*" ".*" ".*"

Artık tarayıcıdan http://localhost:15672 adresine gidip az önce oluşturduğum Jerry kullanıcısı ile giriş yapabilirim. İlk kez karşılaştığım bir arabirim. Ama RabbitMQ'nun artık West-World üzerinde olduğundan eminim.

Producer Tarafını Yazmak

Ortada bir kuyruk sistemi artık var. Sırada mesaj yayınlayacak tarafı yazmak vardı. Klasik olarak Visual Studio Code'u açtım ve terminal penceresini kullanarak bir Console uygulaması oluşturdum. TurboNecati isimli uygulama mesaj gönderen program rolünü üstlenecek. RabbitMQ ile el sıkıştıktan sonrada AfacanMurat için kuyruğa bir kaç mesaj bırakacak. RabbitMQ ile kolayca konuşabilmek için RabbitMQ.Client isimli NuGet paketinden yararlanılabilir (Ben örneği .Net Core tabanlı olarak geliştirdiğim için bu paketi kullanıyorum. Ancak RabbitMQ için dil desteği çok geniş. Python, PHP, Java, Ruby, Go, Objective-C, Swift, Javascript gibi dillerle de rahatlıkla kullanabiliyor)

dotnet new console -o TurboNecati
dotnet add package RabbitMQ.Client
dotnet restore

Amacım kuyruğa mesaj atıp okuyabilmek olduğu için program kodları oldukça mütevazi. 

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using RabbitMQ.Client;

namespace TurboNecati
{
    class Program
    {
        static void Main(string[] args)
        {
            var connectionFactory = new ConnectionFactory() { HostName = "localhost" };
            using (var connection = connectionFactory.CreateConnection())
            {
                using (var channel = connection.CreateModel())
                {
                    channel.QueueDeclare(queue: "commands",
                                         durable: false,
                                         exclusive: false,
                                         autoDelete: false,
                                         arguments: null);

                    var messages = new List<string>{
                                "Matematik çalış",
                                "Haftada bir kitap bitir",
                                "Felsefeyi tanı",
                                "Havalar sıcak sokak kapısına bir kap su bırak"
                            };

                    foreach (var message in messages)
                    {
                        var body = Encoding.UTF8.GetBytes(message);
                        channel.BasicPublish(exchange: "",
                                         routingKey: "commands",
                                         basicProperties: null,
                                         body: body);
                        Console.WriteLine($"'{message}' gönderildi");
                        Thread.Sleep(300);
                    }
                }
            }

            Console.WriteLine(" Şimdilik bu kadar. Görüşürüz.");
            Console.ReadLine();
        }
    }
}

Öncelikle localhost adresini baz alan bir ConnectionFactory nesnesi örnekleniyor. Bundan faydalanarak bir IConnection arayüzü tarafından taşınabilecek bir bağlantı üretiliyor. connection nesnesinden yararlanılaraktan da asıl mesaj gönderme işlerini üstlenecek olan IModel arayüzünün taşıyabileceği bir değişken oluşturuluyor. QueueDeclare metodu ile command isimli bir kuyruk tanımlanmakta(Tabii metod parametrelerinin değerlerinin çeşitli anlamları var. Örnekğe göre mesajlar bellekte saklanacak ve sadece bu bağlantı için geçerli olacak) Eğer RabbitMQ üzerinde bu kuyruk yoksa oluşturulacak. Sonrasında gönderilecek mesajların herbirisi için bir byte dönüştürme işi uygulanıyor. Dolayısıyla çeşitli tipte nesneleri kuyruğa atabiliriz. Mesajların yollanması için BasicPublish metodu kullanılıyor. Temel görevi body ile gelen içeriği ilgili kuyruğa basmak. Programı çalıştırdıktan sonra elde ettiğim sonuçlar oldukça hoş. RabbitMQ web arayüzüne gittiğimde kuyruğa gelmiş ve okunmak için hazır bekleyen toplam 4 mesaj olduğunu gördüm.

Hatta Queues kısmına girdiğimde commands isimli kuyruğun oluşturulduğunu da gördüm.

TurboNecati sevgili yeğeni AfacanMurat için anlamlı yaz tatili mesajlarını bırakmıştı bile. Peki AfacanMurat bu mesajları nasıl okuyacak?

Consumer Tarafını Yazmak

Birileri mesajları yazıyorsa atılan bu mesajları okumak isteyen tarafları da vardır mutlaka. Şimdi de onu yazmam gerekiyordu. AfacanMurat uygulamasını benzer şekilde oluşturdum.

dotnet new console -o AfacanMurat
dotnet add package RabbitMQ.Client
dotnet restore

Sonrasında kodları yazmaya başladım.

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Text;
using System.Threading;

class Program
{
    public static void Main()
    {
        var connectionFactory = new ConnectionFactory() { HostName = "localhost" };
        using (var connection = connectionFactory.CreateConnection())
        {
            using (var channel = connection.CreateModel())
            {
                channel.QueueDeclare(queue: "commands",
                                     durable: false,
                                     exclusive: false,
                                     autoDelete: false,
                                     arguments: null);

                var reader = new EventingBasicConsumer(channel);
                reader.Received += (m, e) =>
                {
                    var body = e.Body;
                    var message = Encoding.UTF8.GetString(body);
                    Console.WriteLine($"{message}");
                    Thread.Sleep(500);
                };
                channel.BasicConsume(queue: "commands",
                                     autoAck: true,
                                     consumer: reader);

                Console.WriteLine("Teşekkürler Necati Amca :)");
                Console.ReadLine();
            }
        }
    }
}

Aslında mesaj gönderen program kodlarına oldukça benzer bir kurgu söz konusu. İlk önce bir ConnectionFactory, ardından IConnection ve oradan da kanal modeli oluşturuluyor. AfacanMurat' da commands isimli kuyruğu dinleyecek. Mesaj okuma işlemi Received olay metodu ile sağlanmakta. BasicDeliverEventArgs tipinden olan e parametresinden yararlanılarak kuyruktaki mesaj alınıp string formata dönüştürülüyor. Örnekte 4 adet mesaj söz konusuydu. RabbitMQ'nun FIFO ilkesine göre de ilk giren mesaj ilk olarak elde edilecektir. Kodu çalıştırdım ve aşağıdaki ekran görüntüsünü elde ettim.

Mesajlar AfacanMurat tarafından okunduğu için kuyruktan silinmişlerdi. RabbitMQ web arayüzünden bunu açıkça görebiliyordum.

Bir şeyleri deneyimleyebildiğim güzel bir Cumartesi gecesi daha sonlanmak üzere. Sevgili CoderBora'nın güzel yazısı West-World üzerinde RabbitMQ'yu deneyimlemek için çok destekleyici oldu. Sonuçta kuyruk modelli bir mesajlaşma sistemini kurup üzerine bilgi yazıp okuyabildim. RabbitMQ bu kadarla sınırlı kalabilecek bir konu değil elbette. Ben kendim için kendi deneyimimi yazıp aktardım sadece. Gerisi sizin elinizde. Okuyun, araştırın, deneyin, ilerleyin. Örneğin ben Work Queues konusunu incelemeyi düşünüyorum. Web uygulamaları düşünüldüğünde bu kritiktir. RabbitMQ'da bu yapı nasıl uygulanır, öğrenir öğrenmez yazıya dökmek istiyorum. Böylece geldik bir maceranın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Kaynaklar :

RabbitMQ'nun güzide Tutorial serisinin başlangıç noktası

Web API'leri Swagger ile Zenginleştirelim

$
0
0

Merhaba Arkadaşlar,

Dokümantasyon sektörümüzün olmazsa olmazları arasında yer alan bir konu. Ancak güncelliğini korumak ve okunabilirliğini sağlamak da bir o kadar zor olabiliyor. Ayrıca dokümantasyon hazırlamak da çoğumuza bir işkence gibi geliyor. Yine de belirli alanlarda tüketicilerin iyiliği için bu dokümantasyonları hazırlamak boynumuzun borcu diye düşünüyorum. Devasa kütüphanalerden oluşan uygulamalarda gerçekten dokümantasyon başlı başına bir işken daha küçük alanlarda etkili kullanabileceğimiz yerleri de var.

Ağırlıklı olarak web servislerini göz önüne alalım(Genel standartlara uyan her tür servis olabilir) Bir servis ne iş yapar, tanımı nedir, içerisinde hangi operasyonları barındırır, bu operasyonların kullanım şekli nasıl olmalıdır, giriş çıkış parametreleri ne türdedir ve akla gelebilecek bir kaç soruyu düşünelim. Bu sorular ilgili web servisini kullanacak taraf için kendi geliştirmeleri sırasında önem arz eder ve mutlaka hayatımızın bir noktasında entegre etmemiz gereken bir servis olmuştur.

Eğer bu bir kurum servisi ise genelde elimize tutuşturulan uzun bir Word dokümanı da olur. Servisin genel yapısı, işleyiş şekli ve diğer bilgilerine buradan bakarız. İşin içinden çıkılır çıkılmasına ama bugüne gelindiğinde artık servislerin kendilerini anlattıkları arabirimleri de üzerinde taşıdıklarını görüyoruz. Hazır ortalık mikro seviyede sayısız Web API hizmetinden geçilmiyorken hızlı ama etkili dokümanlar oluşturmak bu açıdan önemli.

XML tabanlı dokümantasyon konusu aslında .Net'in ilk yıllarından beri hayatımızda yer alıyor. Temiz kod(Clean Code) çerçevesinde yapılan geliştirmeler bu tip yorum satırlarına gerek bırakmıyor gibi düşünülebilir, lakin ilgili XML girdileri otomatik dokümantasyon hazırlayan uygulamalar içindir(Basit bir Help dosyası olabileceği gibi, Swagger arabirimi de olabilir)İşin içerisinde yalın ve hafif Web API'ler söz konusu ise XML yorumlarından kaçınmamakta yarar var.

Evrensel anlamda bir standartlaşma da var. OpenAPI bildirimlerine uyan Swagger arayüzleri Web API hizmetleri için genel kabul görmüş durumda. Bu yazımızdaki amacımız ise bir Web API servisine Swagger modelinde bir yardım dokümanı eklemek. İşimiz çok kolay ve sonuçları oldukça tatmin edici. İlk olarak bir uygulama oluşturalım ve Swagger kullanımını kolaylaştıracak Swashbuckle paketini ekleyelim.

dotnet new webapi -o QuoteWallAPI
dotnet add package Swashbuckle.AspNetCore

XML Documentation File'ın etkinleştirilmesi için proje dosyasına da küçük bir ek yapmamız gerekiyor. PropertyGroup altına GenerateDocumentationFile elementini ekleyip true değerini vermeliyiz. Normalde Visual Studio arayüzünde kolayca yapılabilir ama Visual Studio Code kullanıyorsanız dosya üstünde eklemeniz gerekebilir.

<Project Sdk="Microsoft.NET.Sdk.Web"><PropertyGroup><TargetFramework>netcoreapp2.1</TargetFramework><GenerateDocumentationFile>true</GenerateDocumentationFile></PropertyGroup><ItemGroup><Folder Include="wwwroot\" /></ItemGroup><ItemGroup><PackageReference Include="Microsoft.AspNetCore.App" /><PackageReference Include="Swashbuckle.AspNetCore" Version="3.0.0" /></ItemGroup></Project>

Ben standard olarak gelen ValuesController sınıfını QuotesController olarak isimlendirip içeriğini aşağıdaki gibi değiştrdim. Bol miktarda XML Comment ve bir kaç tane de nitelik(Attribute) kullanımı var.

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using QuoteWallAPI.Models;

namespace QuoteWallAPI.Controllers
{
    ///<summary>
    ///CRUD Controller for Daily Quote
    ///</summary>
    [Route("api/[controller]")]
    [ApiController]
    public class QuotesController 
        : ControllerBase
    {
        ///<summary>
        ///List all public quotes sorted by most recently liked.
        ///</summary>
        ///<remarks>with paging, this returns up to 100 quotes.</remarks>
        ///<return>Quotes list</return>
        ///<response code="200"></response>    
        [Produces("application/json")]  
        [HttpGet]
        public ActionResult<IEnumerable<Quote>> Get()
        {
            return new List<Quote>();
        }

        ///<summary>
        ///Return a specific Quote from ID
        ///</summary>
        ///<param name="id">ID of Quote</param>
        ///<return>Quotes list</return>
        ///<response code="200">If found</response>    
        ///<response code="404">If not found</response>
        [Produces("application/json")]
        [HttpGet("{id}")]
        public ActionResult<Quote> Get(int id)
        {
            return new Quote();
        }

        ///<summary>
        ///Add a new Quote to QDB
        ///</summary>
        ///<param name="value">JSON content of Quote</param>
        ///<remarks>
        ///Sample body content
        ///{"id":1,"Text":"Some words..","Author":"you"}
        ///</remarks>
        ///<response code="201">Added</response>    
        [Consumes("application/json")]
        [HttpPost]
        public void Post([FromBody] string value)
        {
        }

        ///<summary>
        ///Update any Quote belongs to a specific Author and ID
        ///</summary>        
        ///<param name="id">Identity value of Quote</param>
        ///<param name="author">Author of Quote</param>
        ///<param name="value">JSON content of updates</param>
        ///<remarks>
        ///Sample body content
        ///{"Text":"Any update"}
        ///</remarks>
        ///<response code="201">Updated</response>    
        ///<response code="404">If not found from ID</response> 
        [HttpPut("{id}")]
        [Consumes("application/json")]
        public void Put(int id,string author, [FromBody] string value)
        {
        }

        ///<summary>
        ///Delete any Quote from QDB
        ///</summary>
        ///<param name="id">ID of Quote</param>
        ///<response code="204">Deleted</response>    
        ///<response code="404">If not found from ID</response> 
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }
    }
}

Özet bildirimleri için summary, örnek kod parçası yerleştirmek veya daha fazla bilgi vermek için remarks, dönüş tipini bildirmek için return, HTTP durum kodu için response, fonksiyon parametreleri için param elementlerinden yararlanmaktayız. Ayrıca fonksiyonun Request veya Response içerik tipleri için Consumes ve Produces niteliklerini de ele alıyoruz.

QuotesController'ın sunduğu 5 standart fonksiyonellik bir şey yapmıyorlar ancak siz kendi örneğinizi geliştirirken özellikle "Try it out" butonuna basarak yapacağınız test sonuçlarını görmek için içeriklerini doldurabilirsiniz. Örnekte kullanılan Quote isimli bir Model sınıfımız da var. Onu da dokümantasyon için XML Comment ve bir takım niteliklerle zenginleştirmeliyiz.

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace QuoteWallAPI.Models
{
    ///<summary>
    ///Some quote from you
    ///</summary>
    public class Quote
    {
        ///<summary>Id of the Quote</summary>
        [Required]
        public int Id { get; set; }
        ///<summary>Text of the Quote</summary>
        [Required]
        public string Text { get; set; }
        ///<summary>Author of this Quote</summary>
        [DefaultValue("Anonymous")]
        public string Author { get; set; }
    }
}

Id ve Text elementleri Required olarak işaretlendiler. Bu yüzden yardım dokümantasyonunda * sembolü taşıyacaklar. Author değişkeni için varsayılan bir değer belirttik ki bu da Swagger tarafından ele alınacak. Bu nitelikler dışında çok kısa özet bilgilere yer veriyoruz.

Buraya kadar yaptıklarımızla hem Controller hem Model sınıfları hakkında bir kaç bilgi sağlamış olduk. Ancak Swagger arayüzünün bu içeriği kullanması ve otomatik olarak oluşturulması için ilgili Middleware parçasının eklenmesi lazım. Bunun için Startup sınıfında bazı eklemeler yapacağız. 

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.Swagger;

namespace QuoteWallAPI
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            services.AddSwaggerGen(g=>{
                g.SwaggerDoc("v2",new Info
                {
                    Title="DailyQuote CRUD API",
                    Version="2.0",
                    Description="Get your friends daily quotes, add something beaty words and more...",
                    Contact=new Contact { Name = "burak", Email = "selim@buraksenyurt.com", Url = "http://www.buraksenyurt.com"}                    
                });

                g.IncludeXmlComments(Path.ChangeExtension(typeof(Startup).Assembly.Location, ".xml"));
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            //app.UseHttpsRedirection();
            app.UseMvc();

            app.UseSwagger();
            app.UseSwaggerUI(s=>{
                s.SwaggerEndpoint("/swagger/v2/swagger.json","Daily Quote");
            });
        }
    }
}

Öncellikle AddSwaggerGen metodu ile ilgili hizmeti çalışma zamanına eklemekteyiz. Burada kullanılan SwaggerDoc içerisinde ana sayfa için gerekli başlık bilgilerini dolduruyoruz. Servisin adı, versiyon numarası, kısaca ne yaptığının açıklanması ve kontak kurulabilecek kişi bilgilerine burada yer vermekteyiz. Önemli olan kısımlardan birisi de XML dokümantasyon dosyasının bildirimi. Aksi belirtilmedikçe bu dosya üretilen dll dosyası ile aynı yerde olacaktır.

Servisi bu şekilde bildirdikten sonra kullanılması için de UseSwagger ve UseSwaggerUI fonksiyonlarından yararlanmaktayız. Bir endpoint veriyoruz ki kullanıcılar veya servis kaşifi olan botlar kolaylıkla yardım dokümanına erişebilsinler.

Bu çalışmalar sonrasında XML comment'lerinin bir dosya olarak Bin klasörü altında oluştuğunu da görebiliriz. İstersek buradan da içeriğe müdahale etmemiz mümkün.

Uygulamayı

dotnet run

komutuyla çalıştırdıktan sonra artık Swagger arayüzüne ulaşabiliriz. Tek yapmamız gereken herhangibir tarayıcıyı kullanarak http://localhost:5554/swagger/ adresine gitmek(5000 nolu Port Apache hegamonyasında olduğundan UseUrls ile değiştirdim) Karşılama sayfası aşağıdaki gibi açılır. Gayet şık ve göz alıcı gördüğünüz üzere :)

Üst kısımdaki özet bilgileri SwaggerDoc metodu içerisinde belirlemiştik. Sunmuş olduğumuz API operasyonlarına göre bir kaç bölüm açıldığını görebiliriz. Get, Post, Put ve Delete için. Ayrıca Controller tarafında kullanılan Quote sınıfı da Models bölümünde yer alır. Dolayısıyla XML Comment'ler ve Swagger nitelikleri arayüz tarafına da yansımaktadır. Diğer kısımların nasıl göründüğüne de kısaca bakalım dilerseniz.

Sayfalama yapıldığı takdirde 100 quote getiren Get talebine ait parça aşağıdaki gibidir.

Response Content Type'ın JSON olduğunu, HTTP 200 kodu döndüreceğini ve dönüş çıktısının da Example Value kısmındaki gibi olacağını görebiliyoruz. "Try It out" butonuna basarak hemen test de edebiliriz. 

Yeni bir quote eklemek istediğimizde kullanacağımız POST işlemine ait aşağıdaki kısım oluşur.

Örnek body içeriği, olası HTTP Dönüş kodları, parametrenin JSON tipinden olacağı, fonksiyonun kısaca ne yaptığı gibi bilgileri görebiliyoruz. Diğer metodlar için de benzer yardım sayfaları ve anında test edebilmemizi sağlayacak "Try It Out" düğmeleri olacakatır.

Belli bir ID için Quote döndüren Get operasyonu(id alanının Reuired olduğuna dikkat edelim)

Update işlemi için kullanılan Put metodu(Bu fonskiyon için de id alanı zorunludur)

ve son olarak silme işlemleri için kullanılan Delete operasyonu(Kırmızı renk dikkat edilmesi gereken bir işlem olduğuna işaret ediyor olmalı)

Görüldüğü üzere APIyi kullanacak olan geliştirici ve hatta API hizmetini tarayan robot için gerekli tüm bilgiler burada yer alıyor. Operasyon adları, açıklamaları, dönüş durum kodları, ne tür içeriklerle çalıştıkları, örnek mesaj gövdeleri ve tabii test edilmelerini sağlayan "Try It Out" düğmeleri. Bu standartlaştırılmış yardım sayfalarını basit bir kaç hareket ile uygulamak da oldukça kolay. Bize düşen bu standart yardım dokümanlarını hazırlamak için gerekli XML Comment ve nitelikleri doğru bir şekilde uygulamak. Bundan sonra yazacağımız her Web API için biraz vakit ayırıp bu dokümanları hazırlamakta yarar var. Gelecek sadece insanların değil, belli standartları takip eden robotların kullanacağı servislerle dolu olacak. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Basit Bir JsonConverter Uyarlaması

$
0
0

Merhaba Burak,

Nasılsın? Umarım iyisindir ve her şey yolundadır. Beni sorarsan her zaman ki gibi oldukça yoğun bir dönemden geçmekteyim. Özellikle halen devam etmekte olan mimari dönüşüm projesinden dolayı böyle bir yoğunluğumuz var. Gerçi çevikleşmeye başladığımızdan beri fazla mesai yapmıyor ve gerçekten değer içeren sürümlenebilir çıktılar üretiyoruz. Şu an altıncı sprint'i koşmaktayız ve takımın ivmesi rayına oturmuş durumda. Bu açılardan bakınca tatlı bir yoğunluktayım desem yeridir. Sana bunları anlatmama gerek yok nitekim duyduğuma göre siz de benzer bir sürece girmişssiniz.

Ben izninle kafama takılan bir konuyu seninle paylaşmak istiyorum. Firmamız bünyesinde geliştirdiğimiz Web API servislerinde standart input ve output tipleri kullanmaktayız. Payload'ları bunların içerisinde taşıyoruz. Bilirsin, POCO(Plain Old CLR Objects)şeklinde tasarladığımız klasik servis girdi çıktı mesajları işte. Lakin bu mesajların bazılarının farklı döndüğünü gördük. Aktif olarak Load Balancer'lar arkasında kalan servislerin bir kaçı aşağıdaki gibi bir JSON içeriği döndürmekteyken(basit anlatabilmek için içerikleri kırptım),

{
	'operation_name':'Dosya transfer işlemi',
	'state':'Tamamlandı',
	'additional_info':'her şey yolunda',
	'time':'20180404195865'
}

bazıları da aşağıdaki gibi bir içerik döndürüyor.

{
	'function_name':'Batch çalıştırma işlemi',
	'status':'hata aldı',
	'description':'batch yerinde bulunamamış',
	'time':'20180404190001'
}

Senin de göreceğin gibi servislerin bazıları output tipinin farklı bir versiyonunu kullanmakta. Bu sorunu çözmek üzere bir takım geliştirmelere başladık. Ancak bu sorun merak ettiğim başka bir konuyu daha doğurdu. Servisleri test ettiğim tarafta bu iki farklı çıktı için iki farklı sınıf yazmak zorunda kaldım. Bir şekilde mesajlar için araya girip JSON'ların key değerlerine göre tek bir nesne örneğine Deserialize işlemi uygulayabilir miyim? Uygulayabilirsek eğer bir örnek ile nasıl yapabileceğimizi bana anlatabilir misin? En kısa sürede görüşmek ümidiyle sevgili dostum. S(h)arp' a bol bol selamlar :)

Sevgili Nazım,

Sendeki tatlı yoğunluğun bir benzeri bizde de var. Henüz ikinci sprint'teyiz. Taşlar yeni yeni oturmaya başladı. Sanırım bir sonraki sprint'te kapasitemiz ve ne kadarlık iş çıkartabildiğimize dair istatistiki değerler oturmaya başlayacak. Takım olarak hareket etmek oldukça güzel(benim gibi insanları sevmeye birisi için bile). Buradaki çevik takımların isimleri, karakterleri, logoları, duvar kağıtları da var. Oyunlaştırılmış bir deneyimi yaşadığımızı ifade edebilirim. Benim eşleştiğim karakter Ghostbusters'dan Peter Venkman :) Gelelim senin merak ettiğin problemin çözümüne...

Eğer istemci tarafında Newtonsoft'un güzide, meşhur, en mükemmel, en şık JSON paketini kullanıyorsan sanırım aşağıdaki örnek kod parçaslar işini görecektir diye düşünüyorum. Öncesinde tabii işin özetinden bahsedeyim. Deserialize işlemi sırasında araya girmek için JsonConverter sınıfından türetilmiş bir tipi ve içerisinde ezeceğimiz(override) ReadJson metodunu kullanabiliriz. Aslında özelleştirilmiş bir ters serileştirme işlemi söz konusu.

Bu senaryoda aynı anlamı taşıyan ama farklı isimlendirilmiş JSON içerikleri var. key:value çiftlerindeki key bilgileri için basit bir eşleşme tutmamız ve value değerlerine göre kendi tarafımızdaki .Net nesnesini JsonProperty nitelikleri ile desteklememiz yeterli görünüyor. Internet'ten yaptığım araştırmalar sonucunda örnek bazı kod parçalarını rastladım. Reflection ile az da olsa haşır neşir olmamız gerekiyor ki bu kötü bir şey değil. Dün gece de West-World'ün başına geçtim ve basit bir .Net Core Console projesi açtım. İlk iş Newtonsoft.Json paketini projeye dahil ettim. Aşağıdaki gibi.

dotnet add package Newtonsoft.Json --version 11.0.2

Sonrasında ServiceResponseConverter isimli şu sınıfı geliştirmeye başladım.

public class ServiceResponseConverter
	: JsonConverter
{
	private readonly Dictionary<string, string> mappings = new Dictionary<string, string>
	{
		{"operation_name", "function_name"},
		{"state", "status"},
		{"additional_info", "description"},
		{"time","time"}
	};

	public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
	{
		var instance = Activator.CreateInstance(objectType);
		var properties = objectType.GetTypeInfo().DeclaredProperties.ToList();

		var payload = JObject.Load(reader);
		foreach (var property in payload.Properties())
		{
			if (!mappings.TryGetValue(property.Name, out var name))
				name = property.Name;

			var instanceProperty = properties.FirstOrDefault(p => p.CanWrite && p.GetCustomAttribute<JsonPropertyAttribute>().PropertyName == name);
			instanceProperty?.SetValue(instance, property.Value.ToObject(instanceProperty.PropertyType, serializer));
		}

		return instance;
	}

	public override bool CanWrite => false;

	public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
	{
		throw new NotImplementedException();
	}
	public override bool CanConvert(Type objectType)
	{
		return objectType.GetTypeInfo().IsClass;
	}
}

Gördüğün üzere senin örnek JSON içeriklerindeki key değerlerinin eşleştirildiği mappings isimli bir generic Dictionary koleksiyonumuz var. Serialization işlemi uygulamayacağımız için WriteJson fonksiyonunu ele almadım. Bunun yerine ReadJson fonksiyonuna odaklanmanda yarar var. Öncelikle ters serileşmede ki hedef nesne örneğini üretmekteyiz. Hangi tipten üreteceğimiz bilgisi objectType değişkeninde geliyor. JSON içeriğinden okuma yapıp özelliklere değer atayacağımızdan properties isimli bir liste de oluşturuyoruz. Ardından reader içeriğini okuyup JObject türünden olan payload nesnesine alıyoruz. Derken payload içerisindeki tüm özellikleri dolaşmaya başlıyoruz. mappings'deki eşletirmeleri ve hedef sınıfın nitelik tanımlarındaki isimleri kullanarak bir atama gerçekleştiriyoruz. Dibine kadar reflection kullandığımızı fark etmiş olmalısın. Eski C# bilgilerini hatırlamanın zamanı gelmişti zaten ;) Deserialize işlemine konu olacak hedef sınıf içeriği ise aşağıdaki gibi.

[JsonConverter(typeof(ServiceResponseConverter))]
public class ServiceResponse
{
	[JsonProperty("function_name")]
	public string Operation { get; set; }
	[JsonProperty("status")]
	public string State { get; set; }
	[JsonProperty("description")]
	public string Info { get; set; }
	[JsonProperty("time")]
	public string ProcessingTime { get; set; }

	public override string ToString()
	{
		return $"Operation : {Operation}\nState : {State}\nInfo : {Info}\nProcessing Time : {ProcessingTime}\n";
	}
}

Dikkat edersen JsonProperty niteliklerinde mappings listesindeki value adları kullanılmış durumda. Ayrıca ServiceResponse sınıfına uygulanan JsonConverter niteliğine parametre olarak ServiceResponseConverter tipini vermeliyiz ki Deserialize işleminde devreye girebilsin. Örnek main metodu kodlarımız da aşağıdaki gibi.

static void Main(string[] args)
{
	string sample_1 = @"
					{
						'operation_name':'Dosya transfer işlemi',
						'state':'Tamamlandı',
						'additional_info':'her şey yolunda',
						'time':'20180404195865'
					}";
	string sample_2 = @"
					{
						'function_name':'Batch çalıştırma işlemi',
						'status':'hata aldı',
						'description':'batch yerinde bulunamamış',
						'time':'20180404190001'
					}";

	Convert(sample_1);
	Convert(sample_2);
}

static void Convert(string payload)
{
	var result = JsonConvert.DeserializeObject<ServiceResponse>(payload);
	Console.WriteLine(result.ToString());
}

JsonConvert sınıfının DeserializeObject fonksiyonuna generic ServiceResponse bildirimi yapılmış durumda. Çalışma zamanında bu sınıfa uygulanan nitelik sebebiyle özelleştirdiğimiz ters serileştirme akışı devreye girecektir. Senin verdiğin mesaj içeriklerini kullanarak yazdığım basit bir test kodu. Elde ettiğim sonuçlar aşağıdaki ekran görüntüsündeki gibiydi.

Sanıyorum ki senin istediğin de buna benzer bir şeylerdi. Elbette daha iyi şekilde geliştirilebilir bu kod parçası. Hatta hem sayı hem de isimlendirme olarak birbirlerinden tamamen farklı mesajları tek bir tip içerisinde toplamaya çalışmayı da deneyebilirsin. Umarım az da olsa fikir sahibi olmuşssundur. Ben epey şey öğrendim diyebilirim. En kısa sürede görüşmek dileğiyle. Ufaklığa bol bol selamlar :)

Satranç ve ELO Puanlama Sistemi

$
0
0

Merhaba Arkadaşlar,

Satranç oynamayalı yıllar olmuştur. Lise yıllarında kadim dostum Burak Gürkan ile karşılıklı maçlar yapardık. Kerata çok iyiydi. Maçları çoğunlukla kaybederdim ama yine de hoşuma giderdi onunla rekabet içerisine girmek(Bir ara arayayım da yine satranç oynayalım) Herhalde sevdiğimiz bir arkadaşımızla yapabileceğimiz en güzel aktivitelerden birisidir Satranç. Sebat etmek, stratejik düşünmek, saygı göstermek, kabullenmek, odaklanmak…Faydaları saymakla bitmez.

En faal oynadığımız yıllar 1990 ile 1993 arasındaydı. O zamanlar Burak ailesi ile Kadıköy’ün sakin sokaklarından birisinde oturuyordu. Sabahçı olduğumuz için öğleden sonraları zaman zaman evlerine giderdim. Annesi Nimet Hanım’ın lezzetli sütlü kahvesi yanında mutlaka tahinli çörek olurdu. Eğer evdeyse de Ertan amcamızla salonda hoş bir muhabbetin içerisine dalardık. Bize Galatasaray’daki futbolculuk yıllarından, Altay’dan bahsederdi. Burağın odası arka bahçeye doğru bakardı. Herhalde hayatımın orada geçen hiç bir anında tek çıt dahi duymamışımdır. Sakinliği ile huzur veren bir odaydı. O günleri özlediğimi itiraf edebilirim. Çok fazla ve hatta neredeyse hiç problemimiz yoktu(Üniversite sınavı, okul dersleri ve kızlardan başka)

Geçenlerde o günlerin özlemiyle dünya satranç federasyonunun sayfasında gezinirken buldum kendimi. Benim bildiğim en meşhur satranç ustaları Garry Kasparov, Anatoly Karpov, Bobby Fischer ve IBM Deep Blue idi :) Derken şu adreste güncel bir Top 100 listesine rastladım. Magnus birinci, Fabiano ikinci, Mamedyarov üçüncü ve bu şekilde 100ncü sıraya doğru giden bir liste. O anda farkettiğim ve aslında cep telefonlarımızda oynadığımız oyunlardan, basketbol maçlarındaki istatistiklere kadar hep etrafımızda olan bir veri dikkatimi çekti. Rating…Nasıl hesaplanıyordu? Her grup kendi kafasına göre mi hesaplıyordu? Bunun genel kabul görmüş bir algoritması yok muydu? Varsa formülünü kim icat etmişti? Hangi alanlarda kullanılıyordu?

Araştırmalarıma devam ettikçe güzel bulgulara ulaştım. İşin içinde tabiki matematik de vardı. Meğer Birleşik Devletler Satranç Federasyonu’nun kurucusu olan Prof. Arpad Elo’nun kendi adıyla anılan bir eşitliğinden yararlanılıyormuş. Newyork Times'ın haberine göre kendisi 1992 yılının 5 Kasım günü evinde geçirdiği kalp krizi nedeniyle hayatını kaybetmişti. Belki de o gün arka bahçeye bakan odada ebedi dostum ile satranç oynuyorduk.

Hemen denklemin nasıl çalıştığını öğrenmeye başladım. Problem birbirleriyle rekabet halinde olan yarışmacıların, aralarında yaptıklara maçlara göre nasıl puanlanabileceğiyle(derelecendirilebileceğiyle) ilgiliydi. Çözüm için öncelikle birbirlerine karşı yaptıkları bir maçı kazanma ve kaybetme olasılıklarının yüzdesel olarak hesaplanması gerekiyor. Sonrasında bu ELO değerinden yararlanılarak, grubun genellikle 10 ile 32 arasında belirleyeceği bir katsayıya göre ikinci bir eşiltiğin işletilmesi gerekiyor ki puanları güncelleyebilelim.

Konuyu bir örnek üzerinden incelemek en güzeli değil mi? Dünya sıralamasının birinci ve ikinci sırasındaki yarışmacıların aralarında yapacakları maçın sonuçlarını irdelemeye çalışalım. Norveçli satranç ustası Magnus Carlsen’in yazıyı yazdığım tarih itibariyle 2839 puanı vardı. Amerikalı Fabiano Caruana’nın puanı ise 2827. Bu iki kişinin güncel puanlarına göre ELO rating değerlerini aşağıdaki gibi hesaplayabiliriz(Bence kalemi kağıdı çıkartın, sizde benimle birlikte hesaplamaya çalışın. Belki uygularken bir yerlerde hata yapmışımdır)

Magnus’un Fabiano ile yapacağı maçı kazanma olasılığı %52. Fabiano içinse bu oran %48. Çok doğal olarak yüzdesel olarak ifade edilen kazanma olasılıklarının toplamının 1e tamamlanması gerekiyor. Formülde her iki oyuncunun güncel puanlarına başvuruluyor. Bunlar formülün değişken parçaları. Ancak formülün kalan kısmındaki tüm sayılar sabit değerler. Bu hesaplama ile kazanma ihtimallerini çıkartmış ve ilk aşamayı tamamlamış olduk.

Derken maç günü geldi çattı. Sovyet döneminin izlerini taşıdığı her halinden belli olan büyük basketbol sahasının tam orta yerindeki satranç masasında karşılıklı oturan iki kişi vardı. Hemen yanlarında duran hakem ve etrafı çevrelemiş onlarca seyirci heyecanlı bir şekilde karşılaşmanın başlamasını bekliyordu. Kameraman önce satranç tahtasındaki bir piyona odaklandı. Kısa süre sonra televizyonda Magnus’un yüzü belirdi. İlk hamleyi yapmak üzereydi. Elini C2 deki piyona götürdü ve iki kare ileriye doğru ilk hamlesini yaptı. Televizyonda şimdi Fabian’ın yüzü vardı…

Sizce müsabakayı Magnus kazanırsa Dünya sıralamasındaki güncel puanı ne olur? Peki ya Magnus kazanırsa, Fabiano ne kadar geriye düşer? Ya tam tersi durumda?

Satranç federasyonunun bu 4 haneli puan hesabı için bir katsayı belirlediğini düşünelim. Bu sayı 24 olsun. Buna göre müsabakayı Magnus kazanırsa aşağıdaki gibi bir durum oluşur.

İki satranç ustası arasındaki fark biraz daha açılıyor görüldüğü gibi. Aksine maçı Fabiano kazanırsa, birinci ve ikinci sıradakiler yer değiştirecek gibi görünüyor. Aşağıdaki gibi hesaplamayı devam ettirebiliriz.

Formülasyondaki 24 değeri satranç federasyonu tarafından belirlenmiş bir katsayı. İyi bir hesaplama için bu katsayının 10 ile 32 aralığında olması öneriliyor. Maçı kazanma durumunda 1, kaybetme durumunda da 0 sayıları kullanılmakta(Beraberlik şu an için hesaba kattığımız bir kriter değil) Ayrıca yarışmacıların güncel puanları ve buna göre hesaplanmış kazanma olasılıkları da eşitliğin içerisinde yer alıyor.

İtiraf ediyorum işlemleri kolayca yapabilmek için hesap makinesi yerine node.js komut satırından yararlandım :)

Magnus ve Fabiano ilk iki sırada yer alan oyuncular ve ELO rating değerleri birbirlerine oldukça yakın. Lakin puan farkının daha fazla olduğu bir durum söz konusu olursa maç sonucundan oyuncuların çok daha farklı etkileneceklerini söyleyebiliriz. Kazanma oranı çok yüksek olan bir oyuncunun puanında önemli bir artış olmayacak ama kazanma ihtimali düşük olan oyuncunun galip gelmesi halinde kendi puanındaki artış yüksek olacaktır.

Bu durumu irdelemek için Magnus ile 100ncü sırada yer alan Çek satranç ustası Igors Rausis arasında bir maç yapıldığını düşünelim. Magnus’un maçı kazanma olasılığı güncel puanına göre %75, Igors’un kazanma olasılığı ise %25 olarak hesaplanır. Eğer maçı Magnus kazanırsa puanı 2845 olur. 6 puanlık bir artış. Bu noktada maçı kaybeden Igors’un puanıda 6 puanlık düşüşle 2645'e iner.

Peki ya maçı Igors kazanırsa? Bu durumda Igors’un puanı 18 puan artarak 2669 olur. Magnus’da aynı değerde puan kaybedecektir(2821) Aslında birbirlerinden aynı değerde puan kazandıklarını/kaybettiklerini söyleyebiliriz. Tabii noktadan sonraki hassaslığı arttırır ve katsayıyı değiştirirsek puanlardaki artış ve azalma aralıklarını da etkileyebiliriz.

Kullanılan eşitlikleri formülüze etmek istersek aşağıdaki denklemler ortaya çıkacaktır.

Aslında ELO rating değerini hesaplarken yararlanılabileceğimiz bir eğri de bulunmaktadır. Kazanma olasılığı ve oyuncular arasındaki ELO puan farkını hesaba katan bu eğri aşağıdaki gibidir.

Söz gelimi iki yarışmacının güncel ELO puanları arasındaki fark 300 olsun. Pozitif taraftan bakarsak puanı yüksek olan yarışmacının %85 kazanma olasılığına sahip olduğunu söyleyebiliriz. Buna göre rakibinin kazanma olasılığı da eksi değerin olduğu eğrideki kesişim noktasıdır. Yani %15.

ELO oranı bugün League of Legends gibi çok oyunculu platformlarda dahil olmak üzere bir çok oyunda kullanılıyor. Eğer hoşunuza gittiyse tenis oyuncularının rating değerlerini ele alıp aralarında maçlar yaptırarak güncel puanlamalarını öğrenebilirsiniz. Hatta bu formülasyonu koda dökerek, geliştireceğiniz farklı ürünlerde değerlendirebilirsiniz(Zuckerberg’in Harvard’da Facemash için ELO rating algoritmasını kullandığı söylentisi bile var örneğin. Bu tartışma için Quora'ya bakılabilir)

Profesör Arpad Elo’nun 95 yıllık yaşamında insanlığa kattığı önemli değerlerden birisi olan bu hesaplama tekniği, yarışmacıları övmek veya yargılamak yerine adil bir puantaj sistemi ile sıralanmasında büyük rol oynamakta. Üstelik bu formül sistemi 1950den beri hayatımızda…

ITIL'ın Farkına Vardım

$
0
0

Merhaba Arkadaşlar,

Seksenli yıllarda İngilizlerin Central Computer and Telecommunications Agency isimli departmanı, IT hizmetlerindeki sıkıntıların tespiti ve doğru yolun bulunması amacıyla ITIL olarak kısaltılan bir konseptin temellerini ortaya koymuş. Tam olarak açılımı Information Technology Infrastructure Library şeklinde. Ona kütüphane denmesinin makul bir sebebi de süreçlere ait pratikleri içeren beş kitaptan oluşması. O yıllarda temelleri atılan ITIL zaman içerisinde yeni versiyonları ile birlikte gelişmeye devam etmiş. Bugün pek çok IT firmasının(ki sadece IT ile sınırlamak doğru değil nitekim içerisinde hizmet geçen her alanda ele alınabilir) uygulamaya çalıştığı bir, bir…Şey…Immm…Bir ne? 

İşte geçtiğimiz günlerde Doğuş Teknoloji tarafından düzenlenen “ITIL Farkındalık” eğitiminde bu sorunun cevabını bulmaya çalıştık. Ben, her zaman olduğu gibi heyecanla not tutmaya çalıştım. Bir günlük eğitimde yaklaşık onaltı sayfalık not çıkmıştı. Üstelik kaçırdığım bir çok kısım vardı.

Konuya belkide uzun zamandır hayatımda(hayatımızda) olan Service Now ürününden bahsederek başlasam iyi olur. Bazen çalışmayan masa telefonu için, bazen Test ortamından PreProd’a aktarılacak bir SQL Script taşıması için, bazen canlı ortam geçişindeki değişikliğe ait geri dönüşüm dokümanının eklemenmesi için, bazen ekibin üzerine düşen ve belli bir sürede çözülmesi beklenen hizmet kesintisine ait kayıda bakmak için kullanmakta olduğumuz ServiceNow ürününden bahsediyorum. Gerek önceden çalıştığım ING Bank gerek şu an çalışmakta olduğum Doğuş Teknoloji bünyesinde hali hazırda kullanmakta olduğumuz bir ürün.

Neredeyse altı yıldır karşımda olan bu ürünün, ITIL pratiklerinin şirket bünyesinde uygulanması ve hizmet kalitesinin arttırılması için kullanıldığını fark etmem ne acıdır ki bu eğitime nasip oldu. Eğitmenimiz Fırat Okay bir gün boyunca bizleri bilgilendirdi. Aslında proje yönetimi gibi bir konuyla alakalı olacağını düşündüğüm bir eğitimdi ve bu önyargı ile güne başlamıştım(Kimse duymasın sıkılacağımı düşünüyordum) Ancak Fırat hocamız cidden doyurucu bilgiler vererek tabiri caizse ITIL’ı bana sevdirdi.

ITIL’ı anlamak için şu soruyu sormamız gerekiyor; “IT hizmetlerini nasıl daha kaliteli hale getirir ve yönetiriz?”

Bir elektronik posta servisini göz önüne alalım. Bu servis temel olarak müşterilerin haberleşme ihtiycını karşılar. Bu ihtiyacın karşılanması için tasarlanan sistemin içerisinde yazılım(software) ve donanım(hardware) bir arada yer alır. Ancak detaylara inildiğinde işin içerisine güvenlik(security), ağ yönetimi(network management), firewall, anit-virus koruması, router, switch, veritabanı, mail uygulaması gibi konu başlıkları da dahil olur. Üstelik bu konular hem istemci hem de sunucu tarafını ilgilendirir niteliktedir. Görüldüğü üzere basit bir email servisi gibi görünse de, içerdiği bileşenler nedeniyle karmaşık bir sistemle karşı karşıya olduğumuzu söyleyebiliriz.

ITILcada “servis” veya “hizmet” terimi, müşteriye fayda sağlayan ve onun ihtiyacını karşılayan anlamına gelmektedir. 

Bu örnekte aslolan hizmettir(Service) Müşterinin belli bir ihtiyacını karşılamak üzere tasarlanır. Hizmet bileşenleri yukarıdaki örnekte bahsettiğimiz gibi karmaşık bir sistemin parçaları olabilir. Ayrıca her bir bileşen farklı sayıda ve beceride takımların sorumluluğunda işletilebilir. İşin içerisine sorumluluk girince tahmin edeceğiniz üzere dikkat edilmesi gereken hususların sayısı da artar. ITIL , işte bu takımların hizmet faydası ekseninde birleştirilmesini amaç edinir.

Elbette hizmeti sadece IT kapsamında bir olguymuş gibi düşünmemek gerekir. Hatta ITIL’ın faydasının farkına varmak için neden otele ya da restorana gittiğimizi düşünmek gerekir. Bir restorana gittiğimizde bizi kapıda karşılayan kişiden, siparişimizle ilgilenen garsona, mutfaktaki aşçıdan kasiyere kadar herkes verilen hizmetin farkındadır. Müşteriyi(bizi) memnun etmek için herkes kendi sorumluluğunun bilincinde hareket eder ve en iyi hizmeti sunma misyonunun bir parçası olur(İyi restoranlardan bahsediyoruz tabii) Bu farkındalığın bir sonucu olarak ortaya çıkan kaliteli hizmet, sadık müşterilerin ortaya çıkmasına ve restoranın itibarının artmasına sebep olur. Ancak bu farkındalık düzeyi potansiyel Service-Provider rolünde olan her IT organizasyonu için geçerli değildir. Çünkü IT organizasyonuna dahil olanlar bazen bu hizmet olgusunun farkında olmazlar. Dolayısıyla ITIL’ın bu soruna çözüm aradığını da söyleyebiliriz.

ITIL içerisindeki terimler düşünüldüğünde bazı kavramların dikkatli kullanılması gerekir. Hizmet kalitesinden bahsederken bu hizmetin müşterilerin ihtiyacını karşılamak için var olduğunu belirttik. Ancak ITIL kütüphanesine göre müşteri(Customer) ve kullanıcı(User)şeklinde iki ayrı rol söz konusudur. Temelde hizmeti talep eden ve hatta bunun parasını veren tarafı müşteri olarak tanımlayabiliriz. Hizmet ortaya çıktıktan sonra bunu kullanan kişi ise kullanıcı rolünde değerlendirilir.

Fırat hocanın bu ayrımla ilgili verdiği güzel bir örnek de var; Henüz küçük bir çocuğun ebeveyninden bisiklet istediğini düşünelim. Bisikleti araştıran, parasını veren, satıcı ile anlaşan ebeveyn müşteri(Customer) olarak düşünülebilir. Destek tekerlekli sevimli bisikletine kavuşan o minnak çocuk ise kullanıcı(User) olarak tanımlanır.

Eğitimin bu kısımlarında ITIL içerisindeki temel kavramları incelemeye devam ettik. Az çok servisin ne anlama geldiğini, servis müşterisi ve kullanıcısının nasıl ayrıştırıldığını öğrendik. Materyaller yavaş yavaş toplanmaya devam ediyordu. İşin içerisinde servis varsa bunların kurumsal anlamda yönetimi de ITIL açısından değer kazanmakta ki bu durum Service Management olarak ifade edilmekte. Kısacası servisleri nasıl yönetiriz sorusuna cevap aradığımız bir konu olduğunu belirtebiliriz.

Servisler kuvvetle muhtemel süreçlerle(Process) ilişkili olacaktır. Sonuçta bir hizmetin devreye alınması, yönetimi, takibi ve diğer bir çok organizasyonel konu süreçlerle ilişkili. Normal şartlarda süreçleri, belli bir amaç için bir araya getirilmiş aktiviteler dizisi olarak düşünebiliriz. Kurum içindeki iş yapış biçimlerini tanımlamak gibi önemli bir rolleri vardır. Bir çağrı merkezinin talep karşılama adımlarından birisini süreç olarak değerlendirelim. Burada çağrıların nasıl geldiği, neye göre önceliklendirileceği ve çıktının ne olacağı gibi soruların cevapları sürecin aktiviteleri tarafından karşılanacaktır. Aktivite söz konusu olduğunda yine ITILcalaştırılmış bir başka kavram daha karşımıza çıkıyor; fonksiyonlar(Functions) 

ITIL’ın güncel sürümünde 26 ayrı sürecin olduğundan bahsediliyor(Bunu bi araştıralım Burak)

Fonksiyonlar süreç kapsamında ilgili aktiviteyi gerçekleştirecek ekip veya araçlar olarak tanımlanmakta. Dolayısıyla kimin ya da hangi aracın, süreç kapsamındaki hangi fonksiyonu işleteceğinin bilinmesi ve yönetimi de ITIL’ın uğraştığı konular arasında yer almakta. Buradan da fonksiyonların rolleri(Roles) ve sorumluluklarının(Responsibilities) önem kazandığını söyleyebiliriz.

Service, Service Management, Customer vs User, Process, Function, Roles ve Responsibilities…Şu ana kadar ITIL dünyasında ilerleyebilmek için bilinmesi gereken temel terimlerdi. Artık asıl mevzuya girilebilir. Şaşırtıcı olmasa gerek ITIL’ın da bir servis yaşam döngüsü bulunuyor. 

Döngüler bizim hayatımızın olmassa olmazı değil mi? ITIL’da olsa, Agile’da olsa sürekli iyileştirmeye dayanan bir iterasyon zinciri söz konusu. Yukarıdaki güzel şeklin de ifade edeceği üzere ITIL, beş temel başvuru kitabıyla tanımlanmış bir yaşam döngüsü olarak düşünülmeli. Kısaca bunların temel özellikleri üzerinde konuşarak ilerleyelim.

Service Strategy

İhtiyacın belirlendiği aşamadır ve şu sorulara cevap bulan pratikler içermektedir.

  • Biz kimiz? 
  • Vizyonumuz nedir? 
  • Kime hitap ediyoruz? 
  • Müşterimiz kim?
  • Hedeflerimize ulaşmak için izleyeceğimiz yol/yordam nedir?
  • İhtiyaçları karşılamak için ne tür hizmetler sunmalıyız?

Service Design

Tasarımın yapıldığı safha olarak düşünülebilir. Strateji aşamasında alınan kararlara göre servis tasarımı oluşturulur. İhtiyaca yönelik bir tasarımın oluşturulması için gerekli doneleri sağlar.

Service Transition

Testlerin yapıldığı, test sonuçlarına göre hizmetlerin taşındığı(Deployment) aşamanın tariflendiği bölüm olarak düşünülebilir.

Service Operation

Devreye alınan hizmetin işletildiği kısmı tanımlar. Hizmetin müşteriye/kullanıcıya sunulduğu ve aslında ITIL’ın en önemli aşamasıdır(Yani en azından eğitimde en çok üzerinde durduğumuz aşamaydı)

Continual Service Improvement

Aşağıdaki soruların cevaplandığı iyileştirme safhasıdır.

  • Neler iyi gidiyor?
  • Neler kötü gidiyor?
  • Eksikler neler?
  • Neleri iyileştirmek lazım?

Retrospective toplantıları geldi aklınıza değil mi? :) 

Yaşam döngüsünde yer alan Design, Transition ve Operation kısımları sürekli bir iterasyon halinde işlemektedir. Aslında tipik bir yazılım geliştirme sürecini ele aldığımızda ITIL’ın bu süreç üzerinde denk düştüğü belli başlı yerler olduğunu da söyleyebiliriz. Aşağıdaki şekle bir bakalım.

İhtiyaçları belirledikten sonra analizini yapıp bir tasarımın ortaya konması ve buna göre kodlama yapılması söz konusudur. Bunu kullanıcı kabul testleri(User Acceptance Test) ve sonuçlarına göre dağıtım(Deployment) işlemleri takip ediyor. Dağıtılan ürüne daha sonradan destek verilir. 

Evet, biraz Waterfall’a benzer bir yapı gibi görünüyor. Benim kafamda da benzer soru eğitim sırasında oluşmadı değil. Lakin ITIL’ın yaşam döngüsünün de iterasyonlar üzerine dayalı olduğunu görmekteyiz. Bu açıdan bakıdığında Agile yürüdüğümüz yapılar için de iz düşümlerin olduğunu söylemek sanıyorum ki mümkün. Nitekim bir Product Backlog Item’ı sprint’e dahil ettiğimizde onun kısa bir analizi, task’lar halinde parçalanması, task’lara ait kodlamanın yapılması, DevOps söz konusu ise test iterasyonlarının çalıştırılması ve belki de Sprint için bir UAT gerçekleştirilmesi, sprint sonuna gelindiğinde müşteri için değer yaratan increment’lerin dağıtımı ve operasyonun takibi… Pek tabii tüm bu operasyon sürecinin gözden geçirilmesi, gerekli iyileştirmelerin yapılması ve yeniden ihtiyaçların belirlenip aynı akışın devam ettirilmesi…İşte kalıba uydu:) 

Tabii bana göre ITIL‘ın uygulanabilir nitelikte olduğu projeler olmalı. Bir başka deyişle IT açısından ne tür hizmetlerin ITIL çerçevesinde değerlendirilmesi gerektiğinin bir takım kriterleri olmalı. Bakalım notlarımızın sonunda bu soruya cevap bulabilecek miyiz?

Eğitimin bundan sonraki kısımlarında yaşam döngüsünde yer alan bazı maddelerin biraz daha detayına girmeye çalıştık. İlk olarak Service Operation sürecini ele aldık.

Service Operation Process(Biraz daha detay)

Bu safhada Event Management, Incident Management, Request Fullfilment, Problem Management ve Access Management gibi çeşitli alt yönetim süreçleri tanımlanıyor(ITIL versiyonlarına göre farklılıklar olabilir)

Olay yönetimi sürecinde durum değişikliklerinin izlenmesi ile ilgili işlemlere yer verilmektedir. Örneğin bir kullanıcının sisteme Login olması, servisin yeniden başlatılması veya durması gibi haller bu bağlamda düşünülebilir.

Request Fullfilment sürecinde, kullanıcıdan gelen basit ve çabuk gerçeklenebilir isteklere yer verilir. Paralo sıfırlama, biten yazıcı kartuşunun değiştirilmesi, yer değişikliği nedeniyle PC IP adresi taşınması, ortak alandaki bir klasöre erişim yetkisi gibi istekleri örnek gösterebiliriz.

Erişim yönetimi(Access Management) adındanda anlaşılacağı üzere yetkilendirme ile alakalı bir süreçtir. Bir kullanıcının ilgili domain’e eklenmesi, uzak sunucuya erişim yetkisi alınması örnek olarak verilebilir.

Eğitim sırasında üzerinde daha çok durduğumuz ve kısımlar Incident Management ve Problem Management süreçleriydi. Incident ve Problem birbirleriyle çok sık karıştırılabilen kavramlar olduğu için eğitimin bu safhasını biraz daha derinleştirdik.

Devreye alınmış bir hizmette yaşanan plan dışı bir kesinti genel olarak Incident şeklinde adlandırılmakta. Tahmin edileceği üzere bu tip kesintilerin belirlenen veya müşteri ile anlaşılan süreler içerisinde çözümlenmesi bekleniyor/gerekiyor. Aksi durumda hizmet kesintisinin ürünün veya hizmetin itibari üzerinde olumsuz etkileri olması kuvvetle muhtemel.

Geliştirici olarak bizleri en çok ürküten postaların başında Incident kayıtlarına ait bildirimler gelir dersek yeridir. Bir Incident’ın büyüklüğü ve aciliyetine göre SLA(Service Level Aggrement) içinde tanımlanmış sürelerde çözülmesine uğraşırız. Hatta tanımlanan zamanın %25ini, %50sini ya da %75ini tükettiğimizde daha ciddi şekilde uyarılırız. Hele ki Incident çözüme kavuşmazsa…

Bu durum sorunun süreçteki bir üst pozisyona eskale edilmesi ile sonuçlanabilir. Burada bahsi geçen SLA esasında müşteri ile el sıkışılan bir konudur. SLA’in bir amacı, üzerinde anlaşılan seviyelerde hizmet sunulmasını garanti etmektir. Service Design aşamasında tanımlanan SLA’lerin kardeşleri de var. İlerleyen kısımlarda onlar da karşımıza çıkacak.

Belki garip gelecek ama Incident hallerinde günü kurtaracak çözüm neyse uygulanması gerekir(Söz gelimi printer’dan evrak çıktısı alınamıyorsa bir şekilde geçici çözüm uygulanıp sorun giderilmelidir)İşte bu sebepten problem yönetimi isimli ayrı bir süreç daha vardır. Çok sık tekrar eden, etkisi büyük olan veya üst yönetime kadar eskale olduğu için önem arz eden bazı sorunlar problem olarak tanımlanır ve kök nedenleri araştırılır. Bu kök nedenlere bakılaraktan Incident’ların tekrardan oluşmasını önlemek amacıyla kalıcı çözümler uygulanır. Problem yönetimi sürecinde esas amaç kök nedenlerin bulunup kalıcı çözümlerin uygulanmasıdır. Problemler genellikle Workaround, Known Error şeklinde değerlendirilirler. Hatta bilinen hatalar KEDB(Known Error Database) adı verilen veritabanında saklanırlar.

Incedent olarak gelen bir bulgu mutlaka çözülmek durumundadır. Çünkü sistemde kesinti söz konusudur. Ancak Request Fullfilment aşamasında bir zorunluluk yoktur. Nitekim istekler onaya tabidir.

Incident ve Problem yönetimi, Olay izleme ile birleştirildiğinde hizmet kesintilerinin ve sorunlarının çeşitli seviyelerde karşılanması da mümkün hale gelir. Olay izlemedeki bilgilerden yararlanılarak henüz meydana gelmeyen bir kesintinin önceden tespit edilmesi, buna istinaden bir problem kaydının oluşturulup, kök neden tespiti ile kalıcı çözüm uygulanabilmesi sağlanabilir (Bu durum çoğunlukla Proactive Problem Management olarak adlandırılmaktadır) Ancak bu her zaman mümkün değildir. Çünkü işin içerisinde değişim yönetimi(Change Management) denilen ve uygulanacak çözümün çeşidi, büyüklüğü, kritikliğine göre devreye girecek bir onay mekanizması da olabilir.

Servis operasyon sürecinde dört temel fonksiyon(veya ekip diyelim) bulunur. Aşağıdaki şekilde aralarındaki ilişki özetlenmektedir.

Çoğumuz farkındayızdır. Firmada 7x24 etkin görev alan bir operasyon ekibi vardır. Bazı sorunlara otomatik olarak müdahale ederler(Aslında bazı kesintiler önceden tespit edilip robotlaştırılmış süreçler işletilerek sorunlar hissettirilmeden bertaraf da edilebilir) Diğer yandan müşteriden gelen bir kesinti ihbarında bunu ön cephede karşılayan hizmet destek ekibi vardır. Çoğunlukla birinci seviye olarak düşünebileceğimiz bu ekip ilgili sorunu çözmeye çalışır. Bunu yaparken daha önceden kayıt altına alınmış bilinen problemlere ait ipuçlarından da yararlanabilir. Tabii defalarca karşılaşılmış ve birinci seviye tarafından birçok kez çözülmüş bir bulgunun kalıcı olması da iyi değildir. Bunu problem olarak değerlendirip kök sebebini araştırmak ve kalıcı çözüm uygulamak gerekir. Yine de hizmet masası sorunu çözemezse tipine göre bu sorunu teknik veya uygulama yönetim ekiplerine aktarabilir (Çözemessen aktar modeli) 

Teknik ve uygulama yönetimi ekiplerinin aslında IT Operasyon ekibiyle sıkı ilişkisi vardır. Genellikle hizmetin dağıtımı ile ilgili prosedürleri operasyon ekibine de aktarırlar ki bir sorun oluşması halinde 7x24 müdahale edilebilsin. Teknik yönetim kendisine gelen bir bulguyu çözemezse ve bu bulgu üçüncü parti bir servis sağlayıcıya aitse(Vendor) ona aktarır(Ticket açıldı deriz ya bazen) Benzer durum uygulama yönetimi içinde geçerlidir. Lakin burada iki farklı durum olabilir. Bazı uygulamalar iç ekiplerce geliştirilmiştir. Dolayısıyla ilgili bulgu uygulama sahibi ekibe yönlendirilir. Ancak uygulama yine dış firma kökenli ise sorun için ilgili servis sağlayıcıya bir Ticket açılır. 

Bulguları özellikle üçüncü parti fimaya indirgemeye gerek duymayacak şekilde hizmet geliştirmek bence önemlidir. Bu ancak işin en başından itibaren sistem dinamiklerini sürekli olarak test etmekten, test etmekten, test etmekten ve yine test etmekten geçmektedir. TDD gibi yaklaşımlar her ne kadar geliştirme sürelerini uzatsa da, uzun vadede sağlayacağı garantörlük dikkate alınmalıdır. 

Service Transiciton Process(Biraz daha detaylı)

Hizmeti devreye alma süreci olarak tanımlanmıştır. Devreye alınacak, emekli edilecek veya değişikliğe uğrayacak hizmetler ile ilgili her türlü dağıtım işleminin tariflendiği süreçtir. Change Management, Configuration Management, Release and Deployment Management, Transision Planning and Support, Change Evoluation, Knowledge Management gibi alt süreçleri barındırır.

Dikkat edileceği üzere değişim ve konfigurasyon yönetimi de bu süreçler içerisinde yer almaktadır. En önemli süreçlerden birisi değişim yönetimidir. IT süreçlerine etkisi olabilecek her türlü durum değişikliği Change Management’ın konusudur. Çalışan bir sistem üzerinde değişiklik yapmak(yeni özellik eklenmesi, özellik çıkartılması vb) her zaman için kritik ve riskli bir işlemdir. Bu nedenle değişikliğin sistem üzerinde minimum kesintiye uğrayacak şekilde yapılmasının garanti edilmesi gerekir. 

Yine de sevgili Murphy’yi unutmamak lazım. Bu sebeptendir ki, değişim süreçlerinde mutlaka geri alma planları (Remediation Plan) yapılır, yapılmalıdır.

ING Bank bünyesinde çalıştığım dönemlerde taşıma yapılırken mutlaka girmemiz gereken bilgilerden birisi de geri alma planıyla ilgili olandı. Üretim ortamına bir SPmi taşınacak? Taşındıktan sonra sorun olursa sistemin durumunu tekrar eski konumuna nasıl döndüreceğiz, tariflenirdi. Bu tarifleme veritabanı operasyon ekibi tarafında önem arz eden bir konuydu. 

Pek tabii günümüz modern DevOps destekli süreçlerinde olası sorunların taşıma sonrası otomatik olarak hissedilip geri alma ile ilgili programlanmış betiklerin anında yürütülmesi gibi senaryolarda mümkün. Lakin bunların yine de dokümante edilmesi şart. Çünkü sizi/bizi çeşitli regülasyonlar nedeniyle denetlemek zorunda olanlar var(Auditçilerin kulağı çınlasın)

Değişim yönetiminin önemini vurgulamak için Amerikada yapılan bir araştırmayı göz önüne alabiliriz. Araştırmaya göre firmalara gelen çağrıların belirli dönemlerde tavan yaptığı fark edilmiş. Tahmin edeceğiniz üzere çağrı sayılarının anormal seviyede yükseldiği zamanlar değişiklik sonrasına denk gelen anlar. Hatta bir senelik zaman periyodundaki istatistiki verilere bakıldığında, yükselen çağrı zamanlarındaki bulguların neredeyse %90a yakınının yeni değişikliklerle alakalı olduğu saptanmış.

Dolayısıyla yapılacak değişikliklerin riskinin ve etki analizinin de iyi yapılmış olması beklenmekte. Change Management süreci bunu da esas alır. Yeri gelmişken değişiklik adımlarına bakmakta yarar var aslında.

Bütün değişiklikler kayıt altına alınmalıdır. Bir değişim süreci çoğunlukla RFC(Request for Change) adı verilen istekle başlatılır. Söz konusu talep, sonrasında bir değişim kaydı(Change Record) haline gelir. Değişiklikler genelde üç türlüdür. Standart olanlar, acil yapılması gerekenler ve normal şartlara uyanlar. Önceden onaylanmış ve düşük riskli rutin değişiklikler, standart olarak adlandırılırlar. Ancak üretim ortamına alınan hizmette yaşanan ciddi problemler, yasal regülasyonlar nedeniyle uygulanması gereken kurallar veya güvenlik açıklarının oluştuğu durumlar acil değişim(Emergency Change) statüsüne girer. Standart ve acil değişime uymayan haller normal değişim olarak adlandırılır ve minor ya da major tipli olarak iki şekilde kategorilendirilir.

“As soon as possible” tipinden değişimlerin “Mission Impossible” durumuna düşmesini engellemek için planlamanın iyi yapılması önemlidir. 

Değişim kaydının oluşturulmasından sonraki aşama etki ve risk analizinin yapılmasıdır. Buna görede bir onay mekanizması çalıştırılır. Pek çoğumuzun yakınen bildiği şu meşhur CAB(Change Advisory Board) toplantıları yapılır. Hatta acil durumlar için ECAB(Emergency Change Advisory Board) toplantısı gerçekleştirilir. ECAB’in yapılma olasılığı düşük ve onay çıkması da çok kolay değildir. Yönetim çok aksi bir durum olmadığı sürece ritüel değişim sürecinin dışına çıkılmasını istemez. Hoş bunu geliştirme takımı da istemez. Nitekim acil değişim kaydı gerektiren durumların oluşmasına neden olan geliştirici hataları üst tarafta pek de iyi algılanmaz.

Değişim, konfigurasyon yönetimi(Configuration Management) ile de yakın ilişki içerisindedir. Herhangibir özelliğin hizmetleştirildiği süreçleri düşünün. Ortamlar için gerekli bir çok konfigurasyon ayarı bulunuyor. Sunucu adları, veritabanı bağlantıları, sertifikalar vb…Ama olaya sadece yazılım açısından bakmamak lazım. Fiziki sunucular, dokümanlar, IT kadrosu, lisanslamalar ve benzerleri de aslında birer konfigurasyon öğesi(Configuration Item) Keza bunlar arasındaki bağlantılar da değişim sürecinde önemli rol oynamakta. İlişkiler, etki analizi ve kök nedenlerin bulunmasına da yardımcı olan Configuration Management System(CMS) isimli sistem üzerinde tutulurlar.

Çoğunlukla bir sürüm(Release)çıkılacağı zaman birden fazla değişim kaydı sürece dahil olur. Bu durumda onay alanların bir paket haline getirilerek taşınması söz konusudur. Burada paketlerin build, test ve deploy aşamalarının da koordinasyonu gerekir ve bu işlemler sırasında konfigurasyon öğelerinin de sorunsuz işliyor olması önemlidir. 

Release demişken; Hocamızın verdiği bilgiye göre İngiliz Barclay bankası senede bir sürüm(Release) çıkarmış. Eh onların bankacılık süreçleri veya devlet regülasyonları bizimki gibi olmadığı için bu son derece normal diyebiliriz. 

Lakin ING Bank bünyesindeki dönüşüm projesi kapsamında haftada bir sürüm çıkılan dönemlere indiğimizi hatırlıyorum. Preprod ortamına kadar normal olarak ilerleyen hizmetlerin onaya istinaden bir RFC numarası ile canlı ortama alınması söz konusuydu. 

Hatta kanlı bıçaklı CAB toplantıları yapıldığını hatırlıyorum. Ufak bir belgesi eksik olan değişim kaydı onaylanmaz ve o canlı ortam geçişini kaçırabilirdi.

Service Design Process (Biraz daha detay)

Tasarımın tariflendiği bu aşamada iş birimi ve operasyonun gereksinimleri işin içerisine katılarak ilerlenilir. Edindiğim bilgiye göre sekiz süreç içeriyor ancak en önemli ikisi Service Catalogue Management ve Service Level Management.

Service Catalogue daha çok ne sunduğumuzun ya da ne sattığımızın tarifini içeriyor. Eski olsa da şu grafiği göz önüne alabiliriz.

Bu örnekte çeşitli seviyelerdeki müşteriler için verilen depolama ve kurtarma hizmetlerine ait bilgiler yer alıyor. Bunu bir servis kataloğu olarak değerlendirmek mümkün. Hizmetlere ait kısa tanımlamalar dışında dikkat çekecek detaylara da yer verilmekte.

Service Level Management’ta ise bizim daha çok aşina olduğumuz bazı kavramlar var. Bunların başında Service Level Aggrement geliyor. Ancak OLA(Operational Level Aggrement) ve UC(Underpinning Contract)şeklinde iki sözleşme daha yer almakta. SLM’in temel amacı müşteri ile dahili(Internal) ve harici(External) ekipler arasında bir orta yol bulmak. Buna göre antlaşmalar yapılıyor. Antlaşmalarda belirlenen kurallar çerçevesinde de bir servis tasarımına gidiliyor. 

Müşteri çok doğal olarak almak istediği hizmetlerle ilgili olarak bazı gereksinimlerini sunar(Service Level Requirements) Bu zaman zaman fonksiyonel olmayan dilekler olarak da anılır. Bu bildirgede bir hizmetin ne zaman, hangi periyotlarda çalışması istendiği, hizmette kesinti olduğunda da müdahala sürelerinin ne olacağı ve güvenlik kriterleri gibi konulara açıklık getirilir. 

Buna göre müşteri ile iç ekipler arasında operasyonel antlaşma yapılır(OLA) Microsoft, HP, Amazon, Oracle ya da bizim çalışmakta olduğumuz SahaBT gibi firmalarla da SLAler imzalanır. Aslında tarafların durduğu konuma göre antlaşma bir taraf için SLA iken diğer taraf için OLA anlamına gelebilir ya da tam tersi(Bu kısma biraz daha çalışmam lazım)

ITIL, müşteriler ile ilgili SLA, UC ve OLA sözleşmelerinin işin başında yapılması gerektiğini önerir.

Tedarikçi firmalar ile yapılan Underpinning Contract ile Service Level Aggrement sözleşmesi birbirlerine oldukça benzer formattadır. Genellikle UCler çok daha ağır şartlar içerir.

SLA sözleşmesi firmanın kendi iş birimi ile geliştirme ekibi arasında da yapılmış olabilir. Bu durumda iş biriminin talep ettiği hizmetler ile ilgili aksamalarda devreye bu sözleşemelerde belirtilen reaksiyon süreleri girer. Taahüt edilen süreler içerisinde sorunun çözülmesi kaydın atandığı kişi açısından değerlidir(KPI diyeyim siz gerisini anlayın)

Service Strategy ve Continual Service Improvment Hakkında Kısa Kısa

Eğitimin sonları yaklaştıkça bendeki yorgunlukta artmaya başladı. Hocamızın güzel anlatımını pür dikkat dinlemeye çalışırken bir yandan da notlar alıyordum. Ancak her güne sabah 05:50de başlayınca ikindi vakitlerinden sonra enerji az da olsa düşüyor. Yinede servis stratejisi ve iyileştirme noktasında bir kaç kısa not almayı başardım. 

Müşteriye ne sağlayacağımızı bilmek için onu anlamamız gerekir. Bu, strateji sürecinde ele alınan bir olgudur. Business Relationship Management, Service Portfolio Management, Financal Management for IT Services, Strategy Management for IT Services ve Demand Management gibi süreçleri içerir. İsimlerinden anlayacağınız üzere müşteriye sunulacak bir hizmet için organizasyonun finansal bacağından portföyündeki bileşenlerine, arz talep dengesindeki taleplerden IT stratejilerine kadar bir çok önemli kalem hesaba katılır. 

Cep telefonu operatörlerinin maç günlerinde, maç sahasındaki baz istasyonlarının yetersizliği üzerine bir strateji belirleyip ilgili günlerde ilgili alanlara mobil baz istasyonları çıkartması Demand Management için güzel bir örnektir. Arzın arttığı bu vakada talepleri sorunsuz karşılayabilmek için örneğin bir aylık zaman dilimindeki maçların belirlenip mobil istasyonların çıkışını planlamak bir strateji hamlesidir.

Continual Service Improvment diğer ITIL süreçlerini çevreleyen ve dolayısıyla onları gözlemleyip çıktılarını değerlendirerek sürekli iyileştirmenin ele alındığı bir safhadır. Burada adım adım, sakince ilerlenilmesi öğütlenir. Önemli olan bir husus ise ölçümlemektir. İyileştirme yapabilmek için elde metrik değerlerin olması gerekir. Bunları beslemek için de ölçme mekanizmalarının oluşturulması. CSI esasında planlama(plan), yürütme(do), kontrol etme(check) ve aksiyon alma(act) adımlarından oluşan bir döngüyü temel alır(Deming kalite çemberi)

Sonuç

Yönetemediğimiz sistemi kontrol edemeyiz. ITIL, kaliteli hizmet verebilmek, bu hizmetleri yönetebilmek, ölçümleme yapıp iyileştirebilmek için beş farklı disiplini sunmaktadır. Uygulanması bazen çok kolay görünmese de, büyük çaplı kurumsal projelerde ve özellikle müşteriye sunulan hizmetlerin yüksek itibara sahip olduğu durumlarda ITIL gibi endüstüriyel olarak standartlaşmış desenleri tatbik etmeye çalışmak uzun vadede mutlaka yararlıdır. 

Aldığım bu eğitim sonrası Service Now ürününe ve iş yapış şeklimize olan bakışım daha da değişmiş oldu. Hizmeti sahiplenmenin önemli bir unsur olduğunu bir kere daha fark ettim. Elbette ITIL’ı bir şirkete kurgulayacak derecede bilgiye sahip olmamız gerek ve şart değil. Ancak içinde ServiceNow ve benzeri ürünlerin koştuğu, SLA zaman aşımı maillerinin geldiği ve CAB toplantılarının yapıldığı firmalarda bunların neden var olduğunun farkına varılması açısından yazıldığı kadarını bilmek önemli.

Tekrardan görüşünceye dek hepinize mutlu günler dilerim.


.Net Core Web API Uygulamalarını Windows Service Üstünden Sunmak

$
0
0

Merhaba Arkadaşlar,

Scrum'lı çevik günlerimiz tüm hızıyla devam ediyor. Artık takımın kapasitesi standart çizgiye oturmaya başladı. Daha verimli çalışıyor ve daha iyi değerler üretebiliyoruz. Bunun bana olan en büyük artılarından birisi de yeni bir şeyler araştıracak vakit bulabilmek. Nitekim takım üyelerinin değişime uygun olacak şekilde kendisini sürekli yeniliyor olması lazım. Bu açıdan motive edildiğimizi ifade edebilirim. Gündem maddemiz ise uzun zamandır pek yanına uğramadığımız ama mutlaka sistemlerimizin bir yerlerinde koşan Windows Service'ler.

Micorosft, çıkarttığı 2.1 sürümü ile birlikte Asp.Net Core'a bazı yeni özellikler ekledi. Ben de geçtiğimiz zaman zarfında bunlardan bir kısmını inceleme fırsatım buldum. İlgimi çeken yeniliklerden birisi de artık Asp.Net Core uygulamalarını Windows Service üzerinden sunabiliyor olmamız. Bu bana eski WCF'li günlerimi anımsattı. Orada da servisleri self-host mekanizması dışında(Console gibi çalıştırmak), IIS'de veya Windows Service olarak yayınlama şansına sahiptik(hala sahibiz) Yeni gelen kabiliyet tahmin edeceğiniz üzere Windows tabanlı sistemler için geçerli.

Söz konusu yeniliği inceleyeceğimiz bu yazımızda bir Web API hizmetini host etmeyi öğreneceğiz. Dilerseniz vakit kaybetmeden işlemlerimize başlayalım. Öncesinde sistemimizde .Net Core 2.1'ın yüklü olduğundan emin olmakta yarar var. Örneği Windows 10 işletim sisteminde, Visual Studio Code kullanarak yapacağım. 

Web API Uygulamasının Oluşturulması

İlk adım olarak dummy web api uygulamasını oluşturalım. Artık adımız gibi bildiğimiz komutla house isimli bir proje oluşturdum.

dotnet new webapi -o house

Windows Service mekanizmasını Middleware tarafında kullanabilmek ve çalışma zamanında gerekli yönlendirmeleri yaptırmak için Microsoft.AspNetCore.Hosting.WindowsServices paketinin projeye eklenmesi gerekiyor. Bu yüzden terminalden şu şekilde ilerliyoruz.

dotnet add package Microsoft.AspNetCore.Hosting.WindowsServices
dotnet restore

Gelelim kod tarafına. Proje dosyasında yapmamız gereken değişiklikle başlayalım. house.csproj dosyasını açıp PropertyGroup altına eğer yoksa RuntimeIdentifier bilgisini eklememiz gerekiyor.

<Project Sdk="Microsoft.NET.Sdk.Web"><PropertyGroup><TargetFramework>netcoreapp2.1</TargetFramework><RuntimeIdentifier>win7-x64</RuntimeIdentifier></PropertyGroup><ItemGroup><Folder Include="wwwroot\" /></ItemGroup><ItemGroup><PackageReference Include="Microsoft.AspNetCore.App" /><PackageReference Include="Microsoft.AspNetCore.Hosting.WindowsServices" Version="2.1.1" /></ItemGroup></Project>

Standart şablon olarak gelen Controller'lar da bir değişiklik yapmayacağız. Amacımız servis geliştirmekten ziyade host ettirmek. Ancak program sınıfının main metodunda servisin bir Windows Service olarak ayağa kalkacağını belirtmemiz gerekiyor. Bu yüzden Program.cs içeriğini aşağıdaki gibi değiştireceğiz.

using System.Diagnostics;
using System.IO;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.WindowsServices;

namespace house
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var exePath = Process.GetCurrentProcess().MainModule.FileName;
            var rootFolder = Path.GetDirectoryName(exePath);
            var host = WebHost.CreateDefaultBuilder(args)
                                .UseContentRoot(rootFolder)
                                .UseStartup<Startup>()
                                .UseUrls("http://localhost:3403")
                                .Build();

            host.RunAsService();
        }
    }
}

O anki güncel Process'ten yararlanarak exe'nin adını ve yerini buluyoruz. Bu bilgi UseContentRoot tarafından kullanılacak. Windows Service hosting sürecini devreye sokmak içinde RunAsService metodundan yararlanılıyor.

Publish İşlemi

Sırada publish işlemleri var. Uygulamanın release edilebilir bir sürümünü oluşturabilmek için aşağıdaki komutla devam edebiliriz.

dotnet publish --configuration Release

Şirket bilgilsayarımdaki yapıya göre ilgili artifact'ler C:\Projects\tips_tricks\house\bin\Release\netcoreapp2.1\win7-x64\publish klasörüne çıkartıldı. Burada en dikkat çekici nokta house.exe isimli bir dosyanın oluşması. Bildiğiniz üzere web uygulamaları dll olarak dağıtılmakta ancak Windows Service'ler için çalıştırılabilir program dosyası(exe) olması gerekiyor. Bu dosyada üretildiğine göre artık komut satırından Windows Servisi ile ilgili işlemlerimize başlayabiliriz.

SC ile Servisin Hazırlanması

sc aracından ve create komutundan yararlanarak bir servis oluşturabiliriz. Oluşan servisin o anki durumunu öğrenmek için query, Servisi başlatmak içinse start parametresinden yararlanıyoruz. Aynen aşağıda görüldüğü gibi.

sc create HouseAPIService binPath= "C:\Projects\tips_tricks\house\bin\Release\netcoreapp2.1\win7-x64\publish\house.exe"
sc query HouseAPIService
sc start HouseAPIService
sc query HouseAPIService

create komutunda servisin adını ve exe dosyasının olduğu konumu belirtiyoruz. Çok uzun bir adres olduğu için siz denemelerinizi yaparken .Net Core uygulamanızı farklı bir lokasyona çıkartabilirsiniz. --output parametresi bu noktada yardımcı olacaktır. Ben hat ettim siz etmeyin.

Yukarıdaki komutları çalıştırdıktan sonra aşağıdaki ekran görüntüsündeki sonuçları aldım.

Görüldüğü üzere HouseAPIService başarılı bir şekilde çalışır konuma geldi. State geçişlerinden bunu görebiliriz. Buna göre artık http://localhost:3403/api/values adresine talepte bulunabiliriz. İşte postman'den elde ettiğim sonuç.

Artık Windows Service üzerinden yayında olan bir Web API hizmetimiz var. Windows servisini durdurursak tahmin edeceğiniz üzere Web API'yi kullanamayız.

Tabii bu işsiz servisi sistemde tutmak doğru değil. Sonuçta unutulabilir ve boat anchor anti-pattern hali oluşabilir. Bu servisi silmek için sc'nin delete komutundan yararlanabiliriz.

sc delete HouseAPIService

Artık bu servis hayatta değil. Hepsi bu :) 

Böylece bir Web API servisini Windows Service üzerinde nasıl yayınlayabileceğimizi öğrenmiş olduk. Artık Asp.Net Core uygulamaları için elimizde alternatif bir host seçeneği daha var. Asp.Net Core 2.1 ile birlikte gelen diğer özellikleri de zaman içerisinde incelemeye çalışacağım. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Elasticsearch'ü West-World'e Kurdum

$
0
0

Merhaba Arkadaşlar,

Guam adasının güney batısında yer alan ve yaklaşık 11 km derinliğindeki Mariana çukuru, dünyanın en derin noktasıdır. Benim için anlamı "Mariana Çukuru Etkisi" dir. Hatta çevik süreçlerdeki epik senaryoları da "Mariana Çukuru Etkisi" yaratan konular olarak tanımlarım. Ancak bu etkiyi daha çok bir makale için araştırma yaparken yaşarım. Bir kaynaktan diğerine geçtikçe konu derinleşir. Derinleştikçe başladığım yere olan uzaklığımın arttığını fark ederim. Işık azalır, etraf kararmaya oksijen azalmaya başlar. Derken düşünemez bir noktaya gelirim. Çünkü beyne yeteri kadar oksijen gitmez.

İşte "Mariana Çukuru Etkisi" oluşmaya başlamadan durmam gerektiğini bilirim. Madem konu derinleşiyor ve sonrasında içinden çıkmak zorlaşabilir, bölmek en iyisidir derim. Geçen kısa sürede de böyle oldu. Amacım basitçe Elasticsearch'e log'lama amaçlı veri aktarabilmekti. Hatta bunu yapmak için .Net Core tarafında Fluent bir API'den de yararlanacaktım. Tabii başlangıç noktası Elasticsearch ürünüydü. Ona biraz daha yakında bakayım derken konu derinleşti. Konuyu anlayabilmek için parçalamam gerekiyordu. İlk önce onu West-World'e kurup basitçe deneyimleyeyim istedim. İşte buradayız.

Düşünün...Boyut ve içerik olarak sürekli şişmekte olan verileriniz var ve bunlar üzerinde hızlı aramalar yapmak istiyorsunuz. Hepimizin farkında olduğu istekler. Ama işin içinde sihirli bir ifade var; "Büyük Veri". Büyük veri ile kimler haşırneşir oluyor diye kurcalarken de yolu Elasit firmasına ve tabii Elasticsearch'e uğrayanları keşfediyorsunuz.

  • Vimeo(Video aramalarında)
  • The New York Times (150 yıldan fazla zamana yayılmış arşivlerin aranmasında)
  • nvidia (kullanıcı deneyimini arttırmak için gün başına milyar olayın işlenmesinde)
  • Docker (Çalışmakta olan dağıtık uygulamalar için en uygun container'ın bulunmasında)
  • IEEE(Büyük ölçekli finansal uygulamaların altyapılarının anlaşılmasında)
  • Fitbit(saniyede 400bin adet log kaydına bakarak tutarlı sistem performansını sağlamakta)
  • Uber(Kritik pazar davranışlarının iş metriklerinin hesaplanmasında)
  • Microsoft (Azure üzerinde arama yapılıp Social Dynamics'in güçlendirilmesinde)
  • GoDaddy(gerçek zamanlı anomalilerin anlaşılıp kullanıcı deneyiminin arttırılmasında)
  • Zalando(Yeni bir kişiselleştirilmiş alışveriş deneyiminin inşasında),
  • Accenture(en iyi müşteri hizmetinin bulunmasında)
  • eBay(800 milyon listeyi saniye altı sürelerde aramakta)
  • Facebook(milyar sayıdaki kullanıcılarına en iyi yardımı ulaştırmakta)
  • Slack(şüpheli aktiviteleri izleyen bir savunma programının inşasında)
  • Cisco(sistem düşüşlerinin azaltılmasında) 
  • ...

Liste kabarık. Finansal hizmet verenler, sosyal ağlar, yemek işinde olanlar, telekominikasyondakilar vs...

Elasticsearch Hakkında

Aslında basit anlatımıyla ele alırsak veri üzerinde gerçek zamanlı aramaları veya analizleri hızlı yapabileceğimiz dağıtılabilir(Distributed) bir sistemden bahsediyoruz. Kullanımı oldukça kolay olan RESTful yaklaşımına uygun geliştirici dostu API desteği veren, platform bağımsız çalışan ve etkili bir şekilde ölçeklenebilen bir ürün bu. Tabii bu özelliklerine bakarak onu birincil veri saklama ürünü olarak düşünebilirsiniz ama bu bir hata olur. Çünkü tasarım amacı veriyi tutmaktan ziyade hızla arayabilmesi ve sonuç(aggregation diyelim)üretebilmesidir. Bu nedenle onu bir veritabanının indeksine benzetebiliriz.

Elasticsearch çoğu kaynakta veritabanlarımız üzerinde çalışan yerel bir Google arama motoruna benzetiliyor.

SQL, Oracle gibi veritabanı sistemlerini düşünelim. SQL tarafındaki veritabanı(database) Elasticsearch tarafı için index olarak anılabilir. Tablo(table), tip(type) olarak düşünülebilir. Satırlar(row) elasticsearch için dokümandır(Document). Satırdaki sütunlar(column) ise alanları(Field) ifade eder. İçerik JSON formatında tutulur. Elasticsearch, Node ve Cluster yapılarını kullanır. Cluster'lar benzersiz isimlendirilirler. N sayıda node bir araya gelerek Cluster'ları oluşturur. Node'lar da n sayıda Shard örneği barındırabilirler. Mimari tarafını anlamak benim için sanıldığı kadar kolay değil. Ancak içeride geçen temel kavramlar ve kurgu az çok bu şekilde.

Sevgili Burak Tungut'un Elasticsearch enstrümanlarını(index,node,shard,replica vb) anlattığı şu adresteki yazıya bakmanızı şiddetle öneririm.

Elasticsearch'ü tek bir ürün gibi düşünmemek gerekir. Esasında gücünü Apache Lucene'den alır. Lucene bir Java kütüphanesidir ve temel amacı full-text search yapmaktır. Bu açıdan bakıldığında asıl arama işini yüklenen yerdir de diyebiliriz. Dolayısıyla Elasticsearch, Lucene'i sarmallayan bir REST API olarak karşımıza çıkar. Hatta her bir Elasticsearch shard'ı ayrı bir Lucene örneği kullanır. Lucene ana iş yükünü alırken, Elasticsearch dağıtık mimari modelini ondan soyutlayan bir yapı olarak görev alır. Yukarıdaki kavramlarla bunları birleştirirsek sanırım şunları söyleyebiliriz. Bir Elasticsearch örneği, node'lar ve bu node'ların shard'larından oluşur. Her bir shard bir Lucene nesne örneğidir ve index üzerinde oluşan dokümanların bir parçasını tutar. Burada veri dağıtımı işini Elastichsearch koordine eder.

Açıkçası bu kadar laf kalabalığına girmemek lazım. Yoksa "Mariana çukuru etkisi" oluşacak. Haydi geldin şunu West-World'e kuralım ve REST Api'si ile denemeler yapalım.

Kurulum

West-World, 64bit'lik karayollarına sahip bir Ubuntu sürümü. Kurulum gibi işlemler terminelden kolaylıkla gerçekleştirilebilir. Klasik olarak işe sistem güncellemesi ile başlamakta yarar var. Ardından Elasticsearch için gerekli deb paketini indirip install etmek yeterli. İşte ilk komutlarımız,

wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.1.deb
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.1.deb.sha512
shasum -a 512 -c elasticsearch-6.3.1.deb.sha512 
sudo dpkg -i elasticsearch-6.3.1.deb

Kurulum işlemi sonrası elasticsearch, West-World'ün /usr/share/elasticsearch/ bölgesini yerleşmiş oldu. Buradaki /etc/elasticsearch yolundaki konfigurasyon dosyalarından elasticsearch.yml dikkate değer. Sunucu ayarlarının tutulduğu bu dosyada bazı değişiklikler yapmak öğrenme safhasındaki benim gibi bir acemi için kritik.

Örneğin kurulu gelen node başlangıç ayarlarına göre master konumdadır. Slave olmasını yani bir Master'a hizmet etmesini istersek ilgili tanımlamayı false olarak belirlememiz gerekir ki West-World için true bırakılabilir. Bir başka kritik ayar node'un veriyi saklayıp saklamayacığıdır. Varsayılan olarak true değerindedir ama yapılacak işlem sadece veriyi toplamak ve bir arama sonucu üretmekse false olarak bırakılabilir. Yine hazır olarak gelen shard ve replica sayıları değiştirilirse iyi olur. Normalde 5 shard(dolasyısıyla bir index'in dokümanlarının 5 eşit parçaya bölünerek birer shard'a dağıtılması söz konusu) ve 1 replica geçerli ama ben doğal olarak laptop'umda tek bir node kullanıyorum ve sadece öğrenme amaçlı çalışıyorum. Bu nedenle 1 Shard ve 0 Replica olarak ayarlayabilirim. Son olarak değiştirilebilecek path bilgisi de var. Aslında verinin disk üzerinde saklanacağı adresi işaret ediyor. Bunu da farklı bir depolama adresine yönlendirmekte yarar olabilir(Özellikle üretim ortamında değiştirilmesi öneriliyor) 

Tabii büyük sistemlerde n sayıda cluster ve node'un kullanıldığı durumlarda ölçeklemenin etkili olabilmesi için bu ayarları itinayla yapmalı ve hatta bir bilene sorulmalı diye düşünüyorum.

Ben West-World için aşağıdaki ayarları kullandım. Varsayılan 9200 portunu 9205 yaptım, bir cluster ve node adı belirttim. Bunlara ek olarak 1 shard ve 0 replica kullanacağımı söyledim. Diğer pek çok ayarı ise varsayılan konumlarında bıraktım. Bu arada dosyanın yüklü olduğu klasör içerisine giremedim. Çünkü yetki hatası aldım. Ancak gedit aracını bu adresi işaret edecek şekilde kullandığımda yml dosyasını sorunsuz şekilde açabildim.

System Controller'dan yararlanarak Elasticsearch servisini tekrar başlatırsak değiştirdiğimiz konfigurasyon ayarlarının devreye alınmasını sağlayabiliriz. Başlattıktan sonra sağlık durumunu kontrol etmekte de yarar var. Bunları systemctl aracından yararlanarak gerçekleştirebiliriz.

sudo systemctl restart elasticsearch.service
sudo systemctl status elasticsearch.service

Testler

Servis yeni ayaları ile birlikte aktif durumda. Buna göre http://localhost:9205 adresine talepte bulunursak Elasticsearch'ün güncel durumu ile ilgili bilgi alıyor olmamız gerekir. JSON formatında dönen içerikte node ve cluster adları, lucene algortimasının kullanılan güncel versiyonu gibi bilgiler yer alır.

Eğer sistemde firewall kural listesi(ufw) etkinse 9205 adresine dışarıdan gelecek talepler için yeni bir tane eklemek gerekebilir. Ben Apache denemelerim sırasında aktif hale getirdiğim Firewall tanımlarını bu örnek kapsamında kapatmıştım. Gerçek hayat senaryolarında bu güvenlik ayarlarına dikkat edilmesinde yarar var.

Olayı biraz daha enteresanlaştıralım mı? Elasticsearch ile HTTP Get, Post, Put, Delete gibi metodları kullanarak iletişim kurmamız mümkün. Hatta aşağıdaki gibi bir POST talebi oluşturduğumuzu düşünelim.

Metod : HTTP Post
Resource : http://localhost:9205/westy/players/1001
Content-Type : application/json
Content : { "nickName": "bonza monza","level":"500","medal":4 }

Elasticsearch üzerinde westy isimli bir index, altında players isimli bir type oluşturduk.  Bu tip altında 1001 nolu bir doküman içerisine de örnek bir JSON içeriği yükledik. "İnanmassan gel de bak!" :)

Hatta oluşturulan bu içeriği HTTP Get ile çekebiliriz de;

Metod : HTTP Get
Resource : http://localhost:9205/westy/players/1001

Uuuuu beybiii! Çok keyifli.

Peki o zaman bir de PUT metodunu mu denesek? Yani bir güncelleme mi yapsak. Mesela Bonza Monza'nın madalya sayısını arttıralım.

Metod : HTTP Put
Resource : http://localhost:9205/westy/players/1001
Content-Type : application/json
Content : { "nickName": "bonza monza","level":"500","medal":5 }

ve tekrar Get ile 1001 numaralı veri içeriğini isteyelim.

Metod : HTTP Get
Resource : http://localhost:9205/westy/players/1001

Lütfen versiyon numarasına dikkat edin(Tabii bu benim ikinci denemem. O nedenle 3 oldu :D )

Aslında bir Delete işlemi de denenebilir ki bu kutsal görevi siz değerli okurlarıma bırakıyorum. Gördüldüğü üzere Elasticsearch'ün RESTful yapısından yararlanmak oldukça kolay. Kullandığımız uygulama platformuna bakmaksızın ona veri aktarabilir ve bu veriyi çekebiliriz. Farkındayım şu anda onu RESTFul bir veritabanı gibi kullandım ancak gerçek hayat senaryoları düşünüldüğünde taşlar yerli yerine oturuyor.

İki önemli konu var. Elasticsearch'ün doğru ayarlarla kurulması ve ne tür verilerle çalışılacağının belirlenmesi. Örneğimizi düşünecek olursak POST ile gönderilen taleplerde verinin aslı _source niteliği altında tutuluyor. Adres satırındaki değerlerde index, type gibi bilgileri belirliyor. Buraya pekala bir günlük log verisi de atılabilir, A firmasından gelen araştırmaya yönelik müşteri dataları da. Veri pekala parçalanarak farklı Cluster ve Node'lara da dağıtılabilir. İşte buradaki kurgular ve içeriğin belirlenmesi çok önemli. Index kurguları, tipler, dokümanlar için geçerli olacak Shard sayıları vs...Benim gibi acemiler için Elasticsearch doğrudan Log kayıtları üzerinde deneyimlenen bir arama motorundan öteye gidemiyor ama fazlası var. Örneğin siber saldırıların önceden hissedilmesi, seyahet eden insanların 360 derece görünümlerde hızlı arama yapması, devasa bir loglama sisteminin cloud üzerine servis olarak merkezileştirimesi gibi. Yazıda da belirttiğim gibi, bir bilene danışmak, bilenlerle tartışmak, bol bol okumak gerekli. Her öğrenilecek yeni şeyde olduğu gibi.

Serilog'u Devreye Alıyoruz

Gelelim yapmak istediğim bir diğer şeye. Serilog paketini kullanarak West-World'e henüz kurduğumuz Elasticsearch'e log atmak. Aslında işimiz son derece basit. Elimizde dummy bir Web API uygulaması olduğunu düşünelim. Serilog paketi'nin Elasticsearch için yazılmış kütüphanelerini kullanacağız. Dolayısıyla midleware tarafında bir takım ilave bildirimlerimiz olacak. Buna göre artık endüstüriyel standart haline gelmiş olan Log.Information, Log.Warning, Log.Exception gibi metod çağrımları ile Elasticsearch'e kayıt atabileceğiz. İşe Web API uygulamasını oluşturarak başlayalım.

dotnet new webapi -o ElasticSample

Ardından ihtiyacımız olan Serilog paketlerini yükleyerek ilerleyelim.

dotnet add package Serilog
dotnet add package Serilog.Extensions.Logging
dotnet add package Serilog.Sinks.ElasticSearch
dotnet restore

Tabii birde middleware tarafına müdahale etmemiz gerekiyor. Startup sınıfının yapıcısında Loglama stratejimizi Elasticsearch için ayarlamalıyız. İşte kodlarımız.

using Serilog;
using Serilog.Sinks.Elasticsearch;

namespace ElasticSample
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9205")) 
                {
                    AutoRegisterTemplate = true,
                    AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv6,
                    IndexFormat="api-log-{0:yyyy.MM}"
                })
                .CreateLogger();

            Configuration = configuration;
        }
// Kodun kalan kısmı

Minimum gereksinimle Elasitcsearch'e log atacağımızı belirttik. Normalde Elasticsearch, varsayılan ayarlarına göre 9200 nolu porttan hizmet verir ancak hatırlarsanız West-World'de bu ayarları değiştirmiştik. O nedenle ElasticsearchSinkOptions nesnesini örneklerken farklı bir adres söz konusu. Önem arz eden noktalardan birisi IndexFormat bilgisi. Bu değerle Elasticsearch üzerinde oluşturulacak indeksin adını tanımlıyoruz. Varsayılan olarak sisteme göre logstash gibi bir isimle başlıyor ancak değiştirmekte yarar var. Tutulacak log türlerine göre uygun bir isimlendirme de bulunmak lazım. Artık kodun herhangibir noktasından çalışam zamanı logları atabiliriz. Ben örnek olması açısından kobay ValuesController sınıfı içerisine bir kaç satır ekledim.

[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
    Log.Information("GET Request for ValuesController");
    Log.Error("Some error message");
    Log.Warning("Housten we have a problem");
    return new string[] { "value1", "value2" };
}

Sonrasında uygulamayı çalıştırıp Postman ile ValuesController'a bir kaç talep gönderdim. Bu durumda herbir çağrı için 3er Log mesajının yazılmış olması gerekiyor. Peki log indeksine nasıl ulaşacağız? Her şeyden önce ismini ezbere bilemeyebiliriz. Belki de denemelerimiz sırasında Elasticsearch üzerinde bir çok index oluşmuştur. Bunları nasıl göreceğiz? Bildiğiniz üzere Elasticsearch dışarıya güzel bir API sunuyor ;) Aşağıdaki talep ile sistemde var olan indexleri görmemiz mümkün.

HTTP Get 
http://localhost:9205/_cat/indices?v 

Görüldüğü üzere log için oluşturduğumuz index'de burada yer alıyor. Üstelik 3 doküman içermekte(3 tane log mesajı atmıştık) Dolayısıyla aşağıdaki taleple index hakkında bilgi alabiliriz.

HTTP Get
http://localhost:9205/api-log-2018.07 

Peki içerideki log bilgilerimizi nasıl göreceğiz? Yine Elastichsearch API'sinin _search metodundan yararlanabiliriz(_ ile başlayan komutlar Elasticsearch API'sine ait sorgulama metodlarıdır) 

HTTP Get
http://localhost:9205/api-log-2018.07 

Görüldüğü üzere API komutlarından yararlanarak oluşturulan log mesajlarını görebildik. Her ne kadar REST API her tür ihtiyacımızı karışılıyor olsa da gerçek hayat uygulamalarında artan log miktarı onları takip etmemizi zorlaştıracaktır. Şöyle göze hoş gelen, sadece Elasticsearch içindeki bilgileri takip etmekle kalmayıp ek fonksiyonellikleri ile uygulamaları izleyebileceğimiz bir arabirim olsa fena mı olurdu? Olmazdı tabii. İşte Kibana bu noktada devreye giriyor. 

Sisteminizi Kirletmek İstemezseniz

Yazının bu kısmı bir sonraki güne denk geldi. Bir sebepten West-World'e kurduğum Kibana, Elasitcsearch ile konuşamaz hale geldi. Sanırım bazı konfigurasyon ayarlarını bozdum. Onları düzeltmek için tekrardan kurulum yapmayı denemek yerine sistemi neden bu kadar kirlettiğimi düşünmeye başladım. Docker'ın hazır imajlarını kullansaydım ya :)) Eğer sizde sisteminizi kirletmek istemezseniz Elasticsearch ve Kibana için Docker taşıyıcılarını kullanabilirsiniz. Elasticsearh container'ını kurmak ve çalıştırmak için aşağıdaki terminal komutları yeterli.

sudo docker pull docker.elastic.co/elasticsearch/elasticsearch:6.3.1
sudo docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.3.1

Benzer şekilde Kibana container'ını da aşağıdaki komutlarla indirip çalıştırabiliriz.

sudo docker pull docker.elastic.co/kibana/kibana:6.3.1
sudo docker run --net=host -e "ELASTICSEARCH_URL=http://localhost:9200" docker.elastic.co/kibana/kibana:6.3.1

Her iki container birbiriyle haberleşebilir durumdadır. Varsayılan olarak elasticsearch 9200 portundan hizmet verecektir. Bu nedenle Kibana örneğini çalıştırırken ELASTICSEARCH_URL'ini 9200 portuna göre vermek gerekir. Sonuç olarak http://localhost:5601 adresinden Kibana'ya da ulaşılabilir. West-World'de önce Elasticsearch'ü, sonra Kibana örneklerini çalıştırdıktan sonra yukarıda hazırladığımız Web API servisini kullandım. Ah bu arada port bilgisi değiştiği için Startup içerisindeki 9205 değerini tekrardan 9200'e çekmem gerekti. Elbette docker örneklerinin ilgili yml içeriklerini kurcalayarak bu bilgiler değiştirilebilir ama ben şimdilik varsayılan halleri ile bırakıyorum(yemedi :P) 

Üst terminal kibana, alt terminal ise elasticsearch container örneklerinden gelen log'ları göstermekte. Postman ile servise yapılan bir kaç talep sonrası hemen tarayıcıyı açıp http://localhost:5601 adresinin yolunu tuttum. Kibana'ya bu şekilde bağlandıktan sonra aslında izlemek istenen Elasticsearch index'leri için bir desen oluşturulması gerekir. Hatırlanacağı üzere log bilgisini yazarken Serilog'a index adının nasıl olacağını da söylemiştik. Bu senaryo için api-log-2018-07 şeklindeydi. Dolayısıyla api* gibi bir desen kullanarak bu ve benzer isimlere uygun tüm index'lerin Kibana'dan izlenmesini sağlayabiliriz.

Ekran görüntüsündeki adımdan sonra da belki bir filtre belirleyebiliriz. Ben log atılma zamanına göre bir filtre ekleyip Create Index Pattern tuşuna basarak devam ettim.

Artık atılmış olan deneme log'larını izlemek için sadece Monitoring özelliğini açmamız yeterli.

Sonuçta aşağıdaki gibi şık rapor ekranlarına ulaşmış olacağız.

Monitoring kısmında daha farklı bir görünüm,

ve discover kısmında bir görünüm...

Dikkat edileceği üzere arabirimler gayet güzel. Şu anda firmadaki uygulamaların loglama mesajlarını docker üzerinde yaşayan Elasticsearch ortamlarına atacak ve Kibana ile izlememizi sağlayacak değişikliklere başlamak istedim. Docker bu noktada hayatımızı oldukça kolaylaştırdı. Benim için hazırlanması bir kaç güne denk gelen yorucu bir makale oldu diyebilirim. Lakin oldukça keyif aldığımı ifade etmek isterim. Umarım sizler için de bilgilendirici bir yazı olmuştur. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Dip Not

Olur da docker container'larını durduramazsanız terminalden şu komut ile akfit olanları çekip

sudo docker ps

durdurmak istediğiniz için bu komutu(Sizde Container ID numarası farklı olacaktır)

sudo docker stop 3402e6aaced3

kullanabilirsiniz.

Eğlenceli SignalR

$
0
0

Merhaba Arkadaşlar,

Önce üç yumurtayı derin kabın içerisine kırıp el mikseri ile güzelce karıştırdım. Bir su bardağının 4te 3ü kadar şeker ilave edip karıştırmaya devam ettim. Şeker iyice eriyene kadar. Sonra bir su bardağı kadar zeytinyağı, iki su bardağı kadar esmer un katıp(unu önce el eleğine aldım oradan kaba boşalttım. Unu havalandırarak eklemek daha iyi sonuç veriyor çünkü) kulak memesi kıvamına gelinceye kadar çırpmaya devam ettim. Kıvamı ayarlamak için yer yer göz kararı bir miktar daha un eklemem de gerekti. Acemilik tabii...

Bir süre sonra doktor ötker'in kabartma ve vanilya tozlarından birer paketi kaba boşalttım. Kıvam istediğim hale geldikten sonra rendeledeğim limon kabuklarını ve suyunu döküp çok az daha karıştırdım. Karışımı döktüğüm kabı yüksek ısıya dayanıklı eldivenlerimle tuttum ve 180 derece sıcaklığa ayarladığım fırının üst katına yerleştirdim. Yarım saat kadar bekledim ve soununda üstü kahverengileşmiş, hafiftçe de kabarmış limonlu kekim hazırdı. Onu büyük bir keyifle yapmıştım. Tarif önümde hazırdı ve bu nedenle yapması oldukça kolaydı. Ancak mutluluğumun asıl sebebi keki hazırlamaya başlamadan önce bitirdiğim örnek uygulamaydı.

Bu yazımızda, Chart.js kütüphanesini kullanarak tarayıcı üzerindeki bir grafiğin SignalR üzerinden nasıl beslenebileceğini incelemeye çalışacağız. Bunu yaparken Chart.js isimli Javascript kütüphanesinin nefis özelliklerini kullanacağız ama daha da önemlisi verilerin belirli periyotlarla istemci tarafına akmasını ve grafik üzerinden anlık(SignalR kullanmamızın bir sebebidir) olarak izlenebilmesini sağlayacağız. Tarifimiz pratik ve uygulaması kolay.

Öncelikle senaryomuzdan bahsedelim. Kurum için kullanılan servislerin anlık olarak karşıladıkları talep bilgilerini gerçek zamanlı olarak raporlamak istiyoruz. Raporlama aracımız basit bir HTML sayfası üzerindeki çizgi grafiği(Line chart) Bu grafiğin anlık olarak(saniyede bir örneğin) değişmesini istiyoruz. Önemli noktalardan birisi grafiğin ihtiyaç duyacağı veriyi dinlemede olan istemcilere sunmak. Burada SignalR'dan yararlanabiliriz. 

Buna göre örneğimiz üç uygulamadan oluşacak. Birisi Hub rolünü üstlenecek ve veri akışına aracılık edecek. Bu tahmin edeceğiniz üzere bir Web uygulaması olacak ve grafik fonksiyonelliğini içeren HTML içeriğini de barındıracak(ama istemci aynı uygulama üzerinde olmak zorunda değil tabii) Bir diğer uygulama ise veri beslemesi için kullanılacak. Yani Hub'ı dinleyen istemcilere rastgele veriler sunacak. Onu Console olarak tasarlayabiliriz. Her iki uygulamanın ihtiyaç duyduğu verileri tanımlayan ortak bir sınıf kütüphanesi de üçüncü projemiz olarak karşımıza çıkacak. Dilerseniz hiç vakit kaybetmeden başlayalım.

Önce veri modelini içeren projemizi oluşturalım.

dotnet new classlib -o ServiceSensor.Common
dotnet add package Newtonsoft.json

ServiceSensor.Common kütüphanesi HealthInformation isimli bir sınıf içermekte ve JSON serileşmesinde alan adlarının nasıl olacağı ifade edilmekte. Bunu belirtmezsek istemci tarafında name ve level(küçük harf ile başlıyorlar) isimlerini kullanmamız gerekir. Zorunluluk değil ama standartlara uyum açısından JsonProperty kullanmak iyi olur.

using System;
using Newtonsoft.Json;

namespace ServiceSensor.Common
{
    public class HealthInformation
    {
        [JsonProperty("serviceName")]
        public string Name { get; set; }
        [JsonProperty("healthLevel")]
        public int Level { get; set; }
        public override string ToString() => $"{Name} [{Level}]";
    }
}

HealthInformation nesne örnekleri servis adı ve o anki seviyesini(anlık durumunu işaret eden bir gösterge gibi düşünelim) tutmak için planlandılar. Şimdi sunucu tarafını geliştirelim.

dotnet new web -o ServiceSensor.House
dotnet add package Microsoft.AspNetCore.All
dotnet add reference ..\ServiceSensor.Common\
dotnet build

Öncelikle ServiceSensor.House isimli boş bir Web projesi oluşturuyoruz. Sonrasında SignalR yeteneklerinden faydalanmak için bir paket ekliyoruz(.Net Core'un son sürümünde buna gerek olmayabilir lütfen kontrol edin) Pek tabii HealthInformation sınıfını kullanabilmek için de ServiceSensor.Common kütüphanesinin referans edilmiş olması lazım. İlk olarak Hub sınıfını tasarlayalım.

using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using ServiceSensor.Common;

namespace ServiceSensor.House
{
    public class HealthSensorHub
        : Hub
    {
        public Task Broadcast(string sender, HealthInformation information)
        {
            return Clients.AllExcept(new[] { Context.ConnectionId })
                .SendAsync("Broadcast", sender, information);
        }
    }
}

HealthSensorHub sınıfının temel görevi bağlı olan tüm istemcilere parametre olarak gelen information içeriğini asenkron olarak göndermek. Hub türevli olduğu için bu yeteneğe haiz. Üst sınıftan Clients özelliğini kullanıyoruz. AllExcept nedeniyle gelen tüm istemci talepleri kabul edilmekte ve bağlananların her birine Broadcast takma adı üzerinden iki bilgi gönderilmekte. Sender ve Information. Gelelim program sınıfının kodlarına.

using System.IO;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace ServiceSensor.House
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseStartup<Startup>()
                .UseUrls("http://localhost:7001");
    }
}

Aslında root klasörün kullanılacağını(sonradan index.html ekleyeceğiz) ekledik ve web uygulamamızın http://localhost:7001 adresinden yayın yapacağını ifade ettik. Startup.cs içeriğini de aşağıdaki gibi tasarlamamız gerekiyor.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace ServiceSensor.House
{
    public class Startup
    {
       public void ConfigureServices(IServiceCollection services)
        {
            services.AddSignalR();

            services.AddCors(o =>
            {
                o.AddPolicy("All", p =>
                {
                    p.AllowAnyHeader()
                        .AllowAnyMethod()
                        .AllowAnyOrigin();
                });
            });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseFileServer();
            app.UseCors("All");

            app.UseSignalR(routes =>
            {
                routes.MapHub<HealthSensorHub>("/healthSensor");
            });
        }
    }
}

İlk olarak SignalR servisini çalışma zamanına ekliyoruz. Cross-Origin Resource Sharing(CORS) ihlallerine takılmamamk için eklediğimiz birkaç kod parçası daha var. SignalR için gerekli route işaretlemesini yaparken HealthSensorHub sınıfının kullanılacağını belirtiyor ve healthSensor(istemci tarafı SignalR bağlantısını kurabilmek için bu path bilgisini kullanacak) isimli adres bilgisini veriyoruz.

Web uygulamamıza chart.js kütüphanesini ve HTML dosyasını eklemek için yeniden döneceğiz. Ancak öncesinde bağlı olan istemcilere veri sağlayacak uygulamamızı geliştirerek devam edelim. Bunu bir Console uygulaması olarak geliştirebiliriz. Tek yapacağı belirli periyotlarla servislere ait güncel sağlık bilgilerini göndermek olacak ki işimizi kolaylaştırmak için rastgele değerler ürettireceğiz.

dotnet new console -o ServiceSensor.Publisher
dotnet add package Microsoft.AspNetCore.SignalR.Client
dotnet add reference ..\ServiceSensor.Common\
dotnet build

SignalR ile konuşacağımız için Microsoft.AspNetCore.SignalR.Client paketini eklememiz gerekiyor. Artık program sınıfının kodlarını yazabiliriz.

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client;
using ServiceSensor.Common;

namespace ServiceSensor.Publisher
{
    class Program
    {
        static void Main(string[] args)
        {
            var cToken = new CancellationTokenSource();
            Task.Run(() => SendInformationAsync(cToken.Token)
                            .GetAwaiter()
                            .GetResult(), cToken.Token);

            Console.WriteLine("Sonlandırmak için bir tuşa basın...");
            Console.ReadLine();
            cToken.Cancel();
        }

        static async Task SendInformationAsync(CancellationToken cToken)
        {
            var connBuilder = new HubConnectionBuilder()
                .WithUrl("http://localhost:7001/healthSensor")
                .Build();

            await connBuilder.StartAsync();
            Random rnd = new Random();
            int randomHealthPoint = 0;

            while (!cToken.IsCancellationRequested)
            {
                await Task.Delay(1000, cToken);
                randomHealthPoint = rnd.Next(1, 25);
                var information = new HealthInformation() 
                    {
                         Name = $"service_{randomHealthPoint}", 
                         Level = randomHealthPoint
                     };
                Console.WriteLine(information.ToString());
                await connBuilder.InvokeAsync("Broadcast", "HealthSensor", information, cToken);
            }

            await connBuilder.DisposeAsync();
        }
    }
}

Programın temel görevi kullanıcı kesene kadar asenkron bir işin yürütülmesini sağlamak. SendInformationAsync içerisinde ise önemli işler icra ediliyor. Öncelikle http://localhost:7001/healthSensor adresini kullanan bir HubConnecton nesnesi örnekleniyor. StartAsync metodu ile iletişim başlatılıyor. Kurulan sonsuz döngü içerisinde 1 saniyelik aralıklarla rastgele HealthInformation nesneleri örneklenmekte. Bu nesneler InvokeAsync metodundan yararlanılarak Hub'a bağlı olan istemcilere dağıtılıyor. Kullanıcı SendInformationAsync fonksiyonunu kesene kadar bu gönderim işlemi devam edecek.

Artık ServiceSensor.House isimli Web uygulamamıza yeniden dönebiliriz. Öncelikle grafik için chart.js ve SignalR tarafı ile konuşmak için de aspnet/signalr paketlerini yüklememiz lazım. Bu yüklemeler içn npm aracından yararlanabiliriz.

npm install chart.js --save
npm install @aspnet/signalr --save

Paketler varsayılan olarak tüm içerikleri ile birlikte inerler. Ancak ihtiyacımız olan dosyalar sadece chart.js ve signalr.js. Bu dosyaları alıp wwwroot altında açacağımız scripts klasörü içerisine kopyalayabiliriz. Gelelim eğlenceyi oluşturacak kısıma. wwwroot altındaki index.html dosyasını aşağıdaki gibi kodlayarak devam edelim.

<html><head><meta charset="utf-8" /><title>Service Health Sensor Sample</title><script src="scripts/chart.js"></script><script src="scripts/signalr.js"></script><script type="text/javascript">

        document.addEventListener('DOMContentLoaded', function () {
            var samples = 100;
            var speed = 300;
            var values = [];
            var labels = [];
            var charts = [];
            var value = 0;
            values.length = samples;
            labels.length = samples;
            values.fill(0);
            labels.fill(0);

            var chart = new Chart(document.getElementById("serviceChart"),
                {
                    type: 'line',
                    data: {
                        labels: labels,
                        datasets: [
                            {
                                data: values,
                                backgroundColor: 'rgb(102, 178, 255)',
                                borderColor: 'rgb(0, 102, 0)',
                                borderSize: 3
                            }
                        ]
                    },
                    options: {
                        responsive: false,
                        animation: {
                            duration: speed * 1,
                            easing: 'easeInQuad'
                        },
                        legend: false,
                        scales: {
                            xAxes: [
                                {
                                    display: false,
                                }
                            ],
                            yAxes: [
                                {
                                    ticks: {
                                        max: 30,
                                        min: 0
                                    }
                                }
                            ]
                        }
                    }
                });

            var hubConn = new signalR.HubConnectionBuilder()
                .withUrl("healthSensor")
                .build();
            hubConn.on('Broadcast',
                function (sender, info) {
                    values.push(info.healthLevel);
                    values.shift();
                    labels.push(info.serviceName);
                    labels.shift();
                    chart.update();
                });
            hubConn.start();
        });
    </script></head><body><canvas id="serviceChart" style="width: 600px; height: 440px"></canvas></body></html>

Açkçası chart kullanımını bulduğum örnekler üzerinden yapmaya çalıştım(chart.js çok kapsamlı ve geniş bir kütüphane. Değişik örneklerine ulaşmak için şu adrese uğranabilir) Ancak grafiğin veri için kullandığı values ve labels değişkenlerini nasıl beslediğimizi anlatabilirim. hubConn değişkenine dikkat edelim. Önce ServiceSensor.Publisher uygulamasındakine benzer bir şekilde nesne örneklemesi yapılmakta. withUrl parametresinin değeri önemli. on fonksiyonu start sonrası bağlantı bsaşarılı bir şekilde sağlanırsa devreye girecek. Yani istemci dinleme moduna geçecek. Broadcast anahtar kelimemiz(Önceki kodlarda nerede kullanıldığını hatırlayın)İsimsiz fonksiyona gelen parametreler tahmin edeceğiniz üzere Publisher tarafından JSON'a dönüştürülerek gönderilen HealthInformation değerlerini içeriyor. JsonProperty'lerde belirttiğimiz isimlerle anlık verilere ulaşmamız mümkün. push ile veriyi grafiğe basıyor, shift ile o anki barı yana kaydırıyor ve update ile kaynak veri setinin güncellenmesini sağlıyoruz. Hepsi bu kadar. Artık testimize başlayabiliriz. İlk olarak ServiceSensor.House uygulamasını, ardından da veri basan ServiceSensor.Publisher uygulamasını 

dotnet run

komutları ile başlatmalıyız. Publisher belirli aralıklarla rastgele bilgi basacak. Sunucu uygulamanın çalışmasına ait örnek ekran görüntüsü aşağıdaki gibi.

Publisher uygulamasına ait bir ekran görüntüsü de şu. Görüldüğü üzere Console'a düşen bilgilerden rastgele üretilen verileri izleyebiliriz.

Artık tarayıcıdan http://localhost:7001/index.html adresine gidebiliriz. Grafik sağdan sola doğru gelen değerlere göre akmaya başlayacaktır. Bunu canlı canlı izlemek çok zevkliydi benim için. Hatta video kaydını alıp paylaşmak da istedim ama onu görmek için örneği tamamlamaya gayret etmenizin daha faydalı olabileceğini düşündüm. Sizde görmek istiyorsanız örneği tamamlamalısınız :D Şimdilik iki ekran görüntüsü paylaşarak az biraz heyecan yaratayım.

ve

Örnek daha da zenginleştirilebilir. Grafiği değiştrerek işe başlayabilirsiniz. Servislere ait anlık verilerin gerçekten de takip edilmek istenen servislerden beslenmesine çalışabilirsiniz. Tüm istemcilerin değil de sadece yetki bazlı olanların bu grafiğe ulaşmasını sağlayacak güvenlik tedbirlerini deneyebilirsiniz. Servis durumları yerine IoT cihazınıza bağladığınız bir takım sensör verilerinin bu grafiğe yansımasını da sağlayabilirsiniz ;) Neler yapabileceğiniz tamamen sizin hayal gücünüze bağlı.

Bu yazımızda SignalR'ı kullanarak gerçek zamanlı basılan verilerin, dinlemede olan istemcilerde grafiksel gösteriminin nasıl yapılabileceğini incelemeye çalıştık. Ve böylece adım adım yaparken bir kek kadar lezzetli olmasa da çok keyif alacağınızı düşündüğüm bir makalemizin daha sonuna geldik. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Örneğe github üzerinden de ulaşabilirsiniz. 

Docker SQL Server Kullanımı

$
0
0

Merhaba Arkadaşlar,

Geçenlerde Guinness Dünya Rekorları kitabının 2018 baskısını aldım. Bu zamana kadar kayıt altına alınmış bir çok rekorun yer aldığı resimli ansiklopedi de ilgimi çeken değişik bölümler oldu. "Derinler" başlıklı kısmı okurken aklım dünyanın ilginç ve bir o kadar da zorlu mesleklerine gitti. Mesela derin sualtı dalgıcı olduğunuzu düşünsenize. İkinci dünya savaşında Norveç'in soğuk kuzey deniz sularının 245 metre derinine gömülmüş bir savaş gemisine batık dalışı yapan 12 dalgıçtan birisi olabilirdiniz. Her ne kadar 1981'de 31 gün içinde 431 külçe altın çıkartmış olsanız da kolay iş değil. Bunun gibi bir çok zorlu meslek var düşününce. Astrontlar, madencilikle uğraşanlar, yüksek gökdelen camlarını temizleyenler, İstanbul boğazındaki köprülerin bağlantı kablolarına tadilat yapanlar, hayvanat bahçelerindeki timsahlara bakanlar, bomba imhası ile uğraşan uzmanlar ve diğerleri.

Bu meslek gruplarını düşünürkene dedim bir kaç tane atmasyon iş ilanı çıkayım. Astronot, dalgıç arayayım. Bunları West-World'de SQL Server veritabanında bir Web API ile yöneteyim dedim. Sonra gün gelir kullanıcı dostu arayüzü olan bir web uygulaması yazarım dedim. West-World'e daha önceden Microsoft SQL Server sürümünü kurmuş ve basit denemeler yapmıştım. Bu yeni çalışma için de onu kullanabilirdim ama şart değildi. SQL Server'ın Linux platformuna özel bir Docker imajını da kullanabilirdim. Hem benim için de bir değişiklik olurdu. Docker bu. Alışınca vazgeçemiyor insan.

İşte bu yazıda docker imajını baz alarak, Entity Framework çatısını kullanan basit bir Web API servisini nasıl geliştirebileceğimizi öğrenmeye çalışacağız. Vakit kaybetmeden başlayalım mı? Adımlarımız çok çok çok basit. İşe Sql Server'ın linux platformu için kullanılabilir docker imajını yükleyerek başlamak lazım. Bunun için West-World(Ubuntu 16.04 - 64 bit)üzerinde aşağıdaki terminal komutunu kullandım(Yazı yayınlandığında kullanılan linux sql server versiyonu farklı olacaktır)

sudo docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=P@ssw0rd' -p 1433:1433 -d microsoft/mssql-server-linux:2017-CU8

Şu ana kadar West-World'e böyle bir docker imajı yüklemediğimden hub'dan ilgili dosyaların indirilmesi gerekti. İmaj dosyası büyük olsa da kısa bir beklemeden sonra yükleme işlemleri tamamlanmıştı.

İmajın başarılı bir şekilde yüklendiğinden emin olmak için  "docker images -all" terminal komutundan yararlandım.

Artık bu imajı kullanacak bir Web API uygulaması geliştirmeye başlayabilirdim. Kodları yazmak için Visual Studio Code'un başına geçtim. SQL server tarafıyla konuşmak için Entity Framework'ün .Net Core sürümünü kullanmaya karar verdim. İşte kullandığım terminal komutları. Web Api projesini oluştur, EF paketini ekle, bir kere build et.

dotnet new webapi -o AdventureJobs
dotnet add package EntityFramework --version 6.2.0
dotnet build

Bir sonraki adımda model sınıflarını yazmaya başladım. Tipik olarak bir entity sınıfı ve DbContext türevi yazmayı planlamıştım. Model klasöründe yer alan Job isimli sınıf çeşitli iş ilanlarına ait özet bilgileri tutmakla yükümlü.

using System.ComponentModel.DataAnnotations;

namespace AdventureJobs.Models
{
    public class Job
    {
        public int Id { get; set; }
        [Required]
        public string Title { get; set; }
        [Required]
        public string Description { get; set; }
        [Required]
        public decimal Salary { get; set; }
        [Required]
        public string City { get; set; }
    }
}

Job sınıfı identity tipindeki Id özelliği haricinde mutlaka değer içermesi gereken özelliklere de sahip(Required niteliğini-attribute bu nedenle kullandık) ManagementContext isimli DbContext türevli sınıfı da aşağıdaki gibi yazdım.

using Microsoft.EntityFrameworkCore;

namespace AdventureJobs.Models
{
    public class ManagementContext
    : DbContext
    {
        public ManagementContext(DbContextOptions<ManagementContext> options)
            : base(options)
        {
            this.Database.EnsureCreated();
        }

        public DbSet<Job> Jobs { get; set; }
    }
}

Tipik bir Context sınıfı söz konusu. Yapıcı metod(Constructor) içerisinde veritabanının var olduğundan emin olunuyor. Eğer veritabanı mevcut değilse oluşturulacak. Pek tabii bir de Controller sınıfına ihtiyacım vardı. Controllers klasörü içerisindeki JobController sınıfını da aşağıdaki gibi geliştirdim.

using System.Linq;
using Microsoft.AspNetCore.Mvc;
using AdventureJobs.Models;

namespace AdventureJobs.Controllers
{
    [Route("api/[controller]")]
    public class JobsController : Controller
    {
        private readonly ManagementContext _context;

        public JobsController(ManagementContext context)
        {
            _context = context;
        }

        [HttpGet]
        public IActionResult Get()
        {
            var model = _context.Jobs.ToList();
            return Ok(new { jobs = model });
        }

        [HttpPost]
        public IActionResult Create([FromBody]Job entity)
        {
            if (!ModelState.IsValid)
                return BadRequest(ModelState);

            _context.Jobs.Add(entity);
            _context.SaveChanges();

            return Ok(entity);
        }

        [HttpPut("{id}")]
        public IActionResult Update(int id, [FromBody]Job entity)
        {
            if (!ModelState.IsValid)
                return BadRequest(ModelState);
                
            var job = _context.Jobs.Find(id);

            if (job == null)
                return NotFound();

            job.Title = entity.Title;
            job.Description = entity.Description;
            job.City=entity.City;
            job.Salary=entity.Salary;

            _context.SaveChanges();

            return Ok(job);
        }

        [HttpDelete("{id}")]
        public IActionResult Delete(int id)
        {
            var job = _context.Jobs.Find(id);

            if (job == null)
                return NotFound();

            _context.Remove(job);
            _context.SaveChanges();

            return Ok(job);
        }
    }
}

JobsController sınıfı temel CRUD operasyonlarını barındırmakta. Tüm iş listesinin çekilmesi, veritabanına yeni bir işin eklenmesi, var olan bir işin güncellenmesi veya silinmesi operasyonlarını yürütmekten sorumlu. Artık tek yapılması gereken Startup.cs içerisinde docker üzerinden servis veren SQL server için gerekli bağlantı(Connection) bilgisini bildirmek. Bunun için aşağıdaki yolu izledim(SQL Sunucusu, kullanıcı adı ve şifre bilgilerini koddan değil de bir çevre değişkeninden almak daha doğru olacaktır. Siz öyle yapın.)

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().
SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    var conStr = $"Data Source=localhost;Initial Catalog=AdventureJobsDb;User ID=sa;Password=P@ssw0rd;";
    services.AddDbContext<ManagementContext>(options => options.UseSqlServer(conStr));
}

Testlere başlama zamanı gelmişti. Uygulamayı "dotnet run" terminal komutu ile başlattıktan sonra Postman'den bir kaç talep göndererek denemeler yaptım. İlk olarak http://localhost:5001/api/jobs adresine bir HTTP Get talebinde bulundum. Bu ilk çalıştırılma ve AdventureJobsDb veritabanı ortada yok. Terminal satırına düşen log bilgilerine baktığımda veritabanı ve ilgili tabloların oluşturulması için gerekli komutların başarılı bir şekilde işletildiğini gördüm.

Hatta HTTP Get talebine uygun olarak bir SELECT sorgusu da gönderilmişti. Tabii şu anda hiçbir iş bilgisi olmadığından boş bir veri seti ile karşılaşmam son derece normaldi.

Talep : HTTP Get
Adres : http://localhost/api/jobs

Bunun üzerine bir kaç iş ekleyeyim dedim. Yetiştirilmek üzere bir astronot, karayiplerdeki İspanyol kalyonlarına derin dalış yapacak deneyimli bir balık adam ve Top Gear programındaki Stig'in yerini alacak usta bir şoför arayışım vardı. HTTP Post ile ve iş bilgilerini Body kısmında JSON formatında göndermek yeterliydi. 

Talep : HTTP Post
Address : http://localhost:5001/api/jobs
Body Content Type : application/json
Content : {"id": 1,"title": "Dalgıç","description": "Batıklara dalacak deneyimli dalgıç","salary": 10000,"city": "Karayipler"}

Her POST talebi sonrası terminale düşen loglara bakmayı da ihmal etmedim. Buradan SQL Server'a gönderilen INSERT komutlarını görebiliyordum. Sonunda HTTP Get ile elime bir kaç anlamlı iş ilanı geçmişti.

Bunun üzerine Update ve Delete operasyonlarını da denedim. Aşağıda örnek kullanımıları bulabilirsiniz.

İş güncelleme örneği;

Talep : HTTP Put
Address : http://localhost:5001/api/jobs/2
Body Content Type : application/json
Body : {"title": "Astronot","description": "Yetiştirilmek üzere astronot adayları aranıyor.","salary": 20000,"city": "Houston"}

İş silme örneği;

Talep : HTTP Delete
Address : http://localhost:5001/api/jobs/2

Bunlar da başarılı şekilde çalışıyordu.

Yazdığım Web API, Linux üzerinde çalışabilir bir SQL Server örneğinin docker versiyonunu kullanmakta. Vakti zamanında West-World'e bir SQL Server örneği kurmama bile gerek yokmuş aslında :) Buna ek olarak SQL Server veritabanı ile çalışmak için uygulama yazmak zorunda da değildim. SQL server'a terimalden de bağlanılabilir. Aşağıdaki ilk komut bunun için yeterli. Sonrasında temel SQL komutlarından yararlanılıyor. Ben aktif veritabanını değiştirip basit bir SELECT sorgusu çalıştırdım.

sudo docker exec -it 40af8fedc80b /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P P@ssw0rd
use AdventureJobsDb
go
select * from Jobs
go

(-it komutundan sonra SQL Server container ID bilgisi geliyor ancak buraya ismini de yazabiliriz. :setvar ile girilen değerler terminaldeki select çıktılarını daha kolay okumak için kullanılıyor)

Bir Cumartesi gecesi çalışmasının daha sonuna gelmiş bulunuyorum. Docker ile zahmetsizce ulaştığım SQL imajında güzel işler yapılabileceğini görmüş oldum. Bundan sonra West-World'e bir şey kurarken iki kere düşüneceğim. İlk olarak Docker imajı var mı ona bakacağım. Böylece geldik kısa bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Bir Web API Servisi Nasıl Hata Döndürmeli ?

$
0
0

Merhaba Arkadaşlar,

Bir süre öncesine kadar şirkette yeni bir lakabım vardı. "Bad Request Burak" :) Devraldığımız bir kaç projede teknik borçlanmadan ötürü oluşan kirli kodlarla boğuşurken çok fazla detay vermeyen HTTP 400 hataları ile karşılaşıyordum. Bazen bir tanesini çözerken bir başkasının doğmasına neden olacak değişikliklere de sebep olabiliyordum. Hatta hesapta olmayan bu hataların neden oluştuğunu tespit etmek için harcanan zaman kaybı nedeniyle diğer görevlerim de aksıyordu. Bir süre sonra "Bad Request" ünvanı ile hatırlanır oldum :) Hatta bulaşıcı hale gelen bu ünvan ekibimdeki başka arkadaşlara da yayılıverdi. Neyse ki işleri sonunda yoluna koyduk (Her ne kadar ünvanım " Burak 200" olarak değişmese de :D) 

Neden böyle oluyordu. Bu işin bir orta noktası yok muydu? Bir Web API servisi gerçekte nasıl mesaj döndürmeliydi? İşte hata mesajlarını verirken bile bazı kritrleri göz önüne alarak evresenl bir takım standartları uygulamak gerekiyor. Aslında standartlar yazılım geliştirme yaşam döngüsünde konulması ve uygulanması zor olan kavramların başında geliyor. Özellikle endüstüriyel çözümleri göz önüne aldığımızda çeşitli toplulukların kabul gördüğü ve uygulanmasını beklediği standartlara bağlı kalmak da önem arz ediyor. IETF(Internet Engineering Task Force) bu anlamda standart koyuculardan birisi olarak karşımıza çıkmakta. Geçtiğimiz günlerde Web API servisleri ile ilgili durum kodlarının nasıl olması gerektiğini araştırırken güzel bir RFC dokümanına rastladım. Her ne kadar 2016 yılında yazılmış olsa da güncel ve geçerli bir standart olduğu aşikardı.

RFC 7807 maddesinde bir HTTP Web API servisinden hata dönüleceği zaman nasıl bir şablonda olması gerektiğine dair bilgiler yer alıyordu. Konunun özünde Web API servislerinden makinelerin okuyabileceği format ve detayda hata mesajlarının döndürülmesinden bahsediliyor. Buradaki en büyük amaç olası yeni hata kodları için farklı tipte Response içerikleri uydurmayıp belli bir standarda bağlı kalabilmek. Lakin bu gerçekleştirildiği takdirde 7807 standardına uygun her çıktı makineler veya botlar tarafından otomatik olarak algılanabilecek. Bu loglama stratejilerinde, alarm araçlarında otomatikleştirilmiş süreçlerin de çalışabileceği anlamına geliyor. 

Peki konuyu örneklemeye çalışırsak nasıl bir standarttan bahsedilmekte? Diyelim ki arayüzdeki bir fonksiyon bir bankacılık işlemi için(örneğin para transferi olsun) Web API çağrısı yapmakta. Ancak müşterinin bakiyesi bu işlemi gerçekleştirmek için yeterli değil. IETF'ye göre buradaki durum çıktısının aşağıdaki gibi olması öneriliyor.

HTTP Durumu,

HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Content-Language: en

Mesaj çıktısı,

{
    "type": "https://example.com/probs/out-of-credit",
    "title": "You do not have enough credit.",
    "detail": "Your current balance is 30, but that costs 50.",
    "instance": "/account/12345/msgs/abc",
    "balance": 30,
    "accounts": ["/account/12345", "/account/67890"]
}

Tabii burada dikkat edilmesi gereken iki önemli konu var. Standarda göre type, title, detail, status ve instance nitelikleri çıktıda kullanılabilecek olan detaylar. Bunara ek olarak dilersek konuya özel genişletmeler ile ilave bilgiler de verebiliriz. Örnek çıktıdaki balance ve accounts bu şekilde eklenmişler. En basit haliyle type, title ve status niteliklerinin çıktıda yer alması gerekiyor.

Gördüğünüz üzere RFC, çıktının içinde hangi niteliklerin olabileceğini belirtmekte. type, title, status, detail ve instance makinelerin otomatik olarak ilgilenecekleri alanlar. Peki .Net dünyasında geliştirdiğimiz bir Web API servisinden bu şekilde çıktıları nasıl döndürebiliriz? Aslında JSON içeriklerini oluşturmak oldukça kolay. Lakin MVC'nin .Net içine gömülmüş standart Unauthorized gibi tipler için bu davranış şeklinde kökten değiştirmek daha iyi olabilir. Bu noktada işimizi kolaylaştıracak Nuget paketleri de var. Bunlardan birisi Kristian Hellang tarafından yazılmış ve şu adreste yer alıyor.

Şimdi basit bir örnek ile konuyu deneyimleyelim. Başlangıç olarak aşağıdaki komut ile .Net Core tarafında bir Web API servisi oluşturdum.

dotnet new webapi -o DummyDataApi

Sonrasında hazır olarak gelen ValuesController sınıfını aşağıdaki gibi değiştirdim.

using Microsoft.AspNetCore.Mvc;

namespace DummyDataApi.Controllers
{
    [Route("api/[controller]")]
    public class BrandController : Controller
    {
        [HttpGet("{tokenid}")]
        public IActionResult GetBrands(string tokenid)
        {
            string[] brands={"abibas","nayk","niüv balanse"};
            if(Validate(tokenid))
                return Ok(brands);
            else
                return Unauthorized();
        }

        private bool Validate(string tokenid)
        {
            return false;
        }           
    }
}

GetBrands isimli fonksiyon parametre olarak gelen tokenid değerine göre bir liste döndürmekte. Eğer geçerli bir tokenid değeri söz konusuysa HTTP 200 statüsünde markaların listesini döndürüyoruz. Ancak aksi durumda(ki örnekte bunu bilinçli olarak yapmaktayız) Unauthorized durumunu göndermekteyiz. Buna göre örneği Postman üzerinden yaptığım denemenin çıktısı aşağıdaki gibi oldu.

Şimdi IETF'nin önerdiği formatta bir mesaj döndürmeye çalışacağız. Bunun için öncelikle ilgili paketi projeye eklemek gerekiyor.

dotnet add package Hellang.Middleware.ProblemDetails --version 1.0.0

Paketi yükledikten sonra bu servisin kullanılacağını kod tarafında belirtmeliyiz. Bir başka deyişle yeni Middleware'i çalışma zamanına bildirmeliyiz. Bunun için Startup.cs dosyasındaki Configure metoduna aşağıdaki ilaveyi yapmamız yeterli.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
	if (env.IsDevelopment())
	{
		app.UseDeveloperExceptionPage();
	}
	app.UseProblemDetails();
	app.UseMvc();
}

Artık Unauthorized çağrısında yeni servis devreye girecek. Postman'den aldığım cevap şöyle oldu.

Görüldüğü üzere tam da istenen formatta bir çıktı söz konusu. Tabii type,title,status,detail,instance gibi bilgileri değiştrebilir ve ek bilgileri de buraya dahil edebiliriz. Bunun için ProblemDetails tipinden bir sınıf türetmemiz gerekiyor. Örneğimiz için aşağıdaki gibi bir sınıfı kullanabiliriz.

using System;
namespace DummyDataApi.Controllers
{
    public class UnauthorizedTokenProblemDetails
        : Microsoft.AspNetCore.Mvc.ProblemDetails
    {
        public string IncomingTokenId { get; set; }
        public DateTime Date => DateTime.Now;
    }
}

Ek olarak IncomingTokenId ve Date isimli iki özellik ekledik. Şimdi BrandController içerisindeki fonksiyonu değiştirelim.

[HttpGet("{tokenid}")]
public IActionResult GetBrands(string tokenid)
{
	string[] brands = { "abibas", "nayk", "niüv balanse" };
	if (Validate(tokenid))
		return Ok(brands);
	else
	{
		var problemDetail=new UnauthorizedTokenProblemDetails()
		{
			Type = "http://fabrikam.com/dummydataapi/",
			Title = "Token bilgisi hatalı.",
			Detail = "Gönderilen token ile marka bilgileri çekilemiyor.",
			Instance = $"/brand/{tokenid}",
			Status = 401,
			IncomingTokenId = tokenid
		};
		return BadRequest(problemDetail);
	}
}

Tek sıkıntı Unauthorized yerine BadRequest kullanmış olmamız. Nitekim object türünden parametre alan bir versiyon söz konusu. Bu versiyon Unauthorized ya da Forbid gibi dönüş tiplerinde bulunmadığı için böyle bir durum oluşuyor. BadRequest sınıfının yapıcı metoduna parametre olarak UnauthorizedTokenProblemDetails tipinden bir nesne örneği vererek istediğimiz çıktıyı üretiyoruz. Son değişikliklerden sonra Postman'den aldığım tepki kısmen beklediğim gibi oldu.

En azından Bad Request için uç noktaya daha anlamlı bir mesaj ilettiğimizi ve IETF standartlarına uyduğumuzu ifade edebiliriz. Bu arada paket kodlarını githubüzerinden incelemenizi öneririm. Güzel bir Middeware uyarlaması bulacaksınız. Hatta paketi kullanmak yerine kodlara bakarak kendi ara modülünüzü geliştirmeyi de deneyebilirsiniz. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Visual Studio Code İçinde Unit Test Yazmak

$
0
0

Merhaba Arkadaşlar,

Geçtiğimiz günlerde şirketimizin düzenlediği kişisel gelişim eğitimlerinden birisindeydim. Transaksiyonel Analiz'in konularından olan Ego üzerine kişiliğimizin parçası olan ve hayatımızı etkileyen iç karakterlerimizden bahsediliyordu. Yaklaşık üç saatlik eğitimde hoşça dakikalar geçirdik ve epey değişik bilgiler öğrendik. Özellikle uzman psikoloğun yer yer kullandığı görseller ve nokta atışı yapan karikatürler eğitimi çok keyifli hale getirmeye yetmişti. Üstelik uygulamalı olarak yaptığımız testler ile iç benliğimizdeki karakterlerin hangi noktalarda olduğunu da gördük. Eğitim sonrası masama döndüm ve bir kaç gün önce başladığım ama iş yoğunluğu sebebiyle yarım kalan yazımın başına geçtim. Derken eğitimde kullanılan Erdil Yaşaroğlu imzalı nefis karikatür geldi aklıma. Okur için tebessüm ettirici bir başlangıç olur diye düşündüm. Gelelim konumuza.

Şirketimizdeki Framework geliştirme ekibi, yazılan yeni nesil ürünlerde Visual Studio yerine Visual Studio Code ile çalışmamızı önermekte. Bir mecburiyet olmamakla beraber kendi Sprint Review toplantılarında gösterdikleri her şey Visual Studio Code üzerinden anlatılıyor. Çok eski ürünler için olmasa da(15 yıllık motolitik haline gelmiş devasa bir ERP uygulaması mesela) yeni nesil geliştirmeler için özellikle tercih ettiklerini ifade edebilirim. Bu da Visual Studio Code ile daha fazla haşır neşir olmamız ve el yatkınlığımızı arttırmamız gerektiği anlamına geliyor.

Ekibin bize verdiği yönergeleri takip ederek uygulamalara ait .Net çözümlerini kendi makinemde ayağa kaldırırken hem öğrendiğim hem de merak ettiğim şeyler oluyor. Pek çok şeyi Code'un terminalinden hallediyoruz. Özellikle git ile yakın temastayız. Pek tabii bir çok kullanışlı uzantıdan(extensions) da yararlanıyoruz. Benim en son baktığım konu ise Unit Test'lerin yazılması üzerineydi. Visual Studio Code'da bir .Net çözümü için klasör yapısı nasıl kurgulanır, test projesi nasıl oluşturulur, hangi test kütüphanesi kullanılabilir vb...Sonunda Microsoft dokümanlarını kurcalayarak bu işin en basit(ve birazda ilkel) haliyle nasıl yapılabildiğini öğrendim. Bir kaç deneme sonra evdeki West-World'un başına geçtim ve öğrendiklerimi uygulamaya koyuldum. Haydi başlayalım.

Solution Ağacının Oluşturulması

Amacımız bir kütüphane özelinde test projesi ve bağıntılarının nasıl kurgulanabileceğini görmek. Normalde Visual Studio gibi çok gelişmiş arabirimlerde bu oldukça kolay. Bir metoda sağ tıklayarak hızlı bir şekilde Unit Test projesini ve içeriğini oluşturabilirsiniz. Peki ya Code tarafında! Öncelikle bir dizi komut ile çözümün klasör ağacını ve içerisinde yer alacak projelerini oluşturacağız. Visual Studio Code terminalini kullanarak aşağıdaki komutları sırasıyla çalıştıralım.

mkdir UnitTestSample
cd UnitTestSample
dotnet new sln
mkdir TextService
cd TextService
dotnet new classlib
cd ..
dotnet sln add TextService/TextService.csproj
mkdir TextService.Tests
cd TextService.Tests
dotnet new mstest
dotnet add reference ../TextService/TextService.csproj
cd ..
dotnet sln add TextService.Tests/TextService.Tests.csproj
cd TextService.Tests

Öncelikle sistemimizde UnitTestSample isimli bir klasör oluşturduk ve içerisine girerek yeni bir Solution ürettirdik. Hemen ardından TextService isimli bir alt klasör daha oluşturduk. Bu klasörün içerisine girip bu sefer de bir sınıf kütüphanesi(Class Library) ürettirdik. Testlerini yazacağımız ve asıl işi yapan fonksiyonelliklerimizi burada biriktirebiliriz. Sonrasında solution dosyasına henüz üretilen TextService.csproj dosyasını ve dolayısıyla ilgili projeyi dahil ettik. Dolayısıyla ilgili sınıf kütüphanesinin projesini çözümün bir parçası haline getirdik. Birim testleri barındıracak olan TextService.Tests projesi için de benzer bir klasör yapısı söz konusu. Tek fark test projesini üretmek için "dotnet new mstest" komutlarından yararlanmış olmamız. Buna göre MStest kütüphanesini kullanan standart bir test projesi üretiliyor. Pek tabii test projesi TextService kütüphanesini kullanacağından proje referansını da eklememiz gerekiyor. Son adım olarak da TextService.Tests.csproj dosyasını Solution'a dahil ediyoruz. İşlemler bittiğinde kabaca aşağıdaki ağaç yapısını elde etmiş olmalıyız. TextService ve TextService.Tests aynı seviyede klasörlerdir. 

Hemen şu notu da belirtelim; MSTest kullanmak zorunda değiliz. Pek çok bağımsız test kütüphanesi var. Örneğin Microsoft dokümanlarına baktığımızda NUnit ve xUnit örneklerinin olduğunu da görebiliriz.

Esas Sınıf

Şimdi TextService projesinde basit bir sınıf kullanalım. Stringer isimli örnek sınıf bir metin ile ilgili değişik işlemlere ev sahipliği yapacak(güya) Bir fonksiyonu da metin içerisinde belli bir karakterden kaç tane olduğunun hesaplanması. Sınıfın ilk halini aşağıdaki gibi tasarlayabiliriz. Şimdilik metoda ait bir implemantasyonumuz bulunmuyor. Yani kasıtlı olarak NotImplementedException fırlatan bir metodumuz var.

using System;

namespace TextService
{
    public class Stringer
    {
        public int FindCharCount(string content,char character){
            throw new NotImplementedException("Makine soğuk henüz :P");
        }
    }
}

Test Sınıfı

Stringer sınıfının ilgili Test sınıfını da malumunuz TextService.Tests projesi altında yazmalıyız. StringerTest sınıfının ilk hali de aşağıdaki gibi olsun. 

using Microsoft.VisualStudio.TestTools.UnitTesting;
using TextService;

namespace TextService.Tests
{
    [TestClass]
    public class StringerTest
    {
        private readonly Stringer _poo;
        public StringerTest()
        {
            _poo=new Stringer();
        }

        [TestMethod]
        public void Text_Should_Contain_1_S()
        {
            var result=_poo.FindCharCount("",'s');
            Assert.AreNotEqual(0,result);
        }
    }
}

TestClass ve TestMethod nitelikleri ile bezenmiş standart bir Unit Test sınıfı. Text_Should_Contain_1_S isimli örnek test metodu, FindCharCount fonksiyonu için bir kabul kriterine sahip. Buna göre s karakterinden en az bir tane olmalı. Testi çalıştırmak için test projesinin klasöründe olmamız ve aşağıdaki komutu vermemiz yeterli.

dotnet test

Revize

İlk test sonucu tahmin edileceği üzere bir Exception ile sonlanmış durumda. O halde gelin FindCharCount metodunu bir kaç vakayı karşılayabilecek şekilde tekrardan yazalım.
using System;
using System.Linq;

namespace TextService
{
    public class Stringer
    {
        public int FindCharCount(string content,char character){
            if(String.IsNullOrEmpty(content))
                throw new ArgumentNullException("Bir içerik girilmeli");
            else{
                if(content.Length>10)
                    throw new ArgumentOutOfRangeException("Çok uzun, kısalt biraz");
                else{
                    var count=0;
                    foreach(var c in content)
                    {
                        if(c==character)
                            count++;
                    }
                    return count;
                }
            }
        }
    }
}

Bu kez bir kaç durum oluşabilir gibi. Metnin boş gelmesi veya örneğin 10 karakterden fazla olması hallerinde fonksiyonumuz uygun bir Exception ile çağıran tarafı cezalandırmakta. Ancak bu koşullar gerçekleşmediyse bir karakter sayımı yapılmakta ve parametre olan gelen harften kaç tane olduğu bulunmakta. Şimdi yazabileceğimiz bir kaç test metodu daha var. Test sınıfını aşağıdaki gibi değiştirerek devam edelim.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace TextService.Tests
{
    [TestClass]
    public class StringerTest
    {
        private readonly Stringer _poo;
        public StringerTest()
        {
            _poo=new Stringer();
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Text_Is_Null_Or_Empty_Should_Throw_Exception()
        {
            var result=_poo.FindCharCount("",'s');
            Assert.AreNotEqual(0,result);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void Text_Length_Is_Too_Long_Should_Throw_Exception()
        {
            var result=_poo.FindCharCount("güzel güneşli bir günden kalan.",'s');
            Assert.AreNotEqual(0,result);
        }

        [TestMethod]
        public void Text_Should_Contain_Any_S_Character()
        {
            var result=_poo.FindCharCount("pilav",'s');
            Assert.AreEqual(true,result>0);
        }
    }
}

Exception beklediğimiz iki test metodu var. Ayrıca bir de içinde s karakteri barındırmasını beklediğimiz kabul kriterimiz. Exception beklediğimiz durumlar için bilinçli olarak ExpectedException niteliğini(attribute) kullandık. Bu iki test başarılı çalışacaktır. Ancak "pilav" kelimesinde "s" karakteri olmadığından 1 test hatalı sonuçlanacaktır. Testi tekrar çalıştırdığımızda aşağıdaki sonuçları elde ederiz.

Aslında bakmamız gereken başka durumlar da var. Örneğin gerçekten içinde s harfi olan bir metin için gerekli kabul kriterimizi de yazabiliriz. Bu şekilde değişken girdi parametreleri için ayrı ayrı test metodları yazmaktansa DataTestMethod ve DataRow nitelikleri yardımıyla çok sayıda farklı girdiyi kabul kriterlerine dahil edebiliriz. Nasıl mı? Aynen aşağıdaki gibi.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace TextService.Tests
{
    [TestClass]
    public class StringerTest
    {
        private readonly Stringer _poo;
        public StringerTest()
        {
            _poo=new Stringer();
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Text_Is_Null_Or_Empty_Should_Throw_Exception()
        {
            var result=_poo.FindCharCount("",'s');
            Assert.AreNotEqual(0,result);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void Text_Length_Is_Too_Long_Should_Throw_Exception()
        {
            var result=_poo.FindCharCount("güzel güneşli bir günden kalan.",'s');
            Assert.AreNotEqual(0,result);
        }

        [DataTestMethod]
        [DataRow("password")]
        [DataRow("none")]
        [DataRow("sarı")]
        public void Text_Should_Contain_Any_S_Character(string text)
        {
            var result=_poo.FindCharCount(text,'s');
            Assert.AreEqual(true,result>0);
        }
    }
}

Bu sefer dikkat edileceği üzere 3 değişik metin için aynı test metodunu çalıştırdık. Sadece DataRow niteliğinin aldığı değeri Test metodunda parametre olarak ele almamız yeterli. Şimdi testlerimizi tekrar çalıştırırsak aşağıdaki sonuçları elde ettiğimizi görebiliriz.

Dikkat edileceği üzere Visual Studio Code arabirimi dışına çıkmadan MSTest kütüphanesini kullanarak birim testler içeren çözümler üretmemiz oldukça basit ve kolay. Pek tabii görsel olarak test sonuçlarını görebilmek de güzel olurdu. Visual Studio bu anlamda çok fazla ve güzel imkan sunuyor. Ne var ki Visual Studio Code için de açık kaynak olarak geliştirilmiş bir çok Test Explorer var. Bu arada "dotnet test" komutunun da gizemli parametreleri yok değil. Örneğin aşağıdaki komut ile Test sonuçlarının bir XML dosyası içerisine loglanmasını sağlayabiliriz.

dotnet test --logger trx

(Ben örnek deneme sonrasında TestResults klasörü altında trx uzantılı bir log dosyası elde ettim)

Ya da 

dotnet test --list-tests

komutuyla var olan klasörde kullanılabilecek ne kadar test varsa terminal penceresine basabiliriz. Diğer komutları öğrenmek için ne yapmanız gerektiğinizi biliyorsunuz.

dotnet test --help

Bu yazımızda Test Driven Development odaklı yazılım geliştiren ekipler için Visual Studio Code tarafında basitçe nasıl ilerleyebileceğimizi gördüğümüzü düşünüyorum. Hatta hali hazırda var olan projelerinizdeki test senaryoları için Code tarafında denemeler yaparak el alışkanlığı kazanabilirsiniz. Biliyorsunuz ki test edilmemiş kod kaliteden uzaklaşmamız ve teknik borç bırakmamız için gerek ve yeter sebeplerden birisidir.Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Electron ile Cross-Platform Uygulama Geliştirmek

$
0
0

Merhaba Arkadaşlar,

Bu aralar işler doğruyu söylemek gerekirse biraz can sıkıcı. KVKK(Kişisel Verilerin Korunması Kanunu) olarak bilinen ve müşteri verilerinin anonimleştirilmesini gerektiren bir çalışma içerisindeyiz. Verinin dağınık olması, hacimsel büyüklüğü, kurum içi süreçlerin karmaşıklığı, monolitleşmiş ERP uygulamamız üzerindeki etkilerinin çıkartılmasındaki zorluklar, araya giren başla işler nedeniyle yavaş ilerliyoruz. Kurumun tamamını ilgilendiren bir regülasyon söz konusu olduğu için de biraz fazlasıyla statik elektrik yüklüyüz. Yani şöyle bir Galvatron çıksa içimizden ya da Megatron, Electron ortalığı kasıp kavura...Imm, şey...Ne diyorum ben yahu. Electron'da nereden çıktı :) Aslında tesadüfen karşıma çıktı ve bu karşılaşma sayesinde kendimi ilginç ve zevkli bir maceranın içerisinde buldum.

West-World'de masaüstü uygulama yazabileceğimi ve bunu hem MacOS hem de Windows platformunda çalıştırabileceğimi öğrendim. Bu yeni bir şey değil ve hatta aklımıza Mono, Miguel de Icaza, Xamarin Forms geliyor ancak, bunu gerçekleştirmek için HTML, Node.js ve CSS kullanarak ilerleyebileceğimiz bir yol daha var.

İşin aslı vakti zamanında GitHub tarafından açık kaynak olarak geliştirilen electron isimli çatı sayesinde platform bağımsız masaüstü uygulamaları yazmak mümkün(Hemde epey zamandır) Bu oluşumda Chromium ile Node.js alt yapısının hazırladığı bir senaryo söz konusu. Chromium ile HTML bazlı sayfaları bir masaüstü formu gibi sunmak mümkün. Node.js sayesinde alt seviye işletim sistemi işlemleri de yapabileceğimiz için web platformunun geliştirme olanaklarını masaüstüne taşıyabildiğimizi düşünebiliriz.

Sonunda hızlı bir deneyim için interneti taramaya ve basit bir "Hello World" programı yazmaya karar verdim. Zaten ara ara Linux üzerinde masaüstü uygulamaları yazabilmek için ne yapabileceğimi araştırıyordum. En önemli motivasyonumu, West-World(Ubuntu)üzerinde yazacağım masaüstü uygulamasını MacOS ve Windows platformlarında çalıştırabilmek şeklinde belirledim.

West-World Üzerindeki Geliştirmeler

İşe Ubuntu üzerinde yapacağım geliştirmelerle başladım. Ortamda Node.js olması yeterliydi. Öncelikle bir klasör hazırladım ve npm ortamını tesis ettikten sonra electron çatısına ait npm paketini sisteme yüklettim.

npm init
npm install --save-dev electron

npm init ile gelen sorulara bir kaç cevap verip package.json içeriğini aşağıdaki hale getirdim.

{
  "name": "helloforms",
  "version": "1.0.0",
  "description": "a simple desktop application for cross platform",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "burak selim senyurt",
  "license": "ISC",
  "devDependencies": {
    "electron": "^3.0.5"
  }
}

start kısmındaki electron . ifadesi çalışma zamanı için gerekli. Uygulamanın giriş noktası olarak main.js dosyasını kullanacağız. Aslında electron, chromium ile bir ana process başlatacak ve biz istersek diğer alt process'ler ile(Renderer Process olarak ifade ediliyor) IPC(Inter Process Communication) protokolü üzerinden mesajlaşabileceğiz. Bu şu anlama geliyor; ana process ve açılacak başka alt process'ler(örneğin ekranlar veya diğer arka plan işlerini yapan javascript kodları) IPC üzerinden haberleşebilirler. Bu sayede örneğin main.js dışındaki bir ekrandaki düğmeye tıklandığında tetiklenen olaydan, main.js'teki bir dinleyiciye mesaj gönderebilir ve dönüş alabiliriz. İşleyiş esasında bir Windows Forms uygulamasının yaşam döngüsünden pek de farklı değil. Bu durumu örnekte anlatmaya çalışacağım(Öğrenebildiğim kadarıyla) Main.js, index.html ve index.js isimli dosyalara ihtiyacımız var. Main.js içeriğini yazarak işe başlayalım.

const { app, BrowserWindow, ipcMain } = require('electron')
console.log(process.platform)

let win

function createWindow() {
    win = new BrowserWindow({ width: 640, height: 480 })
    win.loadFile('index.html')
    //win.webContents.openDevTools()
    win.on('closed', () => {
        win = null
    })
}

app.on('ready', (createWindow))

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit()
    }
})

app.on('activate', () => {
    if (win === null) {
        createWindow()
    }
})

ipcMain.on("btnclick", (event, arg) => {
    var response = "Hello " + arg + ".How are you today?"
    event.sender.send("btnclick-task-finished", response);
})

electron modülünden app, BrowserWindow ve ipcMain tiplerini tanımlayarak işe başlıyoruz. Sonrasında gelen satırda çalıştığımız platformu console ekranına yazdırmaktayız. Bunu Windows, Linux veya MacOS'da çalışıp çalışmadığımdan emin olmak için ekledim. win isimli değişkenimiz BrowserWindow tipinden. Tahmin edeceğiniz gibi bu nesne ile bir ekran(pencere, form artık nasıl düşünmek isterseniz) tanımlıyoruz. createWindow fonksiyonunda ekranın nasıl oluşturulduğunu görebilirsiniz. Genişlik ve yükseklik belirttikten sonra index.html dosyasının bu pencere içerisine açılacağını belirtiyoruz. Sanki Windows Forms'da bir tarayıcı kontrolü içerisinde yerel bir HTML dosyasını gösteriyormuşuz gibi düşünebiliriz sanırım. Başlangıçta yorum satırı olarak belirttiğimiz openDevTools fonksiyon çağrısı ile Chrome Developer penceresi açılıyor. Şimdilik bu özellik kapalı.

win değişkeni üzerinden ve ilerleyen satırlardaki app, ipcMain nesneleri tarafından da çağırılan on isimli metodlar, belli olayların tetiklenmesi sonrası devreye giren fonksiyonlar. Örneğin açılan pencere için closed olayı tetiklenirse win nesnesi bellekten atılmak üzere null değerle işaretleniyor. Ya da uygulama hazır olduğunda createWindow fonksiyonu çağırılıyor. Birden fazla pencerenin olması da muhtemel tabii. Bu nedenle tüm pencerelerin kapatılması sonucu tetiklenen window-all-closed sonrası eğer üzerinde olduğumuz platform darwin(yani macOS) değilse quit çağrısı ile uygulama kapatılıyor.

Kodun son kısmında yer alan ve ipcMain nesnesi üzerinden çağırılan btnclick olay kontrolü, index.js içeriğini yazdığımız zaman anlam kazanacak. Şimdilik btnclick isimli bir olayın tetiklenmesi sonrası arg değişkeni ile gelen veriyi tekrardan geriye yolladığımızı söyleyebiliriz(Tabii geriye derken nereye, tahmininiz var mı?)

Gelelim index.html ve index.js içeriklerine.

<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Electron Sample - I</title></head><body><h1>Hello Electron! I am MEGATRON :P</h1><div><label>What's your name?</label><input type="text" id="text-name" placeholder="name"></div><div><button id="button-hello">Say Something</button></div>    <div id="div-response"></div><script src="index.js"></script></body></html>

HTML sayfamızda basit bir tasarımımız var. Normalde CSS ile zenginleştirilebilir ama ben kısa yoldan öğrenmenin ve olanları anlamanın peşinde olduğumdan bu durumu es geçtim(CSS bilmemem de işin cabası tabii) HTMLce okuduğumuzda kullanıcının adını sorduğumuz ve düğmeye basmasını beklediğimiz bir pencere olduğunu söyleyebiliriz. Tabii text kontrolünün içeriğini alabilmek için id niteliğinden yararlanacağız. button-hello ve div-response kimlikleri de index.js içerisinde anlam kazanacaklar. index.html ile alakalı javascirpt içeriklerini gömülü olarak da yazabiliriz ama ben index.js ismiyle ayrı bir dosyada tutmaya karar verdim. Html ve javascript dosyaları arasındaki ilişkiyi sonlarda yer alan script bloğunda kurduğumuza da dikkat edelim (Bu arada kişisel tavsiyem ön yüz tarafında Bootstrap gibi bir yapıyı kullanarak ilerlemeniz. Çok daha şık ve her cihaza cevap verebilir arayüzler oluşturabilirsiniz. Github'daki electron repoda Bootstrap ile ilgili örnek yer alıyor)

const ipcRenderer = require('electron').ipcRenderer;
var btnClick = document.getElementById('button-hello');

btnClick.addEventListener('click', () => {
    var name = document.getElementById('text-name').value
    ipcRenderer.send("btnclick", name)
})

ipcRenderer.on('btnclick-task-finished', (event, param) => {
    var div = document.getElementById('div-response')
    div.innerText = param
});

index.js kodları electron'dan ipcRenderer nesnesini alarak işe başlıyor. Hatırlarsanız Main.js içerisinde ipcMain isimli nesneden yararlanmıştık. ipcRenderer, Main process ile ilişkili alt process'ler için kullanılıyor. Ekrandaki button bileşenini klasik getElementById ile yakaladıktan sonra, click isimli bir eventListener ekliyoruz. Bu olay metodu içerisinde text-name kontrolünün veri içeriğini yakalayıp, ipcRenderer'ın send fonksiyonu üzerinden btnclick takısı ile bir yere gönderiyoruz...Sizce nereye göndermiş olabiliriz? Sanırım şu anda ipcMain kullanarak Main.js içerisinde yakaladığımız btnclick takma adlı olay metodu daha da anlam kazandı değil mi? Son olarak ipcRenderer üzerinden bu kez btnclick-task-finished takılı bir olay metodunu kodladığımızı görebilirsiniz. Buraya akan mesaj, main process'den geliyor. Yakalanan mesajı div-response isimli div kontrolü üzerine basıyoruz.

Özetle ekrandan yazılan metni main process'e gönderiyor ve oradan da index.js için açılan alt process'e karşılık veriyoruz. Bunu pekala ekrandan main process üzerinde çalıştırılacak paralel görevlerin icra edilmesi gibi işlemlerde ele alabiliriz. Ancak işin önemli kısmı bir tane main process'in olduğu ve diğer alt process'lerin main process ile IPC üzerinden iletişim kurduğudur(Bunun mutlaka başka faydaları da vardır ama neler olduğunu öğrenmem lazım. Araştırmaya devam Burak)

Aslında örnek kodlarım tamamen bu kadardı. Hemen west-world komut satırından

npm start

ile programı çalıştırdım ve aşağıdaki ekran görüntüsü ile karşılaştım. Bir masaüstü uygulamam olmuştu artık.

Hatta developer modu etkinleştirince(win.webContents.openDevTools() satırını açaraktan) çok tanıdık olduğumuz bir ekranla karşılaştım. Chrome üzerinden Debug yapıp bir şeyleri keşfetmeye çalışanlar aşinadır.

Şirketteki Windows'ta Olanlar

West-World ile yaptığım çalışmaları bitirdikten sonra değişiklikleri github reposuna push edip uyku moduna geçtim. Ertesi sabah işe gider gitmez ilk olarak repodaki kodları indirdim. Çok doğal olarak ilk çalıştırma da hata aldım. Çünkü electron npm paketi sistemde yüklü değildi. Bu beraberinde başka bir konuyu gündeme getirdi. Aslında uygulamayı West-World'de yazdıktan sonra paketleyebilmeliydim. Bunu araştıracaktım lakin Windows üzerindeki sonuçları da çok merak ediyordum. electron paketini yükledim ve yine

npm start

ile programı çalıştırdım. Volaaaa...

Sonuç oldukça tatmin ediciydi benim için. Console loguna göre Windows platformunda olduğum aşikardı. Diğer taraftan metin kutusuna yazdığım bilgi, butona bastıktan sonra da işlenmişti. Önümde tek bir engel kalıyordu. Aynı kod parçasını bir Apple bulup MacOS üzerinde deneyimlemek.

Gökan'ın MacOS'unda Yapılanlar

Sevgili dostum Gökhan sağolsun emektar Apple'ını kullanmama izin verdi. Kendisi her ne kadar yazılımcılıkla uğraşmasa da bilgisayarını tereddütsüz emrime amade etti. Pek tabii makinede pek bir şey yoktu. Node.js'i ve git'i kurmam gerekti. Sonrasında versiyon kontrollerini yaptım ve github adresime attığım projeyi yerel bilgisayara klonladım. electron paketinin olmaması ihtimaline karşın onu da npm üzerinden yükledim. Kabaca aşağıdaki ekran görüntüsündekine benzer bir durum oluştu diyebilirim.

node --version
npm --version
git --version
git clone https://github.com/buraksenyurt/nodejs-tutorials.git
cd Day09
npm i --save-dev electron

Ve derhal npm start komutunu vererek uygulamanın çalışıp çalışmadığını kontrol ettim.

Mutluluk!!!

West-World üzerinde electron kullanarak geliştirilen Chromium tabanlı bir masaüstü uygulamasını, hem Windows hem de MacOS'da sorunsuz şekilde çalıştırabildim. Üstelik çok az eforla. Cross-Platform adına Xamarin sonrası gördüğüm önemli çatılardan birisi oldu electron. Özellikle işin arkasında GitHub'ın olması, açık kaynak topluluk desteği, versiyonlardaki sürekli kararlılık, node.js'in gücü, HTML ve CSS kullanarak aynen web uygulaması geliştiriyormuşçasına ilerleyebilme imkanı cezbedici unsurlar diye düşünüyorum. Evdeki Linux, şirketteki Windows, komşudaki MacOS. Hepsinde github reposundan çekip çalıştırdığım aynı arayüze sahip masaüstü uygulamaları koşuyor. Eğer bunu paketleyerek dağıtmayı da öğrenirsem pek şukela olacak diye düşünüyorum. Ben bunu araştırıyorken siz de boş durmayın öğrenin. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Ayrıca electron ile ilgili Tutorial tadındaki örnek kodlara github'dan ulaşabilirsiniz
Bu uygulama kodlarını da buradan klonlayabilirsiniz (Day09)
Electron Github projesi
Eğer bir sonraki adım ne olmalı derseniz ben şuradaki yazıyı takip edeceğim derim


Daha Verimli Konfigürasyon Yönetimi (.Net Core)

$
0
0

Merhaba Arkadaşlar,

Epey zamandır onbeş yaşından büyük bir proje ile ilgili geliştirmeler yapmaktayız. Uygulamayı ilk kez kullanmaya başladığımda en çok zorlandığım şeylerden birisi, küçük birimlerin testini yazmak olmuştu. Sıkılaşmış ve hatta kemikleşmiş bağımlılıklar nedeniyle basit bir fonksiyon testi için gerekli gereksiz bir çok kütüphanenin kullanılması gerekebiliyordu. Sahte nesneleri araya almak bir yere kadar çözüm olabilirdi. Lakin entegrasyon testlerini deneyimlerken karşılaştığım bir başka sorun daha vardı. Karmaşıklık değeri yüksek konfigurasyon dosyaları. Çok fazla konfigurasyon ayarı, özelleştirilmiş sektör, şifrelenmiş bağlantı cümleleri ve diğer parametrik ayarlarla ilgili bilgiler tutan web.config zaman içerisinde epeyce şişmanlamıştı. God Object değil ama God Configuration(Böyle bir terim yok tabii ben uydurdum) gibi bir anti-pattern oluşmuştu. İlk başlarda sadece gereken ayarları alarak test projesini ayağa kaldırmaya çalışmıştım. İşin sonunda ise tüm web.config'i kopyalamıştım.

Oysa ki konfigurasyon yönetimi de en az temiz kod yazmaya çalışmak kadar titizlikle üzerinde durulması gereken bir mevzu. Bazen bir konfigurasyon dosyasını parçalamak ve bu şekilde yönetmeye çalışmak çok daha anlamlı olabilir. Hele ki ortamların test, pre-prod ve prod olarak ayrıldığı ve Continuous Integration/Deployment/Delivery hattı üzerinde değer bulduğu bir dünyada oldukça önemli. Microsoft .Net dünyasında çok uzun zamandır konfigurasyon içeriklerini efektif bir şekilde yönetebiliyoruz. Pratik bir kaç bilgi ile bu yönetim gücünü daha da verimli hale getirebiliriz.

Bildiğiniz üzere .Net core tarafında konfigurasyon bilgileri varsayılan olarak JSON formatlı olarak tutulur(appSettings.json) Burada ön tanımlı parametreler dışında özel konfigurasyon içeriklerini kullanmak da mümkündür. Hatta istersek birden fazla ve farklı formatta konfigurasyon dosyasını kullanabiliriz. Her ikisini uygulamak da oldukça kolay aslında. Nasıl mı? Gelin birlikte bakalım. İlk olarak basit bir Web API oluşturarak işe başlayabiliriz. Amacımız kendi eklediğimiz bir sekme ve içindeki parametreleri çalışma zamanında kullanabilmek.

dotnet new webapi -o ConfigSample

Varsayılan olarak appSettings.json içeriği aşağıdaki gibi oluşur(Tabii kullanılan .net core sürümüne göre ufak tefek farklılıklar olabilir)

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Logging ve AllowedHosts isimli iki ana sekme bulunuyor. Şimdi bu içeriği aşağıdaki gibi değiştirelim.

{
  "Logging": {
    "IncludeScopes": true,
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "DefaultSettings": {
    "Owner": "B&B Organization",
    "Address": "One way road, Dublin, 10",
    "Contact": "contact@BandB.com"
  }
}

DefaultSettings isimli yeni bir parametre seti ekledik. İçerisinde Owner, Address ve Contact isimli üç anahtar:değer çifti var. Bu içeriği kod tarafında kullanmak için IConfiguration arayüzünden yararlanabiliriz. Örneğin şablon ile birlikte hazır olarak gelen ValuesController sınıfında DefaultSettings içeriğini kullanmak istediğimizi düşünelim. Kodları aşağıdaki gibi düzenleyerek ilerleyelim.

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace ConfigSample.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController
    : ControllerBase
    {
        private readonly IConfiguration _config = null;
        private readonly ILogger<ValuesController> _logger = null;
        public ValuesController(IConfiguration config, ILogger<ValuesController> logger)
        {
            _config = config;
            _logger = logger;
        }
        // GET api/values
        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        {
            var contact = _config.GetValue<string>("DefaultSettings:Contact");
            var owner = _config.GetValue<string>("DefaultSettings:Owner");
            _logger.LogInformation($"Contact Email : {contact}\n");
            _logger.LogInformation($"Owner : {owner}\n");
            return new string[] { "value1", "value2" };
        }
    }
}

ValuesController sınıfının yapıcı metoduna(Constructor) müdahale ettik. IConfiguration ve ILogger<ValuesController> arayüzleri parametre olarak geliyor. Aslında çalışma zamanına enjekte edilen nesnelerin içeriye alındığını ifade edebiliriz. Sonrasında _config ve _logger değişkenlerini diğer metodlarda kullanmamız mümkün. _logger nesnesini log bilgisi vermek için kullanıyoruz. DefaultSettings içerisindeki değerleri okumak için {SectionName}:{KeyName} notasyonundan yararlandağımıza dikkat edelim. Değerleri GetValue metodu ile almaktayız. Eğer uygulamayı

dotnet run

terminal komutu ile çalıştırıp http://localhost:5000/api/values adresine HTTP Get talebinde bulunursak appSettings içerisindeki ilgili değerlere erişebildiğimizi görürüz.

Tabii geliştireceğimiz uygulamaların çeşitli ve çok sayıda konfigurasyon bağımlılığı olabilir ve bu bağımlılıkları mantıksal düzende ayrı dosyalar halinde tutmak isteyebiliriz. Bu durumu deneyimlemek için DefaultSettings içeriğini örneğin copyrightSettings.json isimli ayrı bir dosyaya aldığımızı düşünelim(appSettings.json ile aynı yerde olmalarında yarar var)
copyrightSettings.json;
{
    "DefaultSettings": {
        "Owner": "B&B Organization",
        "Address": "One way road, Dublin, 10",
        "Contact": "contact@BandB.com"
    }
}
Artık appSettings.json dışında yeni bir konfigurasyon dosyamız daha olduğunu uygulama çalışma zamanına bildirmemiz gerekiyor. Bunu gerçekleştirmenin yollarından birisi Startup.cs içeriğini değiştirmekle mümkün. Kodu aşağıdaki gibi düzenleyelerek örneğimize devam edelim.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace ConfigSample
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var cfgBuilder = new ConfigurationBuilder();
            cfgBuilder.SetBasePath(env.ContentRootPath);
            cfgBuilder.AddJsonFile("appsettings.json", false, true);
            cfgBuilder.AddJsonFile("copyrightSettings.json", false, true);
            //cfgBuilder.AddXmlFile

            Configuration = cfgBuilder.Build();
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            services.AddSingleton<IConfiguration>(Configuration);
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseMvc();
        }
    }
}

Startup sınıfının yapıcı metodunda ConfigurationBuilder nesnesini kullanıyoruz. Root Path bilgisine göre AddJsonFile metodundan yararlanarak kullanmak istediğimiz konfigurasyon dosyalarını orta katmana bildiriyoruz. Son olarak ConfigurationBuilder'dan üretilen IConfiguration referansını AddSingleton ile çalışma zamanı servislerine ekliyoruz. Dikkat ederseniz yorumlanmış bir kod satırı da var. Tahmin edeceğiniz üzere JSON formatlı dosyalara bağımlı değiliz. İstersek eski dostumuz XML tabanlı konfigurasyon dosyalarını da işin içerisine katabiliriz. Bunu denemenizi öneririm. Uygulamayı tekrar çalıştırıp doğru çalıştığından emin olmakta yarar var.

Konfigurasyon yönetimi her zaman için önemli konulardan birisi. Veritabanı bağlantı bilgileri, loglama kriterleri, çeşitli ortam parametrelerinin varsayılan değerleri, şifrelenmiş token bilgileri çoğunlukla bu dosyalar içerisine konuluyor. Bu nedenle kod tarafında nasıl yönetebileceğimizi bilmekte yarar var. Bu kısa yazımızda konfigurasyon içerisine alacağımız özel sekmeleri nasıl kullanabileceğimizi ve içerikleri ayrı dosyalar halinde nasıl ele alabileceğimizi incelemeye çalıştık. Umarım faydalı olmuştur. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Dependency Injection'ın TDD'deki Yeri

$
0
0

Merhaba Arkadaşlar,

Ne zamandır oturup da Lego yapmadığımı fark ettim. Her ne kadar fiyatları epeyce artmış olsa da geçenlerde dayanamayıp bir tane aldım. Bitirir bitirmez beni tatile götüreceğini düşündüğüm güzel bir karavan. Bloklardaki canlı renklerin tatlılığı, masmavi surf tahtası, uydu alıcısı, konforlu koltukları, panaromik tavanı, spor lastikleri ile bir saate kalmadan hazırdı bile.

Onunla biraz oynayıp, wrom wrom yaptım. Lego City'nin caddelerinden yavaş ve sakince akarak Malaga kıyılarındaki plajlara indim. Güneş batana kadar yüzdüm, surf yaptım. Bazen sahilden iyice uzaklaştım. İnsanların nokta gibi gözüktüğü mesafelere geldim. Şehre akşamın sakinliği çöküp karanlık bastırdıktan sonra plaja tekrar gelen gençlerin gitar melodilerinden çıkan tınıları dinlemeye başladım. Yaktıkları ateş etrafında toplanmış enerji dolu gençler. Karavanın hemen yanıbaşına kurduğum şezlongumda uzanmış onları izliyordum. Tebessümle. Buzlu kahvemden bir yudum aldım ve açık duran bilgisayarımda yanıp sönen cursor'u izlemeye başladım. Yazılmayı bekleyen kuyrukta kalmış bir şeyler vardı...

TDD süreçlerindeki birim ve entegrasyon testlerinde(Integration Tests) yaşadığımız önemli sorunlardan birisi, test edilmek istenen fonksiyonelliklerde kullanılan nesnelerin diğer nesnelerle olan bağımlılıkları sebebiyle yaşanmaktadır. Söz gelimi test edilmek istenen birimin bağımlılıkları arasında servis çağrıları, veritabanı operasyonları veya uzak sunucu dosya hareketleri gibi işlemler söz konusuysa, otomatik olarak çalıştırılan birim testlerinin CI(Continuous Integration) sürecini sekteye uğratmaması için bir şeyler yapılması gerekebilir. Biliyorum çok karışık bir paragraf ile işe başladım. O yüzden problemi ortaya koymak adına aşağıdaki kod parçalarını göz önüne alarak ilerlemeye çalışalım.

using System;

namespace CodeKata.Services{
    public class CalculationService
    {
        private UserSerice _service;
        public CalculationService()
        {
            _service=new UserSerice();
        }
        public string GetInvoices()
        {
            if(_service.CheckRequest(GetCurrentContext()))
            {
                return "Invoice list";
            }
            else{
                return String.Empty;
            }
        }

        private string GetCurrentContext()
        {
            return "{\"operation\":\"Sum\"}";
        }
    }
}

CalculationService isimli sınıfın GetInvoices metoduna odaklanalım. UserService sınıfına ait nesne örneğinin CheckRequest fonksiyonu kullanılarak bir işlem gerçekleştirilmekte. Açıkça CalculationService sınıfı için bir bağımlılık bulunduğunu görebiliyoruz. Buna ilaveten UserService'in bir web servisi olduğunu düşünelim.

Sorun şu; "GetInvoices için yazılan testler çalışırken ya UserService çalışır durumda değilse?"

Bu Continuous Integration sırasında test aşamasının geçilmesine de engel teşkil edebilecek bir durum olabilir. İşte burada söz konusu servisin CheckRequest fonksiyonunun aslında istediğimiz tipte veriyi döndürecek şekilde kullanılması sorunu çözümleyebilir. Yani bağımlılığı test tarafında istediğimiz gibi enjekte edersek asıl testin çalışıp çalışmadığına odaklanabiliriz. Bunu yapmak için Dependency Injection' a uygun bir tasarıma geçmemiz gerekiyor. 

Bildiğiniz üzere Dependency Injection mekanizması ile bu tip nesne bağımlılıklarının soyutlaştırılması mümkün. Temelde bunu üç farklı şekilde yapabiliriz. Yapıcı metod(Constructor)üzerinden, özelliğe(Property) kullanarak ve arayüz(Interface) tipinden yararlanarak. Yukarıdaki örneği düşünerek bu üç tekniğini nasıl kullanabileceğimizi incelemeye çalışalım. 

Yapıcı Metod Kullanımı

Burada bağımlılığın nesne içerisine yapıcı metod üzerinden aktarımı söz konusudur. Öncelikle aşağıdaki gibi bir arayüze ihtiyacımız var.

public interface IUserService
{
    bool CheckRequest(string request);
}

Buna göre CalculationService sınıfını da aşağıdaki gibi değiştirilmesi gerekiyor.

using System;

namespace CodeKata.Services{
    public class CalculationService
    {
        private IUserService _service;
        public CalculationService(IUserService service)
        {
            _service=service;
        }
        public string GetInvoices()
        {
            if(_service.CheckRequest(GetCurrentContext()))
            {
                return "Invoice list";
            }
            else{
                return String.Empty;
            }
        }

        private string GetCurrentContext()
        {
            return "{\"operation\":\"Sum\"}";
        }
    }
}

Dikkat edileceği üzere yapıcı metoda parametre olarak IUserService arayüzünü veriyoruz. Bu arayüz, UserService sınıfının uygulaması gereken CheckRequest metodunu tanımlamakta. Dolayısıyla IUserService arayüzünü uygulayan herhangi bir sınıfı kullanabilir ve asıl servise gitmeye gerek kalmadan istediğimiz cevabı döndürerek testin ilerlemesini sağlayabiliriz. Bunun için test tarafında aşağıdaki gibi bir sınıfa ihtiyacımız olacak.

public class FakeUserService
    : IUserService
{
    public bool CheckRequest(string request)
    {
        return true;
    }
}

CheckRequest operasyonunu her türlü true döndürecek hale getirdik. Dolayısıyla şöyle bir test metodu yazmamız artık mümkün.

using System;
using CodeKata.Services;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CodeKata.Tests
{
    [TestClass]
    public class CalculationServiceTests
    {
        [TestMethod]
        public void Should_Return_Current_Invoice_List_Is_Ok()
        {
            var calcService = new CalculationService(new FakeUserService());
            Assert.AreEqual("Invoice list", calcService.GetInvoices());
        }
    }
}

Should_Return_Current_Invoice_List_Is_Ok test metodunun içerisinde CalculationService nesnesini örneklerken parametre olarak FakeUserService sınıfını verdiğimize dikkat edelim. FakeUserService, test projesi içerisinde yer alıyor ve asıl servisin bu test için gerekli olan davranışını taklit ediyor. İşte çalışma zamanı sonucu (Örnekleri Visual Studio Code üzerinde ve MSTest türevli bir test projesi ile geliştirmekteyim)

Property Setter Kullanımı

Şimdi enjekte mekanizmasını property üzerinden kuralım. Tek yapmamız gereken CalculationService sınıfını aşağıdaki gibi değiştirmek olacak.

using System;

namespace CodeKata.Services{
    public class CalculationService
    {
        public IUserService UserService
        {
            get;
            set;
        }
        public string GetInvoices()
        {
            if(UserService.CheckRequest(GetCurrentContext()))
            {
                return "Invoice list";
            }
            else{
                return String.Empty;
            }
        }

        private string GetCurrentContext()
        {
            return "{\"operation\":\"Sum\"}";
        }
    }
}

Bu sefer yapıcı metod yerine getter ve setter bloklarına sahip IUserService tipinden bir özellik kullanarak bağımlılığın içeriye alınmasını sağlıyoruz. Doğal olarak ilgili test metodunun da aşağıdaki gibi değişmesi gerekiyor.

[TestMethod]
public void Should_Return_Current_Invoice_List_Is_Ok()
{
    var fakeService=new FakeUserService();
    var calcService=new CalculationService()
    {
        UserService=fakeService
    };
    Assert.AreEqual("Invoice list", calcService.GetInvoices());
}

Test bu durumda da beklediğimiz gibi çalışacak ve asıl servisi hiç kullanmadan ilerleyecektir.

Interface Kullanımı

Son olarak daha çok tercih edilen interface üzerinden nasıl bağımlılık enjekte edebileceğimize bir bakalım. Bu yöntem aslında property tabanlı bağımlılık tanımlamanın genişletilmiş bir versiyonu olarak düşünülebilir ve daha çok birden fazla bağımlılığın olduğu durumlarda ele alınır. Bizim örneğimizde bu tekniği aşağıdaki gibi icra etmemiz mümkün.

public interface IUserServiceInjector
{
    IUserService UserService{get;set;}
}

İlk olarak IUserService arayüzünden referansları döndürecek bir başka sözleşme tanımlıyoruz. Ardından IUserServiceInjector isimli arayüz CalculationService sınıfına uyarlanıyor. Böylece CalculationService sınıfının ilgili servis davranışını IUserServiceInjector üzerinden belirtilen sözleşme çerçevesinde almasını sağlıyoruz.

public class CalculationService
    : IUserServiceInjector
{
    public IUserService UserService
    {
        get;
        set;
    }
    public string GetInvoices()
    {
        if (UserService.CheckRequest(GetCurrentContext()))
        {
            return "Invoice list";
        }
        else
        {
            return String.Empty;
        }
    }
    private string GetCurrentContext()
    {
        return "{\"operation\":\"Sum\"}";
    }
}

Çalıştığımız nesnelerde birden fazla bağımlılığın söz konusu olması halinde ayrı ayrı Injector arayüzleri tasarlayıp uygulamamız mümkün. Son değişikliklere rağmen test metodunda özellik tabanlı örneğimizden farklı bir işleyiş söz konusu olmayacak.

Test güdümlü geliştirme kapsamında Dependency Injection tekniğini bağımlılıkların olduğu her yerde ele almak mümkün. Her ne kadar kod okunmasını biraz zorlaştırsa da daha kolay test yazılmasını sağlamakta olduğu aşikar.

Örneklerde kullandığımız FakeUserService sınıfı terminolojide "Test Double" tipi olarak geçmekte(Hatta Stub türünden bir nesne olduğunu ifade edebiliriz) Test Double, basitçe bir üretim nesnesinin test amaçlı olarak değiştirilerek kullanılması olarak ifade ediliyor. Hatta kullanım şekline göre Dummy, Fake, Stubs, Spies ve Mocks gibi farklı türleri de bulunuyor. En çok kullanılan versiyonlar Mocks ve Stubs nesneleri. "Test Double" türlerini uygulamalarımızda daha kolay kullanabilmek de mümkün. Bu işe özel kütüphaneler bulunmakta.

Mock Nesne Kullanımı

Şimdi Moq Nuget paketini kullanarak Stub yerine mock nesne kullanımını nasıl yapabileceğimize bir bakalım. Bu sayede mock kullanımını daha sade bir şekilde öğrenebiliriz. İlk olarak test projesine Moq4 paketini eklememiz gerekiyor. Bunun için Visual Studio Code terminalinden aşağıdaki komutu vermemiz yeterli.

dotnet add package Moq

Sonrasında test metodunda Moq kütüphanesini kullanmaya başlayabiliriz. Örneğimizde yapıcı metod odaklı enjekte mekanizmasını kullanabiliriz. Hatırlayacağınız gibi bağımlılığın yapıcı metod üzerinden aktarımı söz konusuydu.

using System;
using CodeKata.Services;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace CodeKata.Tests
{
    [TestClass]
    public class CalculationServiceTests
    {
        [TestMethod]
        public void Should_Return_Current_Invoice_List_Is_Ok()
        {
            var mockUserService=new Mock<IUserService>();
            mockUserService.Setup(o=>o.CheckRequest("{\"operation\":\"Sum\"}")).Returns(true);
            var calcService=new CalculationService(mockUserService.Object);
            var result=calcService.GetInvoices();
            Assert.AreEqual("Invoice list",result);
            mockUserService.Verify(o => o.CheckRequest("{\"operation\":\"Sum\"}"), Times.Once());
        }
    }
}

Mock nesne örneğini oluştururken generic bir parametre kullanıyoruz. Burada mock'lamak istediğimiz tip IUserService arayüzü. Sonrasında Setup fonksiyonuna bir çağrı yapılıyor. Setup metodunda CheckRequest metodunu çağırıp ve sonucunda da geriye true döndürülmesini istediğimiz belirtiyoruz. Burada ilgili mock tipinin herhangi bir metodunun ele alabiliriz. Yapılan şey o fonksiyon çağırılmış da dönüşünde Returns içerisinde yazan değer döndürülmüş hissiyatını vermek. CalculationService sınıfına ait nesneyi örneklerken de yapıcı metoda parametre olarak mock nesnesini bir başka deyişle çalışma zamanında IUserService için örneklenen referansını atıyoruz. Sonrasında tek yaptığımız GetInvoices fonksiyonunu çağırmak. Kabul kriterinin kontrolünden sonra birde doğrulama işlemimiz var. Burada ilgili operasyonun sadece bir kere çağırılıp çağırılmadığını kontrol etmekteyiz. Test beklendiği gibi çalışacaktır.

Pek tabii TDD tarafında Dependency Injection kullanımı bu örnek kodlarda olduğu kadar kolay olmayabilir. Özellikle legacy olarak anılan eski projleri sonradan Continuous Integration hattına soktuğumuzda test yazmak gerçekten başa bela olabilir. Çok fazla sayıda özellik barındıran Entity sınıfları ile yürüyen ve iş akışı karmaşık fonksiyonar için entegrasyon testleri yazmak istediğinizi düşünün. Mock nesne kullanımı biraz can acıtıcı olabilir ama uzun vadede rahat edileceği kesindir.

Böylece geldik bir makalemizin daha sonuna. Bu yazımızda Dependency Injection kavramını entegrasyon testlerinde değerlendirerek daha iyi anlamaya çalıştık. Size tavsiyem var olan entegrasyon testlerinizde belli başlı bağımlılıkları mock nesneler kullanarak ortadan kaldırmaya çalışmanız olacaktır. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Teknik Borçları(Technical Debt) Azaltmak

$
0
0

Merhaba Arkadaşlar,

Bir yazılım ürünü geliştirilirken dikkat edilmesi gereken konuların başında kod kalitesi geliyor. Kaliteli kod, bilinen kodlama standartlarına uyan, okunabilirliği yüksek, karmaşıklığı az, dokümante edilmiş ve bakımı yapılabilir özellikleri barındırmak durumunda. Bu kurallara uymaya çalışmak geliştirme sürelerini uzatsa da uzun vadede kalitenin korunması için gerekli. Üstelik endüstüriyel normlara uygun, derecelendirilebilir uygulamalar geliştirmek istiyorsak kuvvetle üzerinde durulması gereken bir konu. Eğer kaliteyi bozacak ihlaller yaparsak uygulama arkasında ödenmesi zor büyük borçlar bırakabiliriz. Nam-ı diğer Teknik Borç(Technical Debt)

Teknik borç yeni bir kavram değil. İlk olarak Wiki’nin kurucusu, Extreme Programming ve Design Patterns kavramlarının öncülerinden olan Ward Cunningham tarafından ortaya konulmuştur. Oluşmasına etken olan sebepler vardır. Bazen geliştirilen ürünün proje bitiş süresi sebebiyle mecburen stratejik olarak kabul edilir ya da ekip kendi insiyatifinde bunu taktiksel olarak kabul edebilir. Bazı durumlarda ekibin yetkinliğinin az olması sebebiyle ortaya çıkar. Ancak belki de en kötüsü teknik borç üstüne yeni yapılan geliştirmelerin getirdiği ek borçlardır.

Bazen artık vazgeçilmesi gereken teknikler üzerine oturmuş çözümlerin, Tool güncelemeleri ile revize edilerek yaşatılması yoluna gidilir. Bu, stratejik bir karar olmakla birlikte kök nedeni gidermekten uzak bir yaklaşımdır.

Teknik borçların bir kısmı pek çoğumuzun bilmeden de olsa gelecek programcılara bıraktığı ve hatta istemeden de olsa üzerine aldığı sorunlardır aslında. Bu sorunlar sebebiyle zamanla kalitesi bozulan, problemleri çoğalan, güvenilirliği ve daha da kötüsü itibarı azalan ürünler ortaya çıkar. Teknik borçların temizlenmesi mi, müşterinin yeni isteklerinin karşılanması mı derken ürün, üzerinde çalışan programcıları eskitmeye de devam eder. Sonunda legacy olarak tanımlanan, işini yapan ama kimsenin de ellemek istemediği yaşamını devam ettiren korkutucu projeler oluşur.

Uygulama çalışır halde olduğu ve müşteri istekleri karşılandığı sürece ürünün başarılı olduğu düşünülür. Ancak teknik borçlanma arttıkça oluşan bakım maliyetleri ve ek olarak gelen başka sorunlar bir noktadan sonra can sıkıcı hal almaya başlar.

Yeni istekler için hesapta olmayan başka bulguları da ortadan kaldırmaya çalışırken uygulanan hızlı çözümler borç üstüne borç ekler. Nihayetinde müşteri(herkes diyebiliriz) bence mutsuz olur.

Unutulmamalıdır ki motivasyon kaybı, çözüm üretmenin önündeki en büyük engellerdendir.

Teknik borçlanmanın önüne geçmek için alınabilecek belli başlı tedbirler var. Her şeyden önce yazılım geliştirme şeklimizi değiştirmemiz gerekiyor. DevOps gibi kültürlerde yaşamalı, şeffaf olmalı, çevik teknikleri işin içerisine katmalı, pair programming uygulamaktan korkmamalıyız.

Test odaklı geliştirme de kalitenin artması için önemli. Hatta DevOps’un olmazsa olmazlarından birisi. TDD(Test Driven Development) en azından Red, Green, Blue ilkesine göre kod geliştirmemizi öneriyor. Blue(Refactoring)olarak nitelendirilen ve kodun yeniden gözden geçirildiği kısım bile sonradan oluşacak borçların önüne geçmek için önemli. Hatta Code Review süreçlerinin işletilmesi de gerekiyor. Pull Request kavramı da buna hizmet eder nitelikte(Farkındaysanız bunların tamamı DevOps kültüründe bahsi geçen konulardan)

Aşağıdaki tabloda kodu yeniden gözden geçirmenin ve iyileştirmenin teknik borçlanma ile olan ilişkisi ifade ediliyor. Zaman ilerledikçe refactor edilmeyen kodlar teknik borcun artmasına ve değişim maliyetlerinin yükselmesine neden olmakta. Hayatlarımızın belli dönemlerinde rastladığımız(rastlayacağımız) o büyük projeleri düşünün. Hani bakım maliyetleri yüksek olan. Temel sebep, zaman içerisinde çoğalarak artan teknik borç miktarıdır.

Yazılımcıya düşen bir çok görev var. Test güdümlü yazılım geliştirmeye yatkın olması, kod kalite standartlarından haberdar olması, kokan koddan rahatasızlık duyması, kodlama standartlarının farkında olması, temiz kodun ne anlama geldiğini bilmesi çok önemli. Ancak birey olarak bilinçlenmek de yeterli değil. Kullandığımız programlama dillerinden ortamlara kadar hemen her şey zamanla yarışırcasına yenileniyor, değişiyor. Dolayısıyla temel bilgileri bilsek bile yardımcı araçlardan faydalanmak gerekiyor.

Çeşitli ölçümleme araçları ile elde edeceğimiz metrik değerler bulunuğumuz durumu görmek, iyiye mi gidiyoruz bilmek, çeşitli riskleri erkenden hissedip gerekli tedbirleri önceden alabilmek için her alanda olduğu gibi yazılım kalite örgüsünde de kritik.

Örneğin kodun statik ve dinamik olarak analiz edilmesi, raporlar çıkartılması ve buna göre gerekli düzeltmelerin yapılması için belli başlı araçlardan yararlanılabilir. SonarQube da bu araçlardan birisi ve beni teknik borçlanma ile ilgili olarak bildiklerimi özetlememin sebepleri arasında.

SonarQube bir statik kod analiz aracıdır. Continuous Inspection(Sürekli denetim) olarak tanımlanan kültürün bir parçası olarak kod kalitesini arttırmak için CI/CD hattında kullanılır.

Kodun boyutu, test edilirliliği, karmaşıklığı, tekrarları, güvenlik açıkları gibi kısımlarına odaklanır. Temel amaç teknik borçların azaltılması için gerekli çıktıları sunmak ve önerilerde bulunmaktır.

Biz de DevOps odaklı kültür değişimimiz süresince kod kalitesini arttırmak ve teknik borçları azaltmak için bazı araçlardan yararlanıyor ve hatta danışmanlık alıyoruz(Burada Saha Bilgi Teknolojileri ve Emre Dundar için ayrı bir parantez açmam lazım. Özellikle kod kalitesinin arttırılmasına yönelik farkındalığın oluşturulmasında çok değerli katkıları var)

Tabii test odaklı geliştirilmeyen, üzerinden çokça yazılımcının geçtiği yaşlı projelerde bu araçların sonuçları pek de beklediğimiz(aslında beklediğimiz)gibi değil. Yeni nesil ürünlerde ise inanılmaz derecede yardımcı oluyor ve işin başında tedbirler almamızı sağıyor. Nitekim belirli ihlaller ürünün çıkmaması için yeterli.

Bu amaçla SonarQube ve SonarLint araçlarından yararlanıyoruz. SonarQube, VSTS Continuous Integration hattı üzerinde devreye girmekte. Konulan kurallara bağlı olaraktan check-in’lenmiş kodun Continuous Delivery noktasına geçirilmesine veya geçirilmemesine karar verebiliyor. Eğer sorunlar varsa bunlar için geri bildirimlerde bulunuyor(Bug’ın sahibine mail ile bildirilmesi gibi) Aşağıdaki örnek ekran görüntüsünde aracın var olan ürünlerimizden birisi için olan rapor ekranını görebilirsiniz(Şeffaf olmaktan zarar gelmez)

Burada çok önemli bilgiler yer alıyor. Örneğin projede toplam 48 bug varmış ve son yapılan geliştirmeler sonrası 13 bug daha dahil olmuş. A, E, D gibi ifadelerle sınav notumuzu görebiliyoruz. Vulnerabilities kısmı kritik. Hatta ilk ele alınan kısım olduğunu ifade edebilirim. Burada özellikle kod bazlı injection’a sebep olabilecek güvenlik açıkları gibi bilgiler yer alıyor. OWASP benzeri standartlar göz önüne alınarak çalıştırılan kurallar da söz konusu.

SonarQube benzeri kod analiz araçlarından birisi de Cast’tir. Ancak Cast dinamik kod analizi yapar. Yani kodu çalışma zamanında denetler ve buna göre rapor üretir. ING Bank bünyesinde çalıştığım dönemlerde Cast aracından yararlanılmaktaydı. Aylık olarak gelen raporlarda temizlenmesi beklenen önceliklendirilmiş bulgu listesi yer alırdı.

Bir de tabii kokan kod(Code Smells) durumu vardır. Kod standartlarına pek uyulmadığı durumlarda bir süre sonra kodda unutulan iyileştirmelerin sayısı artar. Bu da teknik borç oluşmasına sebebiyet veren etkenlerdendir. Nitekim kodun okunurluğunu zorlaştıran, boat anchor gibi anti-pattern’lerin oluşmasına neden olan durumlar vardır(Daha fazlası için sizi şöyle alalımKoddan koku geldiğini anlamanın belli semptomları vardır. Bunları kabaca aşağıdaki gibi sıralayabiliriz.

  • Tekrar eden kod parçaları(Duplicated Code)
  • Anlaşılması güç uzun metod gövdeleri
  • Birden çok şeyi yapmaya çalışan büyük sınıflar(God Object)
  • Çok sayıda parametre
  • Birden fazla yerde tekrar eden Switch ifadeleri
  • Attığı taş kurbayı ürkütmez felsefesindeki tembel sınıflar (Lazy Class)
  • Kodun ne yaptığını anlatan yorum satırları(Bu kısım her zaman tartışmaya açık sanırım)

Tekrar grafiğe dönecek olursak kokan kodların temizlenmesi için öngörülen sürelerin de yer aldığını görebiliriz. Toplamda 13 günlük(tahmini bir süre ve aracın bunu hangi tip developer’a göre verdiğini henüz anlayamadım) bir çalışma yapılması gerektiği ifade edilmekte. Diğer yandan bu yaşlı uygulama için Code Coverage değeri yüzde sıfır. Yani kodun hiç bir kısmı için test yazılmamış durumda. Bu pek de iyi bir durum değil. Kodun test edilebilir olması çok önemli. Raporun bir kısmında da tekrar eden kod bloklarına yer veriliyor. Bu proje özelinde kodun %1.8lik kısmı kod tekrarı içermekte. 188 kod bloğunun tekrar edildiği ifade ediliyor. Issues kısmına geldiğimizde durumla ilgili olarak daha fazla detay görebiliriz.

Severity bölümünde yer alan kısımda derecelerine göre seviyelendirilmiş bulgular yer alıyor. Tahmin edileceği üzere Blocker, Critical ve Major kategorisine giren maddeler öncelikli olarak değerlendirilmeliler. Aşağıda bu kısımlara ait örnekler yer alıyor.

Blocker örneği(1o dakikalık bir efor öngörülüyor ve kokan kod kategorisinde olup bizi bloklayacak bir problemden bahsediliyor)

Critical örneği.

Major örneği.

Bu bulgulara bakıldığında SonarQube’ün detaylı çözüm yollarını ve sorunun olduğu kod dosyalarını görebiliyoruz. Tüm bu kriterlere göre bir Quality Gate puanı hesaplanıyor. Şu anki tabloya göre kodun güvenilirliğinin zayıf olduğunu ve bu sebepten Failed statüsünde kaldığını söyleyebiliriz. Yani CI Server bu paketi hiç bir şekilde taşıma kapısına göndermeyecek.

SonarQube CI server üzerinde kurgulanan bir ürün ancak bulut tabanlı bir sürümü de bulunuyor. Hatta Docker imajını kullanmak da mümkün.

Geliştirici olarak SonarQube sunucusuna gelmeden önce de bir takım tedbirler alabiliriz. Bu noktada SonarLint aracından yararlanabileceğimizi ifade edebilirim. SonarQube sunucusu ile de entegre olabildiği için şirket bünyesinde konulan kural setlerine bağlı kalarak geliştirme yapma şansımız var. Ama yoksa bile local geliştirmeler için varsayılan kural setlerinden yararlanmak mümkün. Hem Visual Studio hem de Code ile eklenti olarak kullanılabilen bir ürün SonarLint.

Kendi github(veya benzeri) repository’inizdeki deneysel projelerinizde çalışırken bile kod kalite standartlarını uygulatmanızı öneririm. Bu, kaliteli kod üretme alışkanlığını kazanmak için önemli bir pratiktir.

Pek tabii static kod analizi için geliştirme ortamı ile birlikte gelen ürünler de kullanılabilir. SonarQube, CI hattı ile entegre olabilen merkezi bir statik kod analiz aracı olduğu için local araçlara göre daha fazla tercih edilmekte. Aslında muadil olan ürünler de mutlaka var. Çok fazla araç bağımlısı olmamak da gerekiyor belki ama kod kalitesini arttırmak ve teknik borç yükünü azaltmak için farkındalık yaratacak araçlar bulunduğunu söyleyebiliriz. Biz yazılım geliştiricilerin de bu çerçevede düşünmesi gerekiyor. Eğer yeni bir ürün geliştirmeye başlıyorsanız boyutuna göre mutlaka statik kod analizine tabii olun derim.

Farkında olalım, farkında kalalım.

Node.js - Asenkron Talep Karşılama

$
0
0

Merhaba Arkadaşlar,

Adrenali oldukça yüksek(özellikle benim için) ve zorlayıcı bir Cumartesi gününü geride bıraktım. Yo yo sandığınız gibi Cape Town'da büyük beyazlar ile dalış yapmadım ya da Helikopter'den Bungee Jumping... Hatta deployment sırasında canlı ortam datalarını da silmedim. Tek yaptığım Vialand'e gitmek oldu. Daha ilk turda bindiğim Vikings beni yeterince heyecanlndırırken, "Nefes Kesen" neredeyse ses hızına yaklaştığımı hissettirdi :P

Ehh, yanınızda bu adrenaline doymayan bir arkadaşınız veya çocuğunuz varsa o aletten diğerine koşturmayı bırakın her bir aleti defalarca deneyimlemek zorunda da kalabilirsiniz. Hoş bunu sevenler ve etkilenmeyenler için inanılmaz derecede eğlenceli bir ortam söz konusu. Lakin benim gibi yaşlı bünyeler için aslında bu kadar adrenalin biraz(belki birazdan da fazla)ürkütücü diyebilirim. Bu yoğun heyecan üzerine beni dengeleyen tek yer çalışma odam oldu.  Viking'deki sulu inişleri, Rollar Coaster'daki 38 saniyelik öldürücü heyecanı, Adelet Kulesinden yapılan 50 metrelik sert düşüşü bir kenara bıraktım ve West World'e doğru yol aldım. Elimde incelenmeyi bekleyen ve hafta boyu gerek Pluralsight eğitimleri gerek dokümanlar olsun çalıştığım güzel bir konu vardı.

Bu yazımızda Node.js ile geliştirilmiş sunucu uygulamalarında async kullanımını inceleyeceğiz. Amacımız istemci talebi sonrası arka planda paralel servis çağrıları gerçekleştirmek ve ayrıca bu süreç sırasında sunucuya gelecek diğer isteklerinde değerlendirilebileceğini görmek. Bunlara ilaveten ön tarafta konuşlandıracağımız ana servisin bir yönlendirici(router) gibi kullanılabileceğini öğreneceğiz. Haydi gelin hiç vakit kaybetmeden serüvenimize başlayalım. Konuyu basit bir şekilde anlayabilmek adına örnek bir senaryo üzerinden gitmekte yarar var. Başlangıç için aşağıdaki şemayı göz önüne alabiliriz.

İki farklı MongoDb (farklı türlerden de olabilir) veri depomuz olduğunu düşünelim. Bunların sayısı daha da artabilir. Her iki mongodb ile ayrı ayrı çalışan servislerimiz var. JSON tabanlı basit Rest servisleri olarak ele alabiliriz. Bu iki oluşumun farklı sunucular üzerinde tesis edildiğini varsayalım. Önde duran ve belirli talepler için arka taraftaki ilgili servislere yönlendirme(routing) işini üstlenen bir başka servis var. Bu servise vereceğimiz temel görev, player ve team servislerine paralel talep gönderek çıktıların istemciye yollanması. Yani öndeki servisimiz takım ve oyuncu listelerini veren servis metodlarını paralel olarak işletip tamamı elde edilince istemciye cevap dönecek.

Testlerimiz sırasında arkadaki servislerin standart get operasyonlarında duraksatma yapacağız(Örneğin 7şer saniye kadar) Bu durumda ervisleri arka arkaya çalıştıracak olsak çıktıların toplamda 14 saniye civarında elde edilmesi beklenir. Ancak paralel çalıştırıp her iki çıktıyı da 7 saniye civarlarında elde etmemiz mümkün. İşte bu noktada async modülü ve paralel fonksiyon çalıştırma özellikleri işimize yarayacak. Hatta bu veri elde etme işlemi yapıldığı süre boyunca öndeki servisimiz farklı talepleri de karşılayabilir durumda olacak ki bu da Node.js'in doğal çalışma prensipleri ile mümkün. Gerçek hayat senaryolarında sıklıkla ihtiyaç duyacağımız bir senaryo. Bakalım Node.js tarafında bu iş nasıl yapılabiliyor basitçe inceleyelim. 

Öncelikle West-World(Ubuntu 16.04 - 64bit)üzerinde çalıştığımı ve MongoDB'nin Compass Community Edition'ının yüklü olduğunu ifade edeyim. Bunlara ek olarak tabii ki node.js'de sistemde yüklü durumda. Biz üç servisimizi de aynı makine üzerinde ama farklı portlardan sunacağız. Bu şekilde grafikteki senaryoyu taklit etmeye çalışacağız. Çözümümüze ait temel klasör yapısını aşağıdaki gibi kurgulayarak işe başlayalım. 

models
---player.js
---team.js
server
---MainServer.js
---PlayerServer.js
---TeamServer.js

Örneklerde kullanacağımız bir takım npm paketleri var. İşlerimizi kolaylaştırması açısından. MongoDb ORM eşlemesi için mongoose, REST servis tarafı için express, asenkron işlemleri kolaylaştırmak için async ve son olarak JSON parsing için body-parser... Terminalden aşağıdaki komutları kullanarak gerekli kurulum işlemlerini yapabiliriz.

sudo npm install mongoose
sudo npm install express
sudo npm install request
sudo npm install async
sudo npm install body-parser

MongoDb tarafında kullanılacak iki temsili veritabanı modelimiz var. Player ve Team. Bunlara ait entity nesnelerini aşağıdaki gibi tanımlayabiliriz.

Player.js

var mongoose = require('mongoose');

var playerSchema = mongoose.Schema({
    fullName: String,
    size: String,
    position: String
});

module.exports = mongoose.model('Player', playerSchema);

Team.js

var mongoose = require('mongoose');

var teamSchema = mongoose.Schema({
    name: String,
    city: String
});

module.exports = mongoose.model('Team', teamSchema);

Her iki kod parçasında mongoose paketinden yararlanılıyor. Schema metodunda Team ve Player nesnelerinin özelliklerini tanımlıyoruz. Bu özellikler aynen MongoDb tarafında da kullanılacaklar. Team ve Player nesnelerini modül üzerinden dışarıya açarken de model fonksiyonundan yararlanılmakta. Burada model adlarını ve eşleştikleri şemaları belirtmekteyiz. Şimdi de Player ve Team nesneleri ve dolayısıyla MongoDb veritabanı ile çalışacak olan REST servislerine ait kodlarımızı yazalım. PlayerServer sınıfının kod içeriğini aşağıdaki gibi geliştirebiliriz.

var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var Player = require('../models/player.js');

var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/player', { useMongoClient: true });

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
    extended: true
}));

app.post('/players', function (req, res) {
    var newPlayer = new Player(req.body);
    newPlayer.save(function (err) {
        if (err) {
            res.json({ error: err });
        };
        res.json({ info: 'oyuncu bilgisi oluşturuldu' });
    });
});

app.get('/players', function (req, res) {
    Player.find(function (err, players) {
        if (err) {
            res.json({ error: err });
        };
        setTimeout(function () {
            res.json({ data: players });
        }, 7000);
    });
});

app.get('/players/:id', function (req, res) {
    Player.findById(req.params.id, function (err, player) {
        if (err) {
            res.json({ error: err });
        };
        if (player) {
            res.json({ data: player });
        } else {
            res.json({ info: 'oyuncu bulunamadı' });
        }
    });
});

var server = app.listen(7001, function () {
    console.log('PlayerServer is online http://localhost:7001/');
});

Yerele makinenin 7001 nolu portu üzerinden hizmet veren PlayerServer, express ve mongoose modüllerini etkin bir şekilde kullanmakta. Model olarak Player.js dosyasından yararlanılıyor. İşlemleri basitleştirmek adına sadece üç operasyon sunmaktayız. Tüm oyuncu listesini çekebiliyoruz ya da mongoDb'de oluşturulan objectId bilgisini kullanarak tek bir tanesini talep edebiliyoruz. Birde oyuncu ekleme işini kolaylaştırmak için yazdığımız Post tabanlı çalışan metodumuz var. MongoDb bağlantısı connect metodu üzerinden sağlanmakta. Eğer MongoDb örneğinde Player veya Team gibi veritabanları yoksa ilk bağlantı sırasında oluşturulacaklardır. Kodda dikkat edilmesi gereken noktalardan birisi de setTimeout metodunu kullanmış olmamız. Bunu testimizin bir parçası olarak düşünebilirsiniz. Senaryomuza göre tüm oyuncu listesinin çekilmesi yaklaşık olarak yedi saniyede gerçekleşiyor. PlayerServer kendi başına çalışabilen bir servis olduğundan belli bir port üzerinden yayın yapacak şekilde ayarlanmış durumda. Örneğimize göre yerel makinedeki 7001 nolu port üzerinden hizmet verecek. TeamServer dosyasındaki kodlarda PlayerServer tarafındakine oldukça benzer. Sadece Team modeli ile çalıştığını söyleyebiliriz.

var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var Team = require('../models/team.js');

var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/team', { useMongoClient: true });

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
    extended: true
}));

app.post('/teams', function (req, res) {
    var newteam = new Team(req.body);
    newteam.save(function (err) {
        if (err) {
            res.json({ error: err });
        };
        res.json({ info: 'takım bilgisi oluşturuldu' });
    });
});

app.get('/teams', function (req, res) {
    Team.find(function (err, teams) {
        if (err) {
            res.json({ error: err });
        };
        setTimeout(function () {
            res.json({ data: teams });
        }, 7000);
    });
});

app.get('/teams/:id', function (req, res) {
    Team.findById(req.params.id, function (err, team) {
        if (err) {
            res.json({ error: err });
        };
        if (team) {
            res.json({ data: team });
        } else {
            res.json({ info: 'takım bulunamadı' });
        }
    });
});

var server = app.listen(7002, function () {
    console.log('TeamServer is online http://localhost:7002/');
});

PlayerServer içerisindeki çalışma prensiplerinin Team nesnesi için değiştirilmiş olduğunu görebilirsiniz. Birde tabii farklı bir port üzerinden yayın yapmaktayız. Bu iki servisi kullanan MainServer isimli yönlendirme servisinin kodları biraz daha farklı olacak. Aynen aşağıda olduğu gibi.

var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var async = require('async');
var request = require('request').defaults({
    json: true
});

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
    extended: true
}));

app.get('/sports/api', function (req, res) {
    async.parallel({
        player: function (callback) {
            request({ uri: 'http://localhost:7001/players' }, function (error, response, body) {
                if (error) {
                    callback({ service: 'player', error: error });
                    return;
                };
                if (!error && response.statusCode === 200) {
                    callback(null, body.data);
                } else {
                    callback(response.statusCode);
                }
            });
        },
        team: function (callback) {
            request({ uri: 'http://localhost:7002/teams' }, function (error, response, body) {
                if (error) {
                    callback({ service: 'team', error: error });
                    return;
                };
                if (!error && response.statusCode === 200) {
                    callback(null, body.data);
                } else {
                    callback(response.statusCode);
                }
            });
        }
    }, function (error, results) {
        res.json({
            error: error,
            results: results
        });
    });
});

app.get('/aloha', function (req, res) {
    res.json({ yuhuuu: Date.now() });
});

var server = app.listen(7000, function () {
    console.log('MainServer is online http://localhost:7000/');
});

MainServer içerisinde async ve request modüllerinden yararlanarak paralel çalışma disiplinlerini uyguluyoruz. sports/api şeklinde gelecek olan bir talep ele alınırken async modülünün parallel metodu çağrılıyor. Burada player ve team isimli iki task oluşturulduğunu görebilirsiniz. Herbiri request nesnesini kullanarak arka taraftaki servislere HTTP Get talebinde bulunuyor. Eğer üç servisinde ayrı makinelerde barındırıldıklarını düşünecek olursak, MainServer Router Service görevini de icra ediyor diyebiliriz. parallel fonksiyonu içerisindeki görevler tamamlandığında(yani arka servislere yapılan çağrıların sonuçları elde edildiğinde) ikinci parametredeki fonksiyon devreye giriyor ve istemci tarafına sonuçların JSON formatında döndürülmesi sağlanıyor. Paralel talep işlenen get fonksiyonu dışında aloha şeklinde gelecek taleplerin ele alındığı bir metodumuz daha var. Yani MainServer birisi parelel task barındırmak suretiyle iki HTTP Get operasyonu sunmakta. Bu fonksiyonu neden koyduğumuzu yazının sonlarında daha net anlayacağımızı düşünüyorum.

PlayerServer ve TeamServer önceden de belirttiğimiz üzere tek başlarına da hizmet verebilirler. 7001 ve 7002 gibi iki farklı porttan aynı anda yayınlanabilirler. Her ikisi için deneysel olması adına POST, GET, GET(ID ile) olmak üzere üç operasyon sunuluyor. İlerlemeden önce bu operasyonların işlerliğini basitçe test etmekte yarar var. Örneğin Postman kullanılarak aşağıdaki komut ile yeni bir oyuncu bilgisini eklememiz mümkün.

Request : HTTP Post
Address : http://localhost:7001/players
Body : {"fullName":"toni kukoç","size":"2.06cm","position":"power forward"}

Sonuç aşağıdaki gibi olacaktır.

Eğer eklenen oyuncuların tamamını çekmek istersek aşağıdaki gibi bir talepten yararlanabiliriz.

Request : HTTP Get
Address : http://localhost:7001/players

Tabii içeriye koyduğumuz 7 saniyelik şaşırtmaca sebebiyle sonuçlar anında ekrana yansımayacaktır. Belli bir IDye bağlı oyuncuyu görmek istersek de aşağıdakine benzer bir talep yapmamız yeterli olur.

Request : HTTP Get
Address : http://localhost:7001/players/5b9e5423c826230460cc0310

Benzer çalışmalar TeamServer servisi çalıştırılarak da deneyimlenebilir. İlerlemden önce yazdığını TeamServer hizmetinin operasyonlarını da test etmenizi öneririm.

Gelelim asıl senaryomuza. Şimdi üç servisi de terminalden ayağa kaldırmamız lazım(ayrı terminal pencereleri kullanarak bu işi yapabiliriz ya da forever gibi bir npm paketinden faydalanabiliriz)

node PlayerServer.js
node TeamServer.js
node MainServer.js

Yine Postman'den yararlanarak aşağıdaki talebi gönderelim.

Request : HTTP Get
Address : http://localhost:7000/sports/api

Hem oyuncu hem de takım listeleri aynı JSON içeriğinde çıktı olarak döndürüldüler. Ancak dikkat edilmesi gereken nokta sadece arka planda yapılan adres yönlendirmesinin başarılı bir şekilde çalışmış olması değil. Her iki servisin get operasyonu 7 saniyelik duraksatma içeriyor ve servisin toplam cevap süresi de 7 saniye civarında. Bu MainServer'a gelen talep sonrası PlayerServer ve TeamServer'a eş zamanlı olarak taleplerin gönderilmiş olduğu anlamına da geliyor. Bu noktada servislerden birisinin duraksatma süresini kaldırıp tekrardan test etmenizi tavsiye ederim. Hatta MainServer'a yapılan talep sonrası oluşan 7 saniyelik bekleme süresince şu talebi göndermenizi öneririm.

Request : HTTP Get
Address : http://localhost:7000/aloha

Yani sorumuz şu; Yedi saniyelik talep cevaplama süresi boyunca yapılacak olan yukarıdaki istek anında cevaplandırılır mı? ;) Tahmin edeceğiniz üzere node.js doğal çalışma dinamikleri gereği ilgili talebi duraksatmayacaktır. Dolayısıyla paralel olarak n sayıda talebin servis tarafında ele alınması mümkündür.

Bu yazıdaki örneğimizde bir yönlendirici servisin nasıl yazılabileceğini ve herhangibir talebin asenkron çalışma prensipleri doğrultusunda paralel görevleri nasıl başlatabileceğini görmüş olduk. Ayrıca eş zamanlı başlatılan görevlerin yer aldığı taleplerin çalışması uzun sürse bile, diğer isteklerin bloke olmadan cevaplanabileceğini öğrenmiş olduk. Bu bilgiler çerçevesinde yüksek performanslı, eş zamanlı talep karşılama yeteneklerine sahip ve talep için paralel görevler icra edebilen servislerin Node.js ile kolayca geliştirilebileceğini ifade edebiliriz.

Ben aynı durumu .Net Core tarafında da deneyimlemeye çalışacağım. Nitekim o tarafta da bu tip geliştirmeler yapmak mümkün. Ayrıca melez çözümler de uygulanabilir. Örnek senaryomuzdaki Player ve Team servisleri pekala farklı teknolojiler ile geliştirilmiş REST API servisleri olabilirler. Hatta MongoDb dışında veri depolama aygıtlarını da kullanabiliriz. Player servisinin MySQL ile konuşan Scala ile yazılmış bir uygulama olduğunu, Team servisinin de MongoDB ile yürüyen .Net Core ile yazılmış başka bir servis olduğunu düşünün(Hatta düşünmeyin kendi denemelerinizde bu kurguyu çalışın) Servis sayıları arttırılabilir ve çeşitlendirilebilir. Bir açıdan n sayıda microservice önüne Node.js ile yazılmış bir MainServer'ı koyduğumuzu da düşünebiliriz. Konuyu derinlemesine araştırmakta yarar var. Ancak gözlerim iyiden iyiye kapanmak üzere. Dolayısıyla müsadenizi istemek durumundayım. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Örneği github üzerinden indirebilirsiniz.

Bilmiyordum, Öğrendim : SQL Merge

$
0
0

Merhaba Arkadaşlar,

Gün geçmiyor ki çevremdeki insanlardan yeni bir şeyler daha öğrenmeyeyim. Bugün o günlerden biriydi...

İş yerinde elimizin her an üzerinde olabileceği binlerce SQL nesnemiz var. Tablolar, fonksiyonlar, sp'ler... Bazen iş biriminden gelen istekler doğrultusunda onlara müdahale etmemiz veya yenilerini yazmamız gerekiyor. Sorun şu ki 2000li yılların başından kalan ve yorum satırlarına bakıldığında üzerinden bir çok geliştiricinin geçtiği spl'lerimiz var. Bazen buradaki kalabalık sorgular arasında samanlıkta iğne ararcasına sorun çözmeye çalıştığımız oluyor. Çok motive edici bir durum değil takdir ederseniz ki. Şükür ki alanlarında yetkin ekip arkadaşlarımız var ve yeri geldiği zaman söyledikleri ufak bir ipucu ile hayatımızı kolaylaştırıyorlar (ki bu etkili yardımlaşmada agile metodolojide koşan bir takım olmamızın da büyük etkisi var)

İşte geçenlerde çok uzun sürdüğü için sorun yaratan bir sp(Stored Procedure) ile cebelleşirken değerli bir yardım geldi. Ekip arkadaşımın bir önerisi üzerine kendimi SQL Merge komutunu araştırırken/öğrenirken buldum. 2008den beri var olan benim bihaber olduğum bu komutu öğrenirken keyifli anlar da yaşadım. Normalde çok kötü bir SQLciyimdir ama Merge komutunu uygulamalı olarak denedikten sonra şirketteki o kallavi sorgunun hem daha da hızlandığını hem de daha okunur hale geldiğini gördüm. Sonunda konuyu kalem alıp paylaşmanın iyi olacağını fark ettim. Hem kendim için kayıt altına almış hem de yazıp çizerek konuyu daha iyi öğrenmiş olmam da ödülüm olacak tabii. Dilerseniz vakit kaybetmeden konumuza geçelim. Başlangıç için aşağıdaki veri içeriklerine sahip iki tablomuz olduğunu düşünelim.

Kaynak tablo içeriği(Book)

BookID      Title                                              ListPrice             StockLevel
----------- -------------------------------------------------- --------------------- ----------
1           Clean Architecture                                 34,55                 5
2           Clean Code                                         20,00                 5
3           Anti-patterns explained                            15,99                 10
4           Programming C#                                     50,40                 20

Hedef tablo içeriği(Store)

BookID      Title                                              ListPrice             StockLevel
----------- -------------------------------------------------- --------------------- ----------
1           Clean Architecture                                 34,55                 5
2           Clean Code                                         10,00                 5
3           Anti-patterns explained                            15,99                 8
6           Cloud for dummies                                  44,44                 3

Veritabanı ile çalışan pek çok uygulamada bu tip birleştirme odaklı tablolara rastlayabiliriz. Genellikle dışarıdan belirli periyotlarla beslenen bir tablo ve bu tablodaki veri içeriğine göre kendini sürekli olarak güncel tutan bir başka tablo olur. Aynen yukarıdaki senaryoda görülen Kaynak ve Hedef tablolar gibi. Özetle hedef tabloyu kaynak tablodaki değişikliklere göre güncel tutmak istediğimizi düşünebiliriz. Kaynaktan silinenlerin hedeften de silinmesi, güncellenenlerin aynı şekilde hedefte de güncellenmesi veya kaynağa yeni gelenlerin hedef tabloya da aktarılması gibi işlemlerden bahsediyoruz.

Book ve Store tablolarını göz önüne aldığımızda Store tablosundan silinen(4 nolu kayıt), eklenen(6 nolu kayıt), güncellenen(2 ve 3 nolu kayıtlar) ve hiç bir değişikliğe uğramayan(1 nolu kayıt) kitap bilgileri olduğunu görüyoruz. Şimdi Book tablosunu Store tablosuna göre güncellememiz gerekiyor. Elbette bunun bir çok yolu var. Örneğin Cursor açıp kaynak tabloyu baştan sona tarayarak bu işlemi gerçekleştirebiliriz. Ya da Select into ifadesi ile birlikte insert, update, delete sorgularını kullanabiliriz. Belki başka çözümler de söz konusu olabilir. Buradaki gibi az sayıda satır içeren veri kümeleri için seçilen tekniğin bir önemi yok aslında. Ancak tablo kayıt sayısı aynen şirketimizdeki senaryodaki gibi milyonlar seviyesine çıkınca performans sorunları yaşayabiliriz. Bir alternatif olarak üzerinde insert, update ve delete işlemlerini uygulamak için tek bir birleştirme maliyeti üzerinden hareket etmek çok daha verimli olabilir. Merge bu noktada devreye giriyor. 

Normal şartlarda yukarıdaki içerikleri eşleştirmek adına pekala aşağıdaki gibi sorgular yazılabilir (Bildiğim kadarı ile yazdım. Bu konuda alternatifler için aydınlatılmaya ihtiyacım var)

Update Book 
Set 
	Title=S.Title,
	ListPrice=S.ListPrice,
	StockLevel=S.StockLevel
from Store S
	inner join Book B
	on B.BookID=S.BookID
where 
	B.ListPrice<>S.ListPrice or B.Title<>S.Title or B.StockLevel <> S.StockLevel;

Delete from Book Where BookID not in 
	(Select S.BookID from Store S where S.BookID in (Select BookID from Book));

İlk olarak farklılıkları bulup gerçekleştirdiğimiz bir Insert işlemi var. Burada alt sorgu kullandığımızı görebilirsiniz. Güncelleme işleminde ise bir inner join kullanımına gittik. En beter sorgu da silme operasyonu için yazdığım olmalı sanıyorum ki. Bu sorguları işlettiğimizde Book ve Store tabloları eşlenecektir. Lakin bir taşla üç kuş vurabiliriz de. Şimdi konuyu merge ifadesini baz alarak ele alalım. Aşağıdaki uçtan uca sorgu işimizi görür(Ben diğer veritabanlarını kirletmemek adına LearningDb isimli ayrı bir veritabanında çalıştım)

Create database LearningDb;
Use LearningDb;

Create Table Book
(
	BookID int primary key,
	Title varchar(50),
	ListPrice money,
	StockLevel smallint
)
Go
insert into Book
Values
(1,'Clean Architecture',34.55,5),
(2,'Clean Code',20.00,5),
(3,'Anti-patterns explained',15.99,10),
(4,'Programming C#',50.40,20)
Go

Create Table Store
(
	BookID int primary key,
	Title varchar(50),
	ListPrice money,
	StockLevel smallint
)
Go
insert into Store
Values
(1,'Clean Architecture',34.55,5), --Değişiklik yok
(2,'Clean Code',10.00,5), -- Fiyat değişti
(3,'Anti-patterns explained',15.99,8), --Stok seviyesi değişti
(6,'Cloud for dummies',44.44,3) -- Yeni geldi
--(4,'Programming C#',50.40,20) -- Silindi
Go

Select * from Book;
Select * from Store;

Merge Book AS T
Using Store As S
on (T.BookID=S.BookID)
When Matched and T.Title <> S.Title Or T.ListPrice<>S.ListPrice Or T.StockLevel<>S.StockLevel Then --Herhangibir güncelleme varsa
Update Set T.Title=S.Title,T.ListPrice=S.ListPrice,T.StockLevel=S.StockLevel
When Not Matched By Target Then -- Yeni eklenmiş kitaplar varsa
Insert (BookID,Title,ListPrice,StockLevel)
Values (S.BookID,S.Title,S.ListPrice,S.StockLevel)
When Not Matched By Source Then -- Silinmiş kitaplar varsa
DELETE
OUTPUT $action [Event], DELETED.BookID as [Target BookID],DELETED.Title as [Target Title],DELETED.ListPrice as [Target ListPrice],DELETED.StockLevel as [Target StockLevel],
INSERTED.BookID as [Source BookID],INSERTED.Title as [Source Title],INSERTED.ListPrice as [Source ListPrice],INSERTED.StockLevel as [Source StockLevel];

Select * from Book;
Select * from Store;

Merge kısmına kadar yapılan hazırlıklarda örnek bir veritabanı oluşturup içerisine Book ve Store isimli tablolarımızı açıyoruz(Buralarda if exist kullanımına gitmekte yarar olabilir ya da başlarda drop table kullanılabilir) Sonrasında ise Merge ifademiz başlıyor. Book ve Store tablolarını BookID alanı üzerinden birleştirdikten sonra When kelimesi ile başlayan üç ayrı kısım yer alıyor.

Eğer bir eşleşme var ve tabloların Title, ListPrice, StockLevel alanlarının en azn birisinde veya tümünde farklılıklar söz konusuysa Then kelimesinden sonra gelen Update ifadesi çalıştırılıyor. Update ifadesinde T ile belirtilen hedef tablo alanlarının S ile belirtilen kaynak tablo alanları ile beslendiğine dikkat edelim. Eğer hedef tabloda kaynaktaki satırlar ile BookID üzerinden bir eşleşme yoksa 'When not matched by Target Then' sonrasında gelen Insert sorgusu çalışıyor. Burada da kaynak tablodaki alan değerlerinin eklendiğine dikkat edelim. Son olarak hedefte olduğu halde kaynakta olmayan satırlar varsa 'When not matched by source then' sonrasındaki Delete ifadesi çalışıyor ve hedef tablodaki ilgili kayıtlar siliniyor.

Merge sorgusunun tamamlanması için mutlaka ; işareti ile ifadeyi bitirmemiz gerekiyor. Bunu yapmadan önce meydana gelen değişiklikleri takip edebilmek adına output ifadesini çalıştırıyoruz. Burada $action değişkeni ile meydana gelen olay yakalanıyor(o satır için insert, update, delete olaylarından hangisi olduysa) DELETED ve INSERTED isimli hazır tabloları kullanaraktan da hangi tabloda ne gibi bir alan değişikliği olduğunu rahatlıkla görebiliyoruz. Sonuçlar aşağıdaki gibi olacaktır.

Event      Target BookID Target Title                                       Target ListPrice      Target StockLevel Source BookID Source Title                                       Source ListPrice      Source StockLevel
---------- ------------- -------------------------------------------------- --------------------- ----------------- ------------- -------------------------------------------------- --------------------- -----------------
UPDATE     2             Clean Code                                         20,00                 5                 2             Clean Code                                         10,00                 5
UPDATE     3             Anti-patterns explained                            15,99                 10                3             Anti-patterns explained                            15,99                 8
DELETE     4             Programming C#                                     50,40                 20                NULL          NULL                                               NULL                  NULL
INSERT     NULL          NULL                                               NULL                  NULL              6             Cloud for dummies                                  44,44                 3

(4 row(s) affected)

BookID      Title                                              ListPrice             StockLevel
----------- -------------------------------------------------- --------------------- ----------
1           Clean Architecture                                 34,55                 5
2           Clean Code                                         10,00                 5
3           Anti-patterns explained                            15,99                 8
6           Cloud for dummies                                  44,44                 3

(4 row(s) affected)

BookID      Title                                              ListPrice             StockLevel
----------- -------------------------------------------------- --------------------- ----------
1           Clean Architecture                                 34,55                 5
2           Clean Code                                         10,00                 5
3           Anti-patterns explained                            15,99                 8
6           Cloud for dummies                                  44,44                 3

(4 row(s) affected)

Artık her iki tablonun verileri de eş.

Gece yayınevlerinden son listeleri alan servis çalıştığında Store tablosunda yapılan değişiklikler, yukarıdaki sorgu sayesinde Book tablosuna da yansıtılacak ve o günün bayilerinin bakacağı asıl içerik eşleştirilmiş olacak. Bu senaryoyu bir düşünüp kurgulamaya çalışın derim. Görüldüğü üzere merge esasında oldukça pratik bir kullanıma sahip ve birleştirme senaryoları için ideal. Pek tabii kurumun iş kuralları gereği bir merge işlemi her zaman için buradaki using ifadesi kadar sade olmayabilir. Örneğimizde doğrudan primary key alanlar üzerinden bir eşleşme yaptık ancak farklı senaryolar olduğu takdirde using ifadesine parantez açılıp daha karmaşık select ifadelerine ait sonuçların kaynak olarak gösterilmesi de sağlanabilir. Lakin maliyeti yüksek olduğu için kaçınmaya çalıştığımız çeşitli sorguları(sub query'ler, çok sayıda tablolu join'ler vb) buraya almanın çok önemli bir pozitif katkısı olmayabilir. Sonuç itibariyle büyük veri kümelerini kullanarak performans testlerini yapmakta ve execution planlara bakıp gerekli müdahaleleri yapmakta yarar var. Bizim senaryomuz için çalışma zamanı planlarına baktığımızda en azından üç iş yerine tek seferlik bir maliyetin altına girdiğimizi görebiliriz.

İlk uygulama biçimimiz için aşağıdaki gibi bir plan oluşur.

Table Spool maliyetleri biraz yüksek görüldüğü üzere. Merge çalışma planında ise durum aşağıdaki gibidir. Şekilde görülmese de %25lik bir Full Outer Join maliyeti var.

İşin aslı konuyu SQL performans yönetimi konusunda uzman birisinin incelemesi daha doğru olabilir. Genellikle şirketlerin veritabanı operasyon ekipleri perfomans arttırımı gerektiren sorgular için destek oluyorlar. Yine de iş oraya gelmeden önce gerekli ön tedbirleri alıp performans iyileştirmelerini yapmak da biz geliştiricilere düşen önemli bir görevdir. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Viewing all 351 articles
Browse latest View live