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

GoLang - Concurrency (goroutine, channel)

$
0
0

Merhaba Arkadaşlar,

Yazılım ürünlerinde eş zamanlı çalışma modeli oldukça önemli. Uygulamalarda yer alan süreçler çoğu zaman alt iş parçalarından oluşmakta ve bu parçalar uygun koşullarda eş zamanlı olarak yürütülebilmekte. Eş zamanlılık için bir çok dilde destek mevcut. Go dili için de öne çıkan kavramlarından birisi aslında. Concurrency denildiğinde aklımıza bir sürece ait n sayıda görevin(Task) aynı anda çalışması gelmeli. Okuduğum kaynakta buna güzel bir örnek veriliyor: Web Sunucusu. 

Bir web sunucusu istemcilerden gelen talepleri(Request) ait oldukları uygulamalara yönlendirip işleten bir çalışma mekaniğine sahiptir. Hiç bir talep için bir diğerini bekleme söz konusu değil. Web sunucusu bu görevleri eş zamanlı olarak yürütüyor. Concurrency'deki temel amaç da bu zaten. Görevleri aynı anda işletebilmek. Go dilinde goroutine fonksiyonu ve channel yöntemi ile Concurrency işlemlerini gerçekleştirebiliriz. İlk olarak goroutine fonksiyonunu inceleyecek sonrasında channel kavramına değineceğiz.

goroutine

goroutine bir fonksiyon aslında. Eş zamanlı çalışacak fonksiyonları çağırmak için kullanılıyor. Goroutine çalışma modeline göre belleğin stack adı verilen bölgesinde başlangıç için 2Kb kadar yer ayrıldığı ve bu alanın gerektiğinde büyüdüğü ifade ediliyor. Bir Thread için bu alan 1 Mb civarında. Thread oluşturma ve yönetmedeki pek çok karmaşık detay goroutine tasarımına dahil edilmemiş. Goroutine'ler işletim sistemi seviyesinde çoklu thread' de çalışabiliyor. Bu nedenle bir goroutine bloklansa bile diğeri(diğerleri)çalışmasına devam edebilir. Son olarak kullanım maliyeti düşük, hafif ve hızlı bir tasarıma sahip olduklarını belirtebiliriz(goroutine'lerin çalışma prensipleri ve Thread kavramı ile arasındaki farkları incelemek için şu yazıya bakmanızışiddetle tavsiye ederim) 

Şimdi basit bir örnekle konuyu anlamaya çalışalım.

package main

import (
	"fmt"
	"time"
	"sort"
	"math/rand"
	)
	
func main(){
	names:=[]string{"captain kirk","barbara","nik","jon calloway","rici ric","sem vitmor"}
	go sort.Strings(names)
	
	go SaySomething("Hello Concurrency")
	
	//gorouting anonymous func sample
	go func(value int){
		fmt.Printf("%d part is going to go\n",value)
		time.Sleep(time.Second*2)
	}(1000)
	
	for i:=0;i<5;i++{
		start:=rand.Intn(250)
		go Calculate(start,start+1000,time.Second*1)
	}
	var userInput string
	fmt.Scanln(&userInput)
	fmt.Println("All is well")
}

func Calculate(start int,stop int,sleep time.Duration){
	for i:=start;i<=stop;i++{
		time.Sleep(sleep)
		fmt.Printf("%d...",i)
	}
}

func SaySomething(message string){
	fmt.Println("Saying...")
	time.Sleep(time.Second*3)
	fmt.Println(message)
}

Örnek kod parçasında goroutine'lerin farklı kullanımlarına ait birer örnek verilmiştir. 

İlk kullanımda var olan bir Go fonksiyonunun(sort.Strings) eş zamanlı çalıştırılması örneklenmiştir. SaySomething fonksiyonu geliştirici tarafından yazılmıştır ve yine go komutu ile eş zamanlı yürütülür. Üçüncü kullanımda anonymous fonksiyon söz konusudur. Fonksiyon tanımından sonra parantez içerisinde verilen 1000 rakımı, value değişkenin değeridir. Ayrıca bu isimsiz fonksiyon içerisinde 2 saniyelik bir duraksatma yapılmıştır(Bizim .net dünyasından aşina olduğumuz Thread.Sleep gibi)

for döngüsündeki kullanım şekli de oldukça şıktır. 5 defa Calculate isimli fonksiyonun farklı parametre değerlerini alarak eş zamanlı yürütülmesi işlemi örneklenmiştir. Dikkat edilmesi gereken noktalardan birisi de main fonksiyonunun sonunda ekrandan giriş beklenmesidir. Eğer bunu yapmazsak tahmin edeceğiniz üzere program kodu anında sonlanır. Örnek kodun çalışma zamanı çıktısı aşağıdaki ekran görüntüsündekine benzer olacaktır.

channel

Aslında goroutine' ler pratik olsalar da tamamlandıklarında sinyal vermemeleri gibi bir sorunları da vardır. Sessizce işlerini tamamlayıp kaynaklarını iade ederler. İşte bu noktada channel yöntemi devreye girmektedir. Temel olarak bir channel ile goroutine'ler arasında iletişim kurabilir ve eş zamanlı çalışan iş parçaları arasında senkronizasyonu sağlayabiliriz. Channel konusu içerisinde bir çok alt konu da bulunuyor. Öğrenmeye çalışırken biraz zorlandığımı itiraf edebilirim ve konunun çok daha fazla derinliği var. 

İlk Örnek

Basit bir kod parçası ile başlayalım ve kanallar nasıl kullanılıyor ele alalım.

package main

import(
	"fmt"
	"time"
	)

func main(){
	payload:=make(chan string)
	
	go Foo(payload,"code:1234")
	go Bar(payload)
	
	var userInput string
	fmt.Scanln(&userInput)
	fmt.Println("All is well")
}

func Foo(channel chan string,content string){
	time.Sleep(time.Second*3)
	fmt.Println("Foo...")
	channel<-content
}

func Bar(channel chan string){
	fmt.Println("Bar...")
	ctx:=<-channel
	fmt.Println(ctx)
}

Örnek kod parçasında iki goroutine arasında kanal açıp veri transferi gerçekleştirmekteyiz. Foo ve Bar isimli fonksiyonlar chan string tipinden parametre alıyorlar. Burada chan ifadesinden sonra gelen string, kanalda hangi tipten veri taşınacağını ifade etmekte. Dolayısıyla farklı veri tiplerini de bir kanal üzerinden eş zamanlı iş parçacıkları arasında taşıyabiliriz. Bir kanalı oluşturmak için make fonksiyonu kullanılıyor. İşin güzel yanı ise <- operatörü(Bir anda Ruby'deki << geldi aklıma) Bu operatör ile kanala veri bırakıp kanaldaki veriyi alma işlemlerini gerçekleştiriyoruz. Akla son derece yatkın. Operatörün sağından soluna doğru bir işlem akışı gerçekleşmekte.

Foo fonksiyonda belli bir süre duraksatma yapmaktayız(Olayı biraz daha dramatize edelim diye) Pek tabi Foo ve Bar fonksiyonları birer iş parçacığı olarak çağırılıyor. Yani goroutine haline getirilmişlerdir. Sonrasında payload isimli channel tipi bu parçacıklar tarafından kullanılarak veri transferi işlemi gerçekleştirilmiştir. Burada büyüleyici olan eş zamanlı çalışan iki fonksiyona arasında bir kanal açarak veri akışı sağlanmış olmasıdır. Bu ilkel kodun çalışma zamanı çıktısı aşağıdaki gibi olacaktır. 

Senkronizasyon

Kanalları kullanarak eş zamanlı çalışan iş parçaları arasında senkronizasyon da yapılabilir. Bir başka deyişle goroutine olarak başlatılan bir işin sonunda kanala işaret bırakılıp(true veya 1 gibi bir değer örneğin) diğer goroutine'in ilgili işareti alana kadar bekletilmesi sağlanabilir. Örneğin ana fonksiyon içerisinden başlatılan ve uzun sürecek bir iş sonlanmadan uygulamanın kapanmasını engellemek istediğimi durumlarda bu tekniği kullanabiliriz. Aşağıdaki basit kod parçasında bu durum örneklenmektedir. 

package main  

import
(
	"fmt" 
	"time"
)

func main() {     
    chnFlag:=make(chan int,1)    
    go DoHeavyWork(chnFlag)    
    <-chnFlag    
}

func DoHeavyWork(flag chan int){
    fmt.Println("Start...")
    time.Sleep(time.Second*5)
    fmt.Println("Done...")
    flag<-1
}

Kodun çalışma zamanı çıktısı aşağıdaki gibidir.

Burada kritik nokta main fonksiyonundaki <-chnFlag ifadesidir. Bu satırı kaldırınca kod durmadan akacak ve program sonlanacaktır.

Yönlendirme(Direction)

Kanalları fonksiyon parametresi olarak kullanabiliyoruz. Bu durumda tip güvenliği adına kanalın çalışma yönünü de belirleyebiliriz. Yani fonksiyon parametresi olan bir kanalın sadece alıcı veya verici olması garanti edilebilir. Aşağıdaki kod parçasında bu kullanıma bir örnek verilmektedir.

package main  

import(
        "fmt"
    )

func sender(channel chan<- string, message string) {
    channel <- message
}

func comm(receiver <-chan string, sender chan<- string) {
    message := <-receiver
    sender <- message    
}

func main() {    
    scott := make(chan string, 1)
    tiger := make(chan string, 1)
    sender(scott, "My name is Bond.James Bond.")
    comm(scott, tiger)
    fmt.Println(<-tiger)
}

sender isimli fonksiyon sadece mesaj gönderme özelliğine sahip bir kanal ile çalışır. İkinci parametre ile gelen string bilgi ilk parametredeki kanala yazılır. comm isimli fonksiyonun ilk parametresi mesaj okuma yeteneği olan bir kanaldır. İkinci parametre yine mesaj gönderme amacıyla kullanılabilecek bir kanalı ifade etmektedir. İlk parametre ile okunan mesaj ikinci parametre ile gelen kanala aktarılır. Kodun çalışmasında muazzam bir şey yoktur aslında. Öğrenmemiz gereken kanalların alıcı veya verici şeklinde sabit yönlerde kullanılmaya zorlanabilmesidir.

buffer Kullanımı

Kanallar normalde senrkon çalışırlar. Yani mesajı gönderen taraf ile mesajı alacak olan taraf birbirlerini beklerler. Buffer kullanan kanallar inşa ederek birbirleriyle asenkron çalışmalarını sağlayabiliriz(Varsayılan olarak buffer kullanılmamaktadır) Buffer kullanacak bir kanal ile kanal tipinden kaç adet göndereceğimizi de belirtiriz. Yani kanal üzerinden akacak içeriği sayı bazında sınırlandırabiliriz. Bu kısıtlama bir anlamda semaphore tekniği uygulamak olarak da düşünülebilir. Aşağıda buffer kullanmına ilişkin basit bir kod parçası yer almaktadır.

package main  

import "fmt" 

func main() {             
    channel:=make(chan string,3)
    channel<-"dam"
    channel<-"van dam"    
    channel<-"cloud van dam"
    channel<-"jan cloud van dam"
    fmt.Println(<-channel)
    fmt.Println(<-channel)
    fmt.Println(<-channel)
}

Oluşturulan channel tipi 3 string içeriği taşıyacak kapasitede tanımlanmıştır. Pek tabii kapasitesinden fazla mesaj atamaya çalışırsak Deadlock oluşmasına neden oluruz. Aşağıdaki ekran görüntüsünde olduğu gibi.

select ifadesi

n sayıda goroutine çalıştırdığımızda kanallar ve select ifadesini kullanarak işi bitenlerin sonuçlarını almayı başarabiliriz. Bir nevi wait any hali diyelim. Aşağıdaki kod parçasını ele alalım.

package main  

import 
    (
        "fmt" 
        "time"
    )

func CalculateOne(channel chan string){
    fmt.Println("Calculation phase one...")
    time.Sleep(time.Second*2)
    channel<-"phase one is done"
}

func CalculateTwo(channel chan string){
    fmt.Println("Calculation phase two...")
    time.Sleep(time.Second*5)
    channel<-"phase two is done"
}

func EvaluateTestData(channel chan string){
    fmt.Println("Creting test data...")
    time.Sleep(time.Second*3)
    channel<-"Evaluation is done"
}

func main() {    
    channelOne:=make(chan string,1)
    channelTwo:=make(chan string,1)
    channelEval:=make(chan string,1)
    
    go CalculateOne(channelOne)
    go CalculateTwo(channelTwo)
    go EvaluateTestData(channelEval)
    
    for i:=0;i<3;i++{
        select{
         case messageOne := <-channelOne:
            fmt.Println(messageOne)
        case messageTwo := <-channelTwo:
            fmt.Println(messageTwo)   
        case messageEval:=<-channelEval:
            fmt.Println(messageEval)
        }
    }
}

Örnekte üç farklı fonksiyon bulunmaktadır. Her birisinde uzun süren işler olduğunu göstermek için time.Sleep fonksiyonundan yararlanılmaktadır. Fonksiyonlar birer goroutine haline getirilip eş zamanlı olarak çalıştırılmaya başladıktan sonra select ifadesi ile kontrol altına alınırlar. 3 goroutine olduğundan for döngüsü de 3 iterasyon ilerleyecektir. Her bir case bloğunda ilgili kanalın dönüşünün olup olmadığına bakılır. Senkronizasyon örneğinde olduğu gibi her fonksiyonun sonunda kanala bırakılan bir sinyal vardır. Bu mesajlar case ifadelerinde ele alınırlar. Tahmin edileceği üzere görevler bittikçe ekrana kanaldan gelen mesajlar basılacaktır. Aynen aşağıdaki ekran görüntüsünde olduğu gibi.

Böylece geldik bir gopher olma çalışmamızın daha sonuna. Bu yazımızda Concurrency konusunun iki önemli kavramına değinmeye çalıştık. Go dili ile ilgili bir şeyler öğrendikçe yazmaya devam edeceğim. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.


Tek Fotoluk İpucu 158 - GoLang'de Constructor var mı?

$
0
0

Merhaba Arkadaşlar,

GO tam anlamıyla nesne yönelimli(Object Oriented) bir dil değildir. Hatta object terimi yerine Type kavramının daha çok öne çıktığı bir programlama dilidir. Geliştirici tanımlı tipler için struct'lardan yararlanılır ve onların örneklenmesinde kullanılabilecekk doğal yapıcı metodlar(built-in constructor) vardır. Yine de istersek kendi yapıcı metodlarımızı yazabiliriz. Nasıl mı? Aynen aşağıdaki fotoğrafta olduğu gibi.

Az da olsa birazcık hile var gibi değil mi? Product isimli struct tipinin built-in constructor ile nasıl üretildiğini 10ncu satırda görebiliriz. car isimli değişken dinamik olarak türlendirilmiş ve := operatörü sonrasında gelen ifade içerisinde tip niteliklerine ilk değerleri verilmiştir. Bu zaten GO'nun sunduğu varsayılan yapıcıdır. Biz hafiften abstract factory design pattern benzeri bir çözüm uyguladık. NewProduct metodu parametre olarak aldığı bilgilere göre GO'nun built-in yapıcı metod özelliğini kullanarak yeni bir ürün tipini geriye döndürmektedir. Bir nevi kendi yapıcı metodumuzu yazmış olduğumuzu ifade edebiliriz. Basit ama bir Object Oriented programcısı için tuhaf. Bir başka ipucunda görüşmek üzere hepinize mutlu günler dilerim.

Bir SOAP Web Servisini Proxy Olmadan WebClient ile Çağırmak

$
0
0

Merhaba Arkadaşlar,

Geçtiğimiz günlerde şirket dışı bir kurumun web servislerini çağırma ihtiyacımız oldu. Lakin Header bilgisinde bir OAuth Token değeri de göndermemiz gerekiyordu. Bu Header bilgisini SOAP bazlı Web servisine nasıl ekleyeceğimizi düşünürken WebClient sınıfı ile de bu işi yapabileceğimizi öğrendik. Üstelik çağrılacak servisin WSDL üretimi referansını projeye eklemeye gerek kalmadan. Bu kısa ipucunda WebClient sınıfının bu amaçla nasıl kullanıldığını incelemeye çalışacağız. Öncelikle eski nesil bir Asp.Net Web Service geliştirdiğimizi düşünelim. İçinde iki değerin toplamını hesap eden basit bir metod yer alacak. Servis kodunu aşağıdaki gibi geliştirebiliriz.

using System.Web.Services;

namespace CalculateService
{
    [WebService(Namespace = "http://azon.com/services/math")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class Common 
        : System.Web.Services.WebService
    {
        [WebMethod]
        public double Sum(double x,double y)
        {
            return x+y;
        }
    }
}

Önemli olan nokta bu servis operasyonunu çağırmak için göndereceğimiz SOAP Header ve Body içerikleri. Yazılan servisin ilgili operasyonunu herhangibir tarayıcıdan çağırırsak bu içerikleri görebiliriz.

Olur da kurumun web servisilerinin yardımcı bir ekran sayfası yoktur(JAVA servisleri gibi) ve sadece WSDL içeriğini görebiliyorsunuzdur, bu durumda SOAP UI gibi bir araçtan yararlaranak ilgili servisi çağırmak için kullanılacak SOAP Body ve Header içeriklerinin otomatik olarak üretilmesini sağlayabilir ve oradan destek alabilirsiniz. Dikkat edilmesi gereken nokta SOAPAction değeridir.

Header bilgisinde servis operasyonunun adını(SOAPAction bilgisi), HTTP'nin hangi metodu ile çağırım yapılması gerektiğini(bu örnekte POST), kullanılması gereken SOAP versiyonunu(bu örnekte 1.1) gönderilecek içeriğin tipini(text/xml; charset=utf-8) bulabiliriz. Body kısmında yer alan string içeriği aynen kullanabiliriz. Sadece x ve y için gerekli sayısal değerleri vermemiz yeterlidir. Kodu basit bir Console uygulamasında aşağıdaki gibi deneyebiliriz.

using System;
using System.Net;
using System.Text;

namespace CallWSWithWebClient
{
    class Program
    {
        static void Main(string[] args)
        {
            string content = @"<?xml version=""1.0"" encoding=""utf-8""?><soap:Envelope 
                                    xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" 
                                    xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" 
                                    xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/""><soap:Body><Sum xmlns=""http://azon.com/services/math""><x>4</x><y>5</y></Sum></soap:Body></soap:Envelope>";

            try
            {
                using (WebClient wClient = new WebClient())
                {
                    wClient.Headers.Add("Content-Type", "text/xml; charset=utf-8");
                    wClient.Headers.Add("SOAPAction", "\"http://azon.com/services/math/Sum\"");
                    var result = wClient.UploadData("http://localhost:52523/Common.asmx"
                        , "POST"
                        , Encoding.UTF8.GetBytes(content));
                    var response = System.Text.Encoding.Default.GetString(result);
                    Console.WriteLine(response);
                }
            }
            catch(WebException excp)
            {
                Console.WriteLine(excp.Message);
            }
        }
    }
}

WebClient sınıfını oluşturduktan sonra Content-Type ve SOAPAction bilgilerini Headers isimli WebHeaderCollection nesnesine eklemekteyiz. UploadData metodu ile Common.asmx servisine POST tekniğini kullanarak son parametre ile geçilen byte[] içeriğini yolluyoruz. Bu içerik aslında content ile belirtilen XML bilgisi. Bu noktada WebClient metodu hangi EndPoint'e hangi HTTP metodu ve içerik tipi ile veri göndereceğini biliyor. Eğer WebException üretilmesine neden olacak bir sorun yoksa gelen byte[] dizisi şeklindeki cevap ekrana basılıyor. Burada string olarak bir veri alımı söz konusu olsada gelen içerik aslında XML tabanlı. Yani XDocument(XmlDocument) gibi tipler yardımıyla daha kolay kullanılabilir. Nitekim daha fazla değer döndürecek sonuç kümelerinde string ile çağırmak çok da mantıklı değil(Tabii içerik JSON dönüyorsa çok daha güzel olur) Kodun çalışma zamanı çıktısı aşağıdaki gibidir.

Gördülüğü gibi başarılı bir sonuç aldık. WebClient sınıfını ağırlıklı olarak REST tabanlı servisleri çağırmak için kullansak da örnekte görüldüğü gibi SOAP tabanlı servisler için de ele alabiliriz. Tekrar hatırlatmakta fayda var ki referans eklemeden bunu yapmamız mümkün. Eğer servis JSON içerik dönüyorsa sonuçları NewtonSoft'un JsonConvert sınıfından yararlanarak JObject olarak deserialize edebilir, servisin ihtiyaçlarına göre ekstra Header bilgilerini(OAuth Token vb) kolaylıkla gönderebiliriz. Böylece geldik ihtiyaç sonrası ortaya çıkan bir yazının daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Tek Fotoluk İpucu 159 - GoLang ile Fonksiyonları Parametre Olarak Kullanmak

$
0
0

Merhaba Arkadaşlar,

GoLang fonksiyonel programlama konusunda oldukça fazla kabiliyete sahip. Birinci sınıf fonksiyonlar(first-class functions), yüksek öncelikli fonksiyonlar(higher-order functions), closures(çeviremedi :) ), birden fazla değer döndüren fonksiyonlar(multiple return values), literals ve kullanıcı tanımlı fonksiyon tipleri(user defined function types) bunlar arasında sayılabilir. Neredeyse her gün GO dili ile ilgili bir şeylere bakmaya çalışırken geçenlerde strings paketinde yer alan FiledsFunc fonksiyonunu ile karşılaştım. Derken kendimi ikinci parametresini nasıl kullanıyoruzu anlamaya çalışırken buldum. FieldsFunc fonksiyonu

func FieldsFunc(s string, f func(rune) bool) []string

şeklinde bir parametre yapısına sahipti. f isimli parametre rune tipinden(aslında int32 ama karakterlerin sayısal ifadesi için kullanılıyor) değer alan ve bool döndüren bir fonksiyondu. Ruby gibi dillerde fonksiyonlara parametre olarak kod bloklarını aktarabileceğimizi, hatta kod bloklarını değişken olarak tanımlayıp kullanabileceğimizi görmüştüm. Go dilinde de benzer fonksiyonellik sunuluyordu. Hatta FieldsFunc ile ilgili github kodlarına baktığımda nasıl uygulandığını da gördüm(Şu adresten incelemenizi öneririm) Peki bu tip bir fonksiyonelliği ben nasıl yazabilirdim? İşin püf noktası ilgili fonksiyon parametresini bir tip(type) olarak tanımlamak ve parametre olarak fonksiyonda kullanmaktı. Derken aşağıdaki fotoğrafta görülen basit örneği geliştirdim(Üşenmeyin GO'yu sisteme kurup LiteIDE'yi de indirip deneyin)

İlk olarak predicate isimli bir fonksiyon tipi tanımladığımızı görebilirsiniz. Bu tip string parametre alıp bool değer döndürecek fonksiyonları ifade edecek şekilde tanımlandı. Select isimli fonksiyon string tipinden bir slice alıyor. İkinci parametre ise predicate tipinden. Dolayısıyla bu fonksiyona parametre olarak string alan ve bool döndüren bir fonksiyonu geçebiliriz. Select içerisinde dönüş içeriğini barındıracak bir slice daha yer alıyor ama asıl önemli olan for döngüsü içerisinde yaptığımız f(word) çağrısı. Bu döngüde words içerisindeki her bir string veriyi f değişkeni ile işaret edilen fonksiyona gönderiyoruz. Sonuç true ise o anki string veriyi yeni slice'a ekleyeceğiz. Bu durumda Select fonksiyonunu kullandığımız yerlere odaklanabiliriz. İki örnek ile ilerledik. İlkinde harf sayısı 5 ve daha fazla olan renkleri buluyoruz. Bu kullanımda Select çağrısını yaparken ikinci parametre ile de fonksiyon bloğunu geçiyoruz. İkinci kullanımda ise strings paketinde yer alan HasPrefix fonksiyonunu kullanarak "g" harfi ile başlayan renkleri yakalıyoruz. Bu kullanımda farklı olan şey ilgili fonksiyonu g isimli bir değişken olarak tanımlamış olmamız.

Bir .Net geliştiricisi için Predicate, Func, Action temsilcileri(delegate) benzeri imkanlar sunan fonksiyonellikleri yazdığımızı düşünebiliriz.

Tabii kodu daha iyi hale getirmek gerekiyor. Örneğin bu kod parçasındaki Select fonksiyonu sadece string tipinden slice'lar ile çalışmakta. Halbuki bu Select fonksiyonunu daha generic hale getirebiliriz. Bu noktada Interface tiplerini ve reflection konusunu devreye almak gerekiyor. Tabii reflection ve generic bir yapının kullanımı performans üzerine negatif etkilere de sahip olabilir. Bu sevimli araştırmayı siz değerli okurlarıma bırakıyorum. Bir başka ipucunda görüşmek dileğiyle. 

GoLang - Web Programlamaya Giriş

$
0
0

Merhaba Arkadaşlar,

Bir web uygulamasının temel malzemeleri nelerdir? Sunucu tarafında çalışan güçlü bir çatı(Framework), içeriklerin gösterildiği statik veya dinamik web sayfaları, iyi tasarım, görsel zenginlik katan materyaller(resimler,css'ler vb), veri depolama enstrümanları ve diğerleri. Aslında internet programcılığının ilk yılları düşünüldüğünde basit HTML sayfalarının neredeyse her tür ihtiyacı karşılayacağı düşünülüyordu belkide. Zaman geçtikçe programlama dillerinin dinamik web sayfaları ile olan etkileşimi, istemci taraflı çalışan betiklerin sunucu taraflı kullanılabilmesi de gündeme geldi. Modern programlama dillerinin neredeyse tamamı web uygulamaları geliştirebilmek için gerekli temel donanıma sahip. Asıl amacı back-end tarafındaki büyük ölçekli sistemlerde yüksek performans sunmak olan, eş zamanlı programlamada öne çıkan GO ile de web tabanlı uygulamalar geliştirebilmemiz mümkün. Go dilinin network haberleşme üzerine sunduğu basitlik ve yüksek performans da göz önüne alındığında web programlama oldukça ilgi çekici bir konu haline geliyor.

Ruby tarafında Ruby on Rails, Python tarafında Django gibi çatılar profesyonel anlamda web uygulamalarının geliştirilmesini kolaylaştırıyor. Go tarafında da bu tip çatılar mevcut hatta Beego bunlar arasında en popülerlerinden birisi. Ne var ki basit düzeyde de dahili paketlerden yararlanarak web uygulamaları geliştirebiliriz. Çünkü teori her zaman basittir. tcp/ip üzerinden belli bir porta gelen çeşitli talepler ve bu taleplere karşılık istemcilerin yorumlayabileceği HTML içerikler. Bu düşünce yapısından yola çıkarak net/http ve html/template kütüphanelerini kullanarak iki sayfadan oluşan basit bir web sitesi yazacağız. İşe ilk olarak aşağıdaki klasör ve dosya yapısını tasarlayarak başlayabiliriz.

- server.go (main fonksiyonunu içeren go kod dosyamız)
- \Pages
- \Pages\index.html (ana sayfamız olarak düşünebiliriz)
- \Pages\players.html (Oyuncu listesini gösterecek olan sayfamız)
- \Pages\common.css (biraz görsellik katmak için kullanacağız)
- \Pages\stronger.gif (siz istediğiniz bir resmi kullanabilirsiniz)

Bu arada çeşitli kitaplardan ve kaynaklardan çalışırken bilgisayarımın başında yazdığım örnekleri github üzerinde toplamaya çalışıyorum. Ders01,02,03... mantığında ilerleyen bir içerik söz konusu. İlk dersle başlayarak Go kod pratiklerinizi geliştirebilir dili orta seviyeye kadar öğrenebilirsiniz.

Kök dizinimizde main fonksiyonunu içeren go kod dosyamız bulunuyor. Bu tahmin edileceği üzere uygulamanın giriş noktasını içeren dosya. Pages alt klasöründeyse html sayfalarımızı ve css, gif gibi kaynaklarımızı barındırıyoruz. common.css tahmin edeceğiniz üzere sayfalarımızdaki görselliği daha keyifli hale getirmek için kullanacağımız bir stil(cascading style sheets) dosyası. Ben içeriğini aşağıdaki gibi yazarak body,table,table-th(başlık),table-td(hücre) düzenini renklendirmeye çalıştım.

Amatörce Bir CSS Dosyası

body{
	border:3px solid cyan;
	border-radius: 24px;
	border-width: 20px;
	text-align: center;
	width: 400px;
	height: 400px;
	margin: auto;
	font-family:Tahoma, sans-serif;
	font-size:16px;
	color:541460;
}
table.gridtable {
	font-family: verdana,arial,sans-serif;
	font-size:16px;
	color:#581845;
	border-width: 1px;
	border-color: #666666;
	border-collapse: collapse;
}
table.gridtable th {
	border-width: 3px;
	padding: 8px;
	border-style: solid;
	border-color: #666666;
	background-color: #FF80E8;
}
table.gridtable td {
	border-width: 3px;
	padding: 8px;
	border-style: solid;
	border-color: #666666;
	background-color: #98A1FC;
}

index.html web uygulamasının giriş sayfası. Aslında kod içerisinde belirleyeceğimiz kök adrese gelen taleplerin doğrudan karşılanacağı sayfa olarak düşünülebilir. CSS uygulamak dışında bir kaç bilgi ve /players şeklinde bir adrese bağlantı içermekte.

Karşılama Sayfası index.html

<html><head><title>Blizert World Game Entertienment</title><link rel="stylesheet" href="http://www.buraksenyurt.com/common.css"></head><body><h1>Wellcome to BliZerT</h1><p>The World's best card players are here. <br/>
	Want to learn some more details? <br/> 
	What are you waiting for. Go Go Go...</p><img src="/stronger.gif"/><p><a href="http://www.buraksenyurt.com/players">Famous Hearthstone Players</a></p></body></html>

Kullanıcılar örneğin http://localhost:8085 gibi bir adrese geldiklerinde bu sayfanın sunulmasını arzu ediyoruz. Eğer /players yönlendirmesini yapan linke basılırsa da players.html isimli sayfaya yönlendirme yapacağız. Bu sayfanın içeriği ise aşağıdaki gibidir.

Oyuncuları gösteren Players.html sayfası

<html><head><title>Gold Players</title><link rel="stylesheet" href="http://www.buraksenyurt.com/common.css"></head><body><h1>Gold Players</h1><table class="gridtable" align="center"><tr><th>Id</th><th>Player No</th><th>Nickname</th><th>Level</th></tr>
		{{ range $index,$player := . }}<tr><td>{{$index}}</td><td>{{$player.Id}}</td><td>{{$player.Nickname}}</td><td>{{$player.Level}}</td></tr>
		{{end}}</table><br/><a href="http://www.buraksenyurt.com/">Go Back</a></body>

Hımmmm...Bir dakika...Burada oldukça enteresan ifadeler var. Bir back-end geliştiricisi de olsak az buçuk HTML'den çakmadığımızı kim söyleyebilir. Peki bu ikişer küme parantezleri de neyin nesi oluyor? range anahtar kelimesi bir yerlerden tanıdık geliyor aslında. Sözü fazla uzatmayalım. {{ ile }} arasında yazdığımız ifadeler Go diline ait kod satırlarını içeren kısımlar. Yani HTML sayfası içerisine GO kodlarını gömdüğümüzü ifade edebiliriz. Önemli olan kısımsa range ile başlayan satırlar. Burada nokta ile sayfaya gelen veri kaynağı üzerinde index ve nesne çiftleri olarak hareket ediyoruz({{ }} arasında GO kodundan gelen bir değişkene ulaşmak istediğimizde adının başına nokta koymamız gerekiyor) Bir döngü söz konusu ama key:value çiftlerini dönüyor. Peki bu key:value çiftlerinin kaynağı kim? Döngü tarafından işlenecek olan veriyi server.go içerisinden göndereceğiz. Göndereceğimiz nesne topluluğundaki öğelerin de Id, Nickname ve Level isimli alanları olacak. Alanları HTML içerisine yedirmek için $index ve $player gibi ifadeler kullanıldığına dikkat edelim. Örneğin döngünün o anki nesnesine gelen Player içerisindeki Nickname alanına ulaşmak için $player.Nickname ifadesini, bu ifadenin sonucunu td takıları arasına yazmak için de {{ }} bloğunu kullanıyoruz. E öyleyse gelelim server.go içeriğine.

Başrol Oyuncusu Server.go

package main

import (
	"html/template"
	"log"
	"net/http"
)

func main() {
	http.Handle("/", http.FileServer(http.Dir("pages")))
	http.HandleFunc("/players", getPlayers)
	log.Println("Web server is active at port 8045")
	http.ListenAndServe(":8045", nil)
}

func getPlayers(response http.ResponseWriter, request *http.Request) {
	log.Println("Get Request for Players")
	players := loadPlayers()
	log.Println("Players loaded")
	t, err := template.ParseFiles("pages/players.html")
	if err == nil {
		t.Execute(response, players)
	} else {
		log.Println(err.Error())
	}
}

type Player struct {
	Id       int
	Nickname string
	Level    int
}

func loadPlayers() []Player {
	return []Player{
		Player{1001, "Molfiryin", 2},
		Player{1002, "Gul'dan", 21},
		Player{1003, "Anduin", 12},
		Player{1004, "Lexar", 5},
		Player{1005, "Turol", 34},
	}
}

Dosyanın genel yapısına baktığımızda Player tipinden bir struct içerdiğini, main dışında loadPlayers ve getPlayers isimli iki fonksiyona daha sahip olduğunu görüyoruz. Bunun dışında html/template, log ve net/http paketlerinin kullanıldığını görüyoruz. localhost üzerinden 8045 nolu adrese gelecek varsayılan talepler doğrudan pages klasörüne yönlendirilmekte. Bu yönlendirmeyi main fonksiyonunun ilk satırındaki HandlFunc ile belirtiyoruz. Genelde tüm web sunucuları varsayılan sayfaları bilirler. Index.html aranan ilk sayfalardan birisidir. Haliyle index.html göreve hazır olacağından bu kök adres çağrısının sonucu aşağıdaki ekran görüntüsündeki gibi bir olacaktır.

Famous Hearthstone Players başlıklı linke tıklandığında ise main fonksiyonundaki http.HandleFunc bildirimlerinden ikincisi devreye girer. Nitekim bağlantı /players şeklinde bir çağrı yapmaktadır. Bu adrese gelecek olan talepler getPlayers isimli fonksiyona yönlendirilmektedir. Bu fonksiyon iki kritik parametre alır. İstemciye gönderilecek cevabın yazılmasında kullanılacak olan ResponseWriter ve gelen talebe ait bilgiler içeren Request(Örnekte Request nesnesi kullanılmamıştır ancak şu adresteki yazıdan kendisi hakkında ek bilgi edinebilirsiniz) getPlayers fonksiyonunda önce terminale log basılarak işlemlere başlanır. Arından Player isimli struct tipinden nesne örnekleri barındıran slice içeriğini elde edeceğimiz loadPlayers fonksiyonu tetiklenir. İşte bu slice içerisindeki Player nesne örnekleri, players.html sayfasındaki ilgili konumlara basılacaktır. Ama nasıl?

Önce template tipinin ParseFiles fonksiyonu ile pages klasöründeki players.html şablonu yakalanır. ParseFiles pek çok Go fonksiyonu gibi iki değer döndürür. İlki kullanacağımız Template, ikincisi de Error tipidir. Eğer bir hata yoksa t isimli Template tipi üzerinden Execute fonksiyonu çalıştırılır. Exceute fonksiyonu ResponseWriter örneğini ve players içeriğini alıp players.html üzerinde gerekli işlemleri gerçekleştirir. Aslında players.html sayfası belleğe alınıp, {{ }} konumları players dizisi içeriği ile beslenip gerekli HTML çıktısının üretilmesi ve bu içeriğin ilgili ResponseWriter'ın sahiplendiği stream üzerinden ağa yazdırılması söz konusudur diyebiliriz. Sonuç aşağıdaki ekran görüntüsündeki gibi olacaktır.

Ta ta ta taaa...Pek çok deneyimli ya da amatör web tasarımcısı için berbat dizaynlar olsa da benim için önemli bir adım diyebilirim. Bu basit örnekte hem statik hem de dinamik web sayfalarını nasıl kullanabileceğimizi incelemeye çalıştık. Statik HTML içeriklerinde iyi bir CSS bilgisi basit bir tasarım çok çok işe yarayabilir. Bunun dışında dinamik olarak bir şeyler sunacak web sayfalarında Template kullanarak sunucu tarafında üretilen verilerin HTML içerisine basılması da mümkündür. Yani arayüz tarafı ile Go kodları etkileşime geçebilir. Go ile Web Programlama oldukça sade ve basit gibi görünüyor. Sevgili Murat Özalp'ın Go Programlama kitabı bu konuda bana büyük vizyon katıyor diyebilirim. Öğrendikçe bilgilerimin pekişmesi için de yazmaya devam edeceğim. Siz de örneği biraz daha geliştirmeye çalışabilirsiniz. Örneğin kullanıcıdan girdi alabileceğiniz bir web sayfası söz konusu olsa girilen içerikleri sunucuya post ettiğinizde bunları Go kodunda yakalayıp nasıl işleyebilirsiniz? Araştırın. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

GoLang - Kalıtım için Gömülü Tiplerin Kullanımı

$
0
0

Merhaba Arkadaşlar,

Bir süredir GO dili ile ilgili çalışmalarıma ara vermiştim. Yakın zamanda ise sevgili Murat Özalp'in "GO Programlama" isimli kitabını takip etmeye başladım. Gerçekten her bölüm son derece doyurucu. Kitabı düzenli olarak hergün çalışıyorum. Bazen çok az zaman ayırsam da her gece bir kaç sayfasını okuyor ve uygulamaya çalışıyorum. Burada yaptığım günlük çalışmaları aksatmamaya gayret ediyor ve kendime göre hazırladığım örnekleri github üzerinde topluyorum. Hatta artık kodları yeni bir IDE üzerinde deniyorum. Yeni gözdem LiteIDE isimli kod editörü(ki Murat Hoca'nın tavsiyesidir ve çok memnun kaldığım bir geliştirme aracıdır)

Son olarak bugün gömülü tiplerin kullanımını öğrenmeye çalıştım. Bildiğiniz gibi GO dili tam anlamıyla nesne yönelimli(Object Oriented) bir dil değil. Her şeyden önce sınıf(class) gibi bir kavram olmayışı kafaları biraz da olsa karıştırıyor. Çalışmalarım sırasında struct ve interface tiplerini kullanarak GO dilinde kalıtımın nasıl ele alındığını bir ölçüde kavramıştım. Diğer yandan pek çok dilde karmaşıklığı arttırdığı için izin verilmeyen çoklu kalıtımı biraz daha güvenli bir şekilde sağlama şansımız var. Gömülü tipler(ya da struct içinde kullanılan struct tipinden değişkenler) bu noktada devreye giriyor. Aslında bir nevi Composition işlevselliğini sağlayarak bu özelliği kazanıyoruz. Nasıl mı? Kendi anladığım kadarı ile durum şöyle...

Öncelikle basit bir senaryo düşünmeye çalıştım. Futbolcu, basketbolcu, boksör ve benzeri oyuncu türlerinin ortak özelliklerini barındıracak bir yapı tasarlamaya karar verdim. Sonrasında bu oyuncuların çeşitli yeteneklerini ifade edecek bir tip daha tasarladım. Her oyuncunun takma adı, sistem için önem arz edecek bir numarası ve söyleyeceği bir şeyleri olsun istedim(bunu bir metod ile halledebilirdim. Metod derken fonksiyon değil struct ile ilişkilendirilen metod) Ayrıca oyuncuların türlerine göre farklı kabiliyetleri de olabilirdi. Tabii tüm kabiliyetleri kendisini tanımlayan bir isimden ibaret olacak şekilde basitçe ele almam benim için daha iyi olacaktı. Bu kabiliyetleri uygulayacağım bir metod da pek şık olurdu. Paragraf ile ifade etmeye çalıştığım şeyi aslında aşağıdaki grafik ile daha güzel anlatabilirim belki de.

Player ve Ability isimli yapılar FootballPlayer ve Boxer isimli diğer yapılarda gömülü tip olarak kullanılıyorlar. Buna göre her futbolcu ve boksör örneği id, nickName gibi temel bilgilere sahip olacak ve bir şeyler söyleyebilecek(saySomething metodu). Ayrıca her birisinin n sayıda kabiliyeti de bulunabilecek ve bu kabiliyetleri uygulayabilecek(useAbility metodu) Bunun için abilities niteliklerini kullanabiliriz. Gelelim bu fotoğrafın kod görüntüsüne.

/*
 Lesson 09
 Embedded type kullanımı
 gömülü türlerden yararlanarak çoklu türetme özelliğini kullanabiliriz
*/
package main

import (
	"fmt"
)

func main() {
	var zidane FootballPlayer
	zidane.self = Player{id: 10, nickName: "Zinadine Zidane"}
	zidane.position = "Midfield"
	zidane.abilities = []Ability{
		Ability{name: "shoot", power: 92},
		Ability{name: "high pass", power: 84},
	}
	zidane.abilities[1].useAbility()
	zidane.self.saySomething("What can I do sometimes. This is football.")
	zidane.abilities[0].useAbility()

	var tayson Boxer
	tayson.self = Player{id: 88, nickName: "Bulldog"}
	tayson.knockdownCount = 32
	tayson.abilities = []Ability{
		Ability{name: "defense", power: 76}, //virgül koymayınca derleme hatası verir ;)
	}
	tayson.self.saySomething("I will win this game")
	tayson.abilities[0].useAbility()
}

// oyuncuların ortak niteliklerini barındıran bir struct
type Player struct {
	id       int
	nickName string
}

// player yapısına monte edilmiş saySomething metodu
// oyuncunun bir şeyler söylemesi için kullanılabilecek bir metod
func (player *Player) saySomething(message string) {
	fmt.Printf("%s says that '%s'\n", player.nickName, message)
}

// oyuncuların farklı yeteneklerini tanımlayacak olan Ability isimli yapı
type Ability struct {
	name  string
	power int
}

// Ability yapısına monte edilmiş olan useAbility isimli bir metod
// oyuncunun bir yeteneğini kullandırmak için
func (ability *Ability) useAbility() {
	fmt.Printf("[%s] yeteneği kullanılıyor. Güç %d\n", ability.name, ability.power)
}

// Player ve Ability yapılarını gömülü tip olarak kullanan ve
// futbolcuları tanımlayan yapı
type FootballPlayer struct {
	position  string
	self      Player
	abilities []Ability
}

// farklı bir oyuncu tipi
type Boxer struct {
	knockdownCount int
	self           Player
	abilities      []Ability
}

main fonksiyonunda zidane(makale fotoğrafının sebebini de özetlemiş olduk) ve tayson isimli iki değişken kullanılmakta. zidane isimli değişken FootballPlayer yapısı tipinden. tayson ise Boxer tipinden. Kodun akışında her birisinin adını, numarasını belirliyor, bir şeyler söylemelerini sağlıyor ve farklı kabiliyetler ekleyerek bunları uygulayışlarını izliyoruz. Dikkat edilmesi gereken ve kendime söylediğim bir kaç nokta da var. Oyuncuların kabiliyetlerini tutan abilities isimli nitelikleri Ability türünden bir Slice olarak tanımladık. Bir oyuncunun belli bir yeteneğini uygulamak için ilgili Slice öğesine gitmeli ve sonrasında useAbility metodunu çağırmalıyız. Metodlar hatırlanacağı üzere yapılara fonksiyonellik kazandırmak üzere kullanılıyorlar. useAbility ve saySomething isimli metodlar sırasıyla Ability ve Player yapıları ile ilişkilendirilmiş durumdalar. Yazımları sırasında metod adından önceki parantezlerde hangi struct için kullanılacakları belirtilmekte. * işaretine yani pointer kullanıldığına dikkat de edilmeli.

Kodu çalıştırdığımızda aşağıdaki ekran görüntüsündekine benzer bir sonuçla karşılaşmamız gerekir.

Gömülü tipleri kullandığımız bu örnek kod parçasında bir yapının başka yapıları kullanarak çoklu kalıtımı nasıl ele alabileceğini incelemeye çalıştık. Anahtar nokta struct tiplerini içerme şeklinde değerlendirmekten ibaret. Felsefe olarak, genişletmek istediğimiz türün içereceği nitelikleri yapılar içerisinde toplamak gerekiyor. Eğer gömülü tip üzerinden uygulanması beklenen ortak fonksiyonellikler de söz konusu ise bunların metod şeklinde tanımlanması türeyen tip için yeterli. Böylece geldik kısa bir GO turumuzun daha sonuna. Bir başka makalemizde görüşünceye dek hepinize mutlu günler dilerim.

GoLang - Bir Go Paketini Ruby'den Çağırmak

$
0
0

Merhaba Arkadaşlar,

Pek çok kaynak GO dilinin ileride C'nin yerini alabileceği yönünde görüşler belirtmekte. Özellikle IoT alanında bu dilin daha fazla ön plana çıkacağı vurgulanıyor. Bu düşüncenin haklı olabileceği yönünde bazı tespitlerim var.

Söz gelimi GO ile yazılmış paketleri başka dillere ait ortamlarda kullanabilmemiz mümkün. Bir başka deyişle bir GO paketini C, Python, Java ve Ruby gibi dillerde kullanabiliriz. Sonuçta yazılan GO kodları C-Style API şeklinde ifade edebileceğimiz derlenmiş kütüphanelere dönüştürülebilmekte.

Yapılması gereken tek şey, yazılan GO kodlarının C Shared Library türünde derlenmesi. Bu modda yapılan derleme sonucu GO fonksiyonlarını dışarıya açabileceğimiz Shared Object Binary File içerikleri üretiliyor. İşte bu yazımızda basit bir GO kütüphanesini bahsettiğimiz formatta derleyip örnek olarak Ruby ile yazılmış bir kod üzerinden kullanmaya çalışacağız. Dilerseniz vakit kaybetmeden işe koyulalım.

Sistem

Örneği Ubuntu platformu üzerinde denedim. Aslında ilk olarak Windows 7 yüklü makinemde denedim lakin Shared Library oluşturulmasında 64bitlik ortamım sorun çıkarttı. Hemen emektar Ubuntu'ya döndüm ve örneği orada yazmaya karar verdim. Ubuntu tarafında Ruby(2.1.5 i386) ve Go(1.8.3 i386) versiyonları yüklü. Yazıyı yazdığım tarihler itibariyle en son sürümler bunlardı. Ruby tarafı için ihtiyaç duyacağımız FFI isimli bir gem paketi var. Bu paket ile GO tarafında üretilecek olan derlenmiş kod dosyalarını ruby ortamına yükleyip, arayüzden sunulan fonksiyonları kullanabileceğiz. Sonuçta kodlar GO ile yazılmış C kütüphaneleri de olsa bir şekilde diğer çalışma zamanı ortamlarına yüklenerek değerlendirilebilirler. FFI isimli paketi yüklemek için terminalden gem install komutunu kullanabiliriz(Kullanıcım root haklarına sahip olmadığından sudo komutu ile yükleme işlemini gerçekleştirdim)

sudo gem install ffi

GO Paketinin Yazılması

İşe aşağıdaki GO paketini yazarak başlayabiliriz.

package main

import "C"

import (
	"math"
)

//export CircleSpace
func CircleSpace(r float64) float64 {
	return math.Pi * math.Pow(r, 2)
}

func main() {}

SomeMath.go ismiyle kaydedeceğimiz kod dosyasında daire alanı hesaplaması yapan tek bir fonksiyon bulunuyor. Siz örneğinizi geliştirirken farklı tipler ile çalışan fonksiyonları da işin içerisine katabilirsiniz. İçerikte dikkat edilmesi gereken bir kaç nokta var. Her şeyden önce C isimli bir GO paketini kullanmaktayız. Bunun haricinde yine bir main fonksiyonu(entry point) bildirimi söz konusu ancak içerisinde iş yapan hiçbir kod parçası bulunmuyor. //export şeklinde belirtilen yorum satırı ise önemli. Nitekim sadece export ile işaretlenmiş fonksiyonlar dış dünyaya açılabiliyorlar. Diğer yandan paylaşımlı olarak kullanılacak kütüphanenin mutlaka main paketi şeklinde düzenlenmesi gerekiyor.

Derleme İşlemi

GO uzantılı kaynak kod dosyası hazır. Bunu direkt go derleyicisi ile derlersek bildiğiniz üzere çalıştırılabilir bir exe üretilir. Oysaki ihtiyacımız olan diğer dillerin ele alabileceği paylaşılabilir bir nesne olmalı(Shared Object) Bu nedenle yazılan paketin komut satırından aşağıdaki gibi derlenmesi gerekiyor.

go build -o SomeMath.so -buildmode=c-shared SomeMath.go

Derleme işlemi sonrasında SomeMath.h isimli C Header ve SomeMath.so isimli bir Shared Object dosyası oluşur. Shared Object içeriği itibariyle daha büyüktür nitekim GO runtime ve gerekli paketler barındırmaktadır. Header dosyasına baktığımızda fonksiyon ve tip bazından bir eşleştirme bilgisi bulundurduğunu görebiliriz. Yaptığımız derleme işlemi sonrası oluşan header içeriği aşağıdaki gibidir.

/* Created by "go tool cgo" - DO NOT EDIT. */

/* package command-line-arguments */
/* Start of preamble from import "C" comments.  */
/* End of preamble from import "C" comments.  */
/* Start of boilerplate cgo prologue.  */
#line 1 "cgo-gcc-export-header-prolog"

#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H

typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt32 GoInt;
typedef GoUint32 GoUint;
typedef __SIZE_TYPE__ GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;

/*
  static assertion to make sure the file is being used on architecture
  at least with matching size of GoInt.
*/
typedef char _check_for_32_bit_pointer_matching_GoInt[sizeof(void*)==32/8 ? 1:-1];

typedef struct { const char *p; GoInt n; } GoString;
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;

#endif

/* End of boilerplate cgo prologue.  */

#ifdef __cplusplus
extern "C" {
#endif


extern GoFloat64 CircleSpace(GoFloat64 p0);

#ifdef __cplusplus
}
#endif

Bu dosya en baştaki yorum satırında da belirtildiği üzere değiştirilmememlidir. Kodun son kısmında CircleSpace fonksiyonuna ait bir bildirim olduğu da gözden kaçmamalıdır. typedef tanımlamalarına bakıldığında ilgili GoFloat64 tipi için double eşleştirilmesi yapıldığını da görebiliriz.

Ruby Tarafından Çağırım

Yazılan GO paketini Ruby tarafından çağırmak için aşağıdaki basit kod parçasını kullanabiliriz.

require 'ffi'

module ShapeMath
	extend FFI::Library
	ffi_lib './SomeMath.so'
	attach_function :CircleSpace, [:double], :double
end

puts ShapeMath.CircleSpace(10)

magic.rb isimli dosya ffi gem paketini kullanıyor. ShapeMath isimli bir module tanımı içermekte. Bu modül, ffi_lib metoduna gönderilen SomeMath.so dosyasını yükleyip içerisindeki CircleSpace isimli fonksiyonu çalışma zamanına ekleme işlemini gerçekleştirmekte. Son kod satırında ise CircleSpace isimli metodun çıktısının ekrana basıldığı bir komut yer alıyor. attach_function bildiriminde Ruby dünyası için geçerli olan veri tipleri söz konusu. Go tarafında float64 olarak ifade ettiğimiz tipleri burada double olarak ele almaktayız. İşte çalışma zamanı sonuçları.

Elbette işler her zaman bu kadar basit olmayabilir. GO'da var olan bir takım türlerin çağırılmak istenen programlama dilinde karşılığı olmayabilir. Örneğin bir Slice ya da map kulladığımız fonksiyonlar ya da bizim tarafımızdan tanımlanmış yapılar(struct) nasıl eşleştirilmelidir. Ruby'de, Pyhton'da, C# tarafında bu tipler nasıl ele alınmalıdır. Doğru dönüşümleri yapabilmek bu açıdan önemli. Bu "Hello World" tadındaki örnek sadece bu işin yapılabildiğini gösterir niteliktedir. Daha fazlası için Google :) Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Powershell'i Kurcalayan Bir Yazılımcı

$
0
0

Merhaba Arkadaşlar,

Powershell betikleri uzun zamandır yaygın ve etkin bir şekilde Windows ailesinde yer almakta. Özellikle sunucu tarafındaki kullanımının öne çıktığını görüyoruz. Temel olarak işletim sistemi üzerinden betik dillerin avantajlarını da kullanarak pek çok işlemin yapılabilmesine olanak sağlayan bir kabuk olarak düşünülebilir. Powershell .Net Framework kütüphanelerini de doğrudan kullanabildiğinden önemli avantajlar sağlamaktadır. Klasör listeleme veya dosya kopyalama gibi çok basit işlemler dışında, n sayıda sunucuya dağıtım paketi çıkacak programcıkların geliştirilmesi benzeri operasyonları da içeren geniş bir yetkinliğe sahiptir. DevOps kültüründe Windows ailesi için değerli ve öne çıkan bir programlama ortamıdır diyebiliriz.

Sistem yöneticilerinin biraz programlama bilgisi ile veya programcıların da biraz sistem bilgisi ile bu dili temel seviyede kullanmaları mümkündür. Elimizdeki Windows 7'den son sürüm Windows Server ailesine kadar pek çok sistemde Powershell betiklerini kullanabiliriz. Geçtiğimiz günlerde bir boşluk yakaladım ve en çok tavsiye edilen Powershell betikleri nelerdir araştırayım dedim. Uzman olmadığım bir alan olsa da tanıma mahiyetinde neler yapılabileceğini görmek istiyordum. Daha önceden Powershell ile üzerinde Gacutil olmayan bir makinede .Net Framework 1.1 ile yazılmış bir kütüphanenin register edilmesi ve eğlence amaçlı olarak Star Wars marşının çaldırılması gibi işlemler yapmıştım ama çok çok daha fazlası olduğunu biliyordum.

Makinemdeki Servisler

İlk olarak sistemde yüklü servislerin bir listesini nasıl alırım sorusunun cevabını aradım. Böylece işletim sisteminin yönetsel alanlarından birisine ait bilgilere nasıl ulaşabileceğimi görecektim. Komut satırından Powershell'i açtım ve ilk betiği aşağıdaki gibi yazdım.

Get-Service

Komutun çıktısı aşağıdaki ekran görüntüsündeki gibi olmuştu.

Makinemde yüklü olan tüm servislerin bilgisini görebiliyordum. Adları(Name), kısa açıklamaları(Display Name) ve o anki durumları(Status). Liste gözüme kalabalık görününce acaba bir sorgu atabilir miyim diye kurcalamaya başladım. Belki de adı C harfi ile başlayan servislerin listesini de çekebilirdim. Araştırınca get-service fonksiyonunu aşağıdaki gibi kullanabileceğimi öğrendim.

Get-Service -name C*

Artık elimde adının ilk harfi C olan servislerin bir listesi vardı. Asteriks kullanımı söz konusu olduğuna göre farklı benzerlikleri de sorgulayabilirdik. Örneğin içinde .NET geçen servisleri bulmak mümkündü. Ne var ki, durum bilgisine göre servisleri bir türlü sorgulayamıyordum. Aynen name parametresi gibi status parametresi üzerinden de sorgu atabileceğimi düşünmüştüm.

get-service -status "running"

Ancak çalışma sonrası aşağıdaki hata mesajını aldım. status parametresi desteklenmiyordu.

Yardım Lazımdı

Peki get-service fonksiyonunun nasıl kullanıldığının bir dokümanı yok muydu? Öğrendim ki get-help komutu ile bir başka powershell komutunun nasıl çalıştığını öğrenebilirim.

get-help get-service

Bu komutla get-service kullanımına ait yardım içeriğine ulaştım. Komutun ne işe yaradığı, yazım biçiminin nasıl olması gerektiği, parametreleri, genel açıklması ve hatta örnekler sunan yardımcı internet adresli bile vardı.

PS C:\Users\buraksenyurt> get-help get-service

NAME
    Get-Service

SYNOPSIS
    Gets the services on a local or remote computer.


SYNTAX
    Get-Service [[-Name] <string[]>] [-ComputerName <string[]>] [-DependentServices] [-Exclude <string[]>] [-Include <s
    tring[]>] [-RequiredServices] [<CommonParameters>]

    Get-Service -DisplayName <string[]> [-ComputerName <string[]>] [-DependentServices] [-Exclude <string[]>] [-Include<string[]>] [-RequiredServices] [<CommonParameters>]

    Get-Service [-InputObject <ServiceController[]>] [-ComputerName <string[]>] [-DependentServices] [-Exclude <string[
    ]>] [-Include <string[]>] [-RequiredServices] [<CommonParameters>]


DESCRIPTION
    The Get-Service cmdlet gets objects that represent the services on a local computer or on a remote computer, includ
    ing running and stopped services.

    You can direct Get-Service to get only particular services by specifying the service name or display name of the se
    rvices, or you can pipe service objects to Get-Service.


RELATED LINKS
    Online version: http://go.microsoft.com/fwlink/?LinkID=113332
    Start-Service
    Stop-Service
    Restart-Service
    Resume-Service
    Suspend-Service
    Set-Service
    New-Service

REMARKS
    To see the examples, type: "get-help Get-Service -examples".
    For more information, type: "get-help Get-Service -detailed".
    For technical information, type: "get-help Get-Service -full".

PS C:\Users\buraksenyurt>

Where ile Sorguyu Genişlettim

Gerçekten de get-service komutunun -status gibi bir parametresi mevcut değildi ancak -InputObject parametre kullanımı dikkat çekiyordu. Biraz araştırmadan sonra aşağıdaki gibi bir komut ile DisplayName bilgisinde "Service" kelimesi geçenler hizmetlerden durmuş olanları alabileceğimi gördüm.

Get-Service * | Where-Object {$_.DisplayName -like "*Service*" -and $_.Status -eq "Stopped"}

Bu komutu anlamak oldukça kolaydı. * ile Get-Service'in döndüreceği tüm servis listesi üzerinde işlem yapacağımızı belirmiştik. | arkasından bir where koşulu geliyordu. Süslü parantezler belli ki bu komutun alacağı kod bloğunu taşıyacaktı. $_. ile başlayan değişkenler ile DisplayName ve az önce alamadığımız Status alanlarına ulaşabiliyorduk. -like benzer , -and mantıksal ve, -eq ise eşitlik anlamında kullanılan komut parametreleriydi(Bu durumda büyüktür veya küçüktürü hatta veyayı nasıl ifade edebileceğimizi de çözmüş oluyoruz)

Döngü Kullanımını Merak Edince

Internet kaynaklarını tararken dilin pek çok diğer dilde olan temel özelliklere sahip olduğunu görmek kaçınılmazdı. Değişken tanımlamaları, koşullu ifadeler, döngüler vb Hazır elim değmişken bir de döngü yazayım istedim. Örneğin şu an makinede çalışmakta olan Process'lere bakıp Id ve Name bilgilerini ekrana bastırmaya çalıştım.

Get-Process| ForEach-Object{[string]::Format("{0} - {1}",$_.id,$_.name)}

Burada dikkat çekici noktalardan birisi de .Net kütüphanesinden bir fonksiyona erişilmesiydi. String sınıfının Format metodunu kullandığımız gözünüzden kaçmamış olmalı. [Sınıf Adı]::[Metod Adı] notasyonu ile static tanımlanmış üyelere erişmek mümkün. ForEach-Object komutu ise Get-Process'in ürettiği her bir process nesnesini dolaşmakta. Döngü içerisinde o anki Process'in id ve name bilgilerine nasıl eriştiğimize dikkat edelim.

Çıktıları Dosyaya Basalım

Yaptığımız denemelerin sonuçları hep komut satırına basıldı. Anlık olarak iyi olsa da bazen bu çıktıları bir dosyaya basmak ve bir yerlere göndermek de isteyebiliriz(Hatta inanıyorum ki bir powershell işlem çıktısının ürettiği dosyayı otomatik mail ile bir yerlere yollayabilir veya ortak bir ağ klasörüne kopyalatabiliriz. Bir deneyin) Bunun için out-file parametresini eklemek yeterli. 

Get-Service * | Where-Object {$_.DisplayName -like "*Service*" -and $_.Status -eq "Stopped"} | out-file "ServiceReport.txt"

Bunları Fonksiyon Haline Getiremez miydim?

Servisler için uyguladığım betiğin çıktısını metin tabanlı bir dosyaya basmış oldum. Pratik görünüyor değil mi? Aslında tam olarak değil. Betikleri fonksiyonelleştirmek yeniden kullanılabilirlik anlamında çok daha mantıklı olabilir. Örneğin servis raporunu çekerken like, status ve dosya kriterlerini parametrik olarak bir fonksiyona almak çok daha işe yarardı. Sonunda aşağıdaki gibi bir şeyler yazılabileceğini gördüm.

function Service-Report ($name="A*",$status="Running",$fileName="Service_Report.txt")
{
	Get-Service * | Where-Object {$_.Name -like $name -and $_.Status -eq $status} | out-file $fileName
}

Service-Report bir fonksiyon olarak 3 parametre ile çalışmakta. $ işareti ile tanımlanan parametreler metod bloğunda aynı şekilde bir değişken olarak ele alınıyorlar. Dikkat çekici nokta parametrelere varsayılan değer verilebilmesi. Yani name, status ve fileName bilgileri boş geçilirse varsayılan değerleri kullanılacak. Komut satırından bu fonksiyonu çalıştırmak da oldukça kolay.

Service-Report "C*" "Stopped" "Reports.txt"

Reports.txt dosyasının içinde artık adı C harfi ile başlayan durdurulmuş servislerin bir çıktısı yer alıyor.

ISE Diye Sevimli Bir IDE

Bu işlerle uğraşırken komut satırının hem sevimli hem de sevimsiz olduğunu fark ettim. Terminalden çalışmak her zaman için daha keyifliydi ama iş bir betik hazırlamaya geldiğinde basit bir editör hayat kurtarabilirdi. En azından Notepad++ düşünülebilirdi. Çok daha iyisini buldum. Powershell'in ISE(Integrated Scripting Environment) adında bir de kod editörü varmış. Bunun üzerine ISE editörünü açıp içerisine iki fonksiyonellik katıp tools.ps1 adıyla bulunduğum klasöre kayıt ettim.

function Service-Report ($name="A*",$status="Running",$fileName="Service_Report.txt")
{
	Get-Service * | Where-Object {$_.Name -like $name -and $_.Status -eq $status} | out-file $fileName
}

function Process-Report($name,$cpuRate,$fileName="Process.txt")
{
    Get-Process | where-object {$_.name -eq $name -and $_.CPU -gt $cpuRate} |Format-List * -Force | out-file $fileName
}

Artık fonksiyonellikleri bir betik dosyası içerisine konumlandırıp komut satırından bu şekilde çalıştırabilirdim. ISE yetenekli bir editör. Betiği yazabildiğimiz arayüzü dışında yine kendi üstünde yer alan komut satırı ile çalışmaların anında denenmesi mümkün. Üstelik çağırılan komutların geriye doğru log'unun tutulduğu bir penceresi de mevcut.

Bu arada bir ps1 dosyasının yüklenmesi sırasında "...cannot be loaded because the execution of scripts is disabled on this system" şeklinde bir hata alınıyorsa ExecutionPolicy değerinin RemoteSigned olarak değişitirilmesi çözüm olabilir. 

ISE editöründe tools.sp1 dosyası içerisindeki fonksiyonların kullanılabilmesi için öncelikde dosyanın sistem çalışma zamanına yüklenmesi gerekiyor. Bir program çalıştırıyor gibi F5 ile veya Debug menüsünden Run/Continue komutları sayesinde bu işlem gerçekleştirilebilir. Dosya başarılı bir şekilde yüklendikten sonra içerisindeki fonksiyonlar kullanılabilir hale gelir. Söz gelimi tools.ps1'i yükledikten sonra sistemde o an açık olan chrome sekmelerinden CPU tüketimi belli bir değerin üstünde olanları bulmak için aşağıdaki ekran görüntüsüne olduğu gibi ilerlememiz yeterlidir. 

Kimbilir Powershell betikleri ile daha neler neler yapılabilir? Mesela çok fazla bellek tüketen process'lerin belirli kriterlere uyanlarını öldürebilecek, durmuş bir servisi tekrardan ayağa kaldırabilecek betikler yazabilir, ağ üzerinden ulaşılabilecek uzak sunucular hakkında anlık durum bilgilerine sahibi olabiliriz. Sistem üzerinden gerçekleştirilecek tüm bu operasyonlarda sadece bu alana özel yazılmış bir betik dilin programlama özelliklerine sahip olmak da işin cabası.

Bu yazıdaki anafikre göre kısa zamanda öğrenilebilecek bir betik dil ile işletim sistemi üzerinde hakimiyet sağlayıp pek çok işi otomatize edecek geliştirmeler yapabileceğimizi düşünebiliriz. İşte böyle sevgili okuyucu. Bir boşluğu iyi değerlendirmek adına Powershell ile yaptığım bir kaç saatli maceralarımı kısaca anlatmaya çalıştım. İşi daha da ileri götürmek mümkün. Ara ara burada betikler yazılabilir. Amazon'da konu ile ilgili pek çok kitap var ama şuradaki online tutorial seti de epey işe yarar görünüyor. Bir başka yazımızda görüşmek dileğiyle hepinize mutlu günler dilerim.


GoLang Built-In JSON Desteği

$
0
0

Merhaba Arkadaşlar,

Mesajlaşma formatlarından çeşitli NoSQL sistemlerine, REST tabanlı veri servislerinden mobil cihazlardaki depolama kabiliyetlerine kadar pek çok alanda JSON(JavaScriptObjectNotation) standardının kullanıldığını görüyoruz. Özellikle XML(eXtensible Markup Language) kadar fazla yer tutmuyor oluşu da onu ön plana çıkartan özelliklerinden birisi. Hatta sıkıştırılmış formatının kullanıldığı ağ protokolleri bile olabiliyor. Verinin rahatça okunabildiği bu standart ile barışık olmayan programlama dili neredeyse yok gibi(Internet Engineering Task Force kurumunun JSON veri değiş-tokuş standartları ile ilgili yazısına buradan, JSON API standartları ile ilgili DevNot üzerinden yayınlanan yazıya da şuradan bakabilirsiniz)Özellikle son on yıl zarfında geliştirilen veya ön plana çıkan ne kadar dil varsa JSON için çekirdekten destek sunuyor. Sunmayanlar da bu sürede ek paket veya eklentilerle bu veri modelini kullandırıyor. GoLang için de benzer durum söz konusu. Nasıl mı? Aynen aşağıdaki kod parçasında olduğu gibi.

package main

import (
	"fmt"
	"encoding/json"
	"os"
)

func main() {
	// json serileştirme için kullanacağımız Game yapısından bir tip tanımlıyoruz
	goldZone:=Game{
		5555,
		"Mohani Gezegeni Görevi",
		[]Player{
			Player{100,"deli","cevat",10.90},
			Player{102,"nadya","komenaççi",12.45},
			Player{103,"biricit","bardot",900.45},
		},
	}	
	jsonOutput,_:=json.Marshal(goldZone)
	fmt.Println(string(jsonOutput))
	var game Game	
	if err := json.Unmarshal(jsonOutput,&game); err != nil {
        	panic(err)
    	}
	fmt.Printf("Game : %s\n",game.Name)
	for _,player:=range game.Players{
		fmt.Println(player.Id,player.FirstName,player.Point)
	}
	// dilersek json sınıfının NewEncoder metodunu kullanarak
	// çıktıları farklı yerlere yönlendirebiliriz
	// işletim sistemi ekranı veya bir HTTP mesajının gövdesi gibi
	encoder := json.NewEncoder(os.Stdout)    	
    	encoder.Encode(game)    	
}

type Player struct{
	Id int `json:"PlayerId"` // İstersek bir alanın JSON çıktısında nasıl adlandırılacağını söyleyebiliriz
	FirstName string // Büyük harf public'lik anlamındadır!
	lastName string //küçük harfle başlayanlar private'lık kazanır. O yüzden json çıktısına yansımaz
	Point float32
}

type Game struct{
	Id int
	Name string
	Players []Player
}

Kodun çalışma zamanı çıktısı aşağıdaki gibi olacaktır.

Primitive tiplerin JSON formatında serileştirilmesi oldukça kolay zaten. Bu nedenle örnek uygulamamızda Game ve Player isimli iki struct tipi kullanıyoruz. Game tipi içinde Player tipinden bir slice yer alıyor. JSON serileştirme ve ters-serileştirme işlemlerini Marshal ve Unmarshal metodları ile gerçekleştirebiliriz. json tipi encoding/json paketi içerisinde yer almaktadır. Marshal çıktısını aldıktan sonra ekrana basarken string tipine dönüştürme işlemi uyguladık(Bunu yapmadığımız takdirde nasıl bir sonuç elde edeceğinizi inceleyin lütfen) Unmarshal metodu sırasında oluşabilecek bir paniği kontrol altına almaya da çalıştık. Marshal ve Unmarshal çağrıları dışında NewEncoder metodunu kullanarak çıktıların bir Stream'e doğru yönlendirilmesi de sağlanabilir. Bu stream işletim sistemine ait terminali gösterebileceği gibi bir HTTP mesajının body kısmı da olabilir. Son kod satırında bu işlem ele alınmış ve çıktılar doğrudan terminale verilmiştir. İlgili özelliği kullanabilmek için os paketi koda dahil edilmiştir.

Örnek kodu çalışırken ilk başta JSON çıktısına hiçbir alan adının yazılmadığını fark ettim. Sonrasında bunları küçük harfle yazdığımı gördüm. Küçük harf ile başlamak GO dilinde private'lık anlamına geliyor. Büyük harf ile başlandığınında ise ilgili üyeye public'lik kazandırmış oluyoruz. Bunu yeni öğrendim. Utanıyorum.

Uygulamanın ekrana bastığı JSON içeriğini Chorme gibi bir tarayıcıda göstermek isterseniz aşağıdaki sonuçları görmemiz gerekiyor(Chorme'da JSONView eklentisini kullandığımı belirteyim)

Pek tabii size düşen bir kaç görev var. Örneğin bu çıktıyı fiziki bir dosyaya kaydetmeyi deneyebilirsiniz. Sonrasında ise kaydedilen dosya içeriğinden ilgili içerikleri canlandırmaya çalışabilirsiniz. Bu işi biraz daha ileri götürüp JSON formatında veriler ile çalışan basit bir NoSQL veritabanı sistemi yazabilir hatta fonksiyonelliklerini(ekleme,çıkartma,arama vb) REST API olarak dış dünyaya sunabilirsiniz. Bence bunu bir deneyin. Böylece geldik kısa bir kod parçasının daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

GoLang - Ticker ile Periyodik İş Çalıştırma

$
0
0

Merhaba Arkadaşlar,

GO dilinin en güçlü yanlarından birisi eş zamanlı programlama(Concurrent Programming) kabiliyetleri sayesinde sunduğu performans ve kullanım kolaylıkları. Daha önceden şu yazıda Concurrency konusunu GoRoutine ve Channel kavramları üzerinden incelemiştim. Çalıştığım kaynaklarda ilerledikçe eş zamanlı programlama konusunda yeni şeyler de öğrendim. Bunlardan birisi de time tipi. Bu tipin NewTimer ve NewTicker isimli iki önemli fonksiyonu bulunuyor. Doğruyu söylemek gerekirse NewTimer ile yapacağımız işlemleri time.Sleep kullanımı ile de sağlamamız mümkün. Bu nedenle NewTimer'ı daha çok bir GoRoutine'in beklenen sürede işini yapmasını beklediğimiz, aksi hallerde ise zaman aşımı halini ele alacağımız durumlarda kullanmanın çok daha mantıklı olduğunu öğrendim. Diğer yandan NewTicker fonksiyonu daha çok dikkatimi çekti. Bu fonksiyon ile belirli periyotlar boyunca tekrar etmesini istediğimiz eş zamanlı görevler planlayabiliriz. Konuyu anlamaya çalışırken önce teorik bir örnek ile ilerlemeye çalıştım. Ardından daha pratik bir örnek geliştirdim. İlk GO kodlarımızı aşağıdaki gibi geliştirdiğimizi düşünelim.

package main

import (
	"fmt"
	"time"
)

func main() {
	timer := time.NewTimer(time.Second * 4)

	fmt.Println("timer nesnesi tanımlandı. Kod akışına devam ediyor.")
	fmt.Println(time.Now())
	now := <-timer.C //C ile timer'ın NewTimer'a parametre olarak gelen süre sonrasındaki zaman elde edilir
	fmt.Println("Timer ile belirtilen süre doldu.")
	fmt.Println(now)

	// Bu seferki Timer, süresi dolduğu için Expire durumuna düşecek
	// Bir Timer'ı expire olmadan önce durdurmak istediğimiz senaryolarda ele alabiliriz
	timer = time.NewTimer(time.Second)
	go func() {
		<-timer.C
		fmt.Println("İkinci timer süresi geçti") // time.Second nedeniyle func içerisinde timer.C yakalanamadan Stop metoduna düşülür
	}()
	stop := timer.Stop()
	if stop {
		fmt.Println("Timer durduruldu")
	}

	// ticker ile zamanlanmış görevler hazırlayabiliriz.
	tickTime := time.NewTicker(time.Second * 2) // iki saniyede bir zaman döndürecek Ticker tanımlandı
	go func() {
		fmt.Println("İş yapıyorum...")
		for t := range tickTime.C { // C ile yukarıdaki tickTime'ın o anki süresi ele yakalandı
			fmt.Println(t)
		}
	}()
	//time.Sleep(time.Second * 12) // main thread 12 saniye duracak. Bu süre boyunca 2 saniyede bir for t:=range bloğu çalışacaktır
	//tickTime.Stop()              //Ticker durduruldu

	// Yukarıdaki kullanımdan farklı olarak şimdi kullanıcı Enter tuşuna basana kadar for t:=range bloğu çalışacaktır
	var enter string
	fmt.Println("Çıkmak için Enter tuşuna basınız")
	fmt.Scanln(&enter)
	tickTime.Stop()
}

Öncelikle kodun çalışma zamanı çıktısına bir bakalım.

Örnek kod parçasında iki NewTimer ve bir NewTicker örneği yer alıyor. main, 4 saniye sonrası için kurulan bir timer tanımlaması ile başlıyor. Bu tanımlama sonrası kod akışına devam edecek ve now değişkenine C ile bir bilgi geçilecektir. Aslında burada bir kanal(Channel)üzerinden o anki zamanın döndürülmesi işlemi gerçekleşmektedir. Tahmin edileceği üzere kod bu satıra gelinceye kadar bekler. Yani 4 saniyelik bir bekleme oluşur(Az öncede belirttiğim gibi bu noktada time.Sleep metodundan da yararlanılabildiği belirtiliyor) timer'ı tekrar oluşturduğumuz kod satırında ise bir saniye sonrası için planlama yapılmıştır. Hemen ardından bir GoRoutine başlatıldığı görülür. Bu fonksiyon içerisinde <-timer.C satırı ile kanaldan gelecek bilgi beklenmektedir. Ancak GoRoutine hemen çalıştığından kod bir sonraki ifadeye geçecek ve timer için Stop metodu devreye girecektir. İşte bu noktada GoRoutine için bir zaman aşımı senaryosu işletilmiş olur. Lakin Stop çağrısına gelinmeden önce NewTimer ile açılan süreden daha uzun süren işlemler söz konusu olursa, GoRoutine işleyişini de tamamlayabilecektir.

Kodun son parçasında bir ticker üretilmektedir. NewTicker metodu ile oluşturulan tickTime, 2 saniyelik periyotları ifade eder. Hemen ardından gelen GoRoutine içerisinde ise C üzerinden yakalanan kanal içeriğinin bir takibi yapılır. Takip için for döngüsü ve range ifadesinden yararlanılır. Her iki saniyede bir kanala o anki zaman bilgisi düşeceğinden bu bir range üzerinden for döngüsü ile yakalanabilir. Kullanıcı ekrandan enter tuşuna basana kadar 2 saniyelik zaman dilimlerinde for bloğunun içerisindeki kod parçası devreye girecektir. Tabii söz konusu işlemlerin bir süre sonra tamamlanmasını da sağlayabiliriz. Bunun için yorum satırı yapılmış olan kısma dikkat edelim. Yorum satırları açıldığı takdirde 12 saniye sonrasında periyodik olarak devam eden işlemler duraksatılacaktır.

Sonra durdum ve daha akılda kalıcı bir periyodik kod parçası yazabilir miyim diye düşünmeye başladım. Aklıma belirli bir klasörün içerisindeki dosya değişikliklerini belirli zaman aralıklarında izleyecek bir örnek geldi(Bir nevi .Net dünyasından aşina olduğumuz FileSystemWatcher'ın ilkel bir halini geliştirmek istedim diyebiliriz) Amacım belirli zaman aralıklarında C:\Reports isimli klasördeki dosya içeriklerini terminale bastırmaktı. Bunun için komut satırından yürüyecek bir go programı yazabilir, içerisinde eş zamanlı olarak belirli periyotlarda tetiklenecek bir GoRoutine oluşturabilirdim. Öğrenmem gereken bir başka şey de bir klasör içerisindeki dosyaları nasıl ele alabileceğimdi. Biraz araştırma sonrası aşağıdaki kod parçası ortaya çıktı.

package main

import (
	"fmt"
	"os"
	"path/filepath"
	"time"
)

func main() {
	var pathName string = "C:\\Reports"
	ticker := time.NewTicker(time.Second * 10)
	go func() {
		for t := range ticker.C {
			fmt.Printf("Time : %s\n", t)
			getFileList(pathName)
		}
	}()

	var enter string
	fmt.Println("Press Enter for Exit")
	fmt.Scanln(&enter)
	ticker.Stop()
}

func getFileList(pathName string) {
	fmt.Println("___", pathName, "___")
	filepath.Walk(pathName,
		func(path string, fileInfo os.FileInfo, err error) error {
			if !fileInfo.IsDir() {
				fmt.Printf("\t%s\t%d bytes\n", fileInfo.Name(), fileInfo.Size())
			}
			return nil
		})
	fmt.Println("____________________________________")
}

Kod 10 saniyede bir çalışacak bir Ticker tanımlaması ile başlıyor. Sonrasında gelen GoRoutine içerisinde ise for döngümüzü kurguluyoruz. C kanalı üzerinden gelen zaman bilgisini ekrana basıyor ve getFileList isimli fonksiyonu çağırıyoruz. Bu döngü 10 saniyede bir çalışacak ve her seferinde getFileList fonksiyonu çağırılacak. Fonksiyon parametre olarak gelen klasördeki dosyaları alabilmek için filePath tipinin Walk metodunu kullanıyor. İlk parametre klasör adı ama ikinci parametre biraz daha değişik. Burada WalkFunc fonksiyon tipinden bir fonksiyon bildirimi yer alıyor. Aslında klasör içerisinde yürürken her bir klasör veya dosya için ikinci parametre ile gelen fonksiyon bloğu çağırılmakta. Bu fonksiyon üç parametre almakta. Üzerinde çalışılan klasör, o anki öğe(dosya veya klasör gibi) ve varsa hata mesajı. Geriye ise bir error nesnesi döndürmekte(return nil satırının konulma sebebi de aslında bir hata olmayacağını düşünmem) Walk tüm klasör ve dosyaları dolaştığından ve senaryo gereği bana sadece dosyalar gerektiğinden IsDir çağrısı ile o anki öğenin ne olduğuna bakıyorum. Eğer klasör değilse dosya olduğuna karar verip ekrana o dosya ile ilgili bilgiler bastırıyorum(Dosya adı ve byte olarak boyutu şimdilik yeterli) Program kullanıcı enter tuşuna basana kadar çalışıyor. Denemeler sırasında C:\Reports altına bir kaç dosya attım, silme işlemi gerçekleştirdim. Sonuçlar aşağıdaki ekran görüntüsündekine benzer oldu.

Bu örnekle NewTicker ve time paketinin kullanımı kafamda biraz daha anlamlı bir yer edindi diyebilirim. Bazı konuları öğrenmeye çalışırken gerçek hayat örneklerini kullanmak çok ama çok faydalı. Senaryo daha da zenginleştirilebilir. Söz gelimi bir klasördeki dosya değişimleri gözlemlenebilir ve loglama amaçlı bir işlevsellik sağlanabilir. Aynı stratejiyi kullanarak belirli zaman aralıklarında gerçekleşmesini istediğimiz işlemler için planlamaları basitçe yapabiliriz. Böylece geldik bir GoLang maceramızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

GoLang - Yönlendiriciler(Routers)

$
0
0

Merhaba Arkadaşlar,

Yönlendirme(Routers) mekanizmaları özellikle anlamlı HTTP taleplerinde önemli bir rol oynar. Bir tarayıcının adres satırından gelen ifadelerin sunucu tarafında ele alınması sırasında işleri kolaylaştırıcı kütüphaneler de bulunmaktadır. Sevgili Murat Özalp'ın kitabında ilerlerken GO'nun built-in yönlendirme mekanizmaları dışında github üzerinden sunulan pek çok basit ve kullanışlı çatının olduğunu öğrendim. Tabii burada bahsedilen kütüphaneler ağırlıklı olarak web taleplerinin bir eşleştirme koduna göre uygun fonksiyonlara yönlendirilmesi ve cevaplandırılması ile alakalıydı. Bazıları performans açısından öne çıkarken bazıları komple bir web çatısını sunma kabiliyetine sahipti. Güncel listeye şu adresten bakabilirsiniz. Yeni paketler geldikçe veya var olanlarda değişiklikler oldukça benchmark sonuçları da etkilenecektir. Bu nedenle ara ara uğramakta yarar olduğu kanısındayım.

Bende kitabın sıkı bir takipçisi olarak örnek üzerinden ilerlemeye başladım ve bahsedilen httpRouter kütüphanesini kullanmayı denedim. Pek tabii aynı örneği değil de konuyu kendim için eğlenceli hale getirecek bir versiyonunu yapmaya çalıştım. Aklıma en sevdiğim film serilerinden olan Star Wars'taki gezegenler geldi. Bir kaç gezegeni ve önemli şehirlerini şimdilik bellekte tutacağım bir yapı(struct) ilişkisi oluşturup aşağıdaki HTTP taleplerini karşılayacak bir web sunucusu yazmak eğlenceli olabilirdi(Yandaki fotoğrafta görülen galaksinin detaylarına ve çok daha kapsamlı bir sunumuna şu adresten ulaşabilirsiniz. Adamlar üşenmemişler koskoca bir evreni hayal edip kurgulamışlar. Bu kurgulamayı Star Trek serisinde de görmekteyiz.)

/ ile bir karşılama sayfasına yönlendirilip
/planets ile gezegenler listesini gösterip
/planets/:name ile de bir gezegendeki şehirleri verebilirdim.

3ncü tanımlamada dikkat edileceği üzere bir parametre de söz konusu. Böylece HTTP Get talebinde gelen bir parametreyi alıp nasıl kullanabileceğimi de görmüş olacaktım. 

Kodlar

Kod içeriğini genel hatları ile şöyle özetleyebiliriz. Planet ve City isimli iki yapı bulunuyor. Bu yapılara ait test içeriklerinin yüklendiği bir fonksiyonumuz da var. github adresinden referans edilen httpRouter paketinin nimetlerinden yararlanaraktan localhost:4568 adresinden bir sunucu ayağa kaldırıyoruz. Sunucu yukarıdaki 3 temel talebi alıp işleyecek şekilde çalışıyor. Nihayi sonuçta kullanıcılara gezegenleri ve bu gezegenlerdeki önemli şehirleri göstermeyi planlıyoruz. Tüm kod içeriğini aşağıda görebilirsiniz.

package main

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/julienschmidt/httprouter"
)

func main() {
	router := httprouter.New()              
	router.GET("/", Index)                  
	router.GET("/planets", GetPlanets)      
	router.GET("/planets/:name", GetCities) 
	http.ListenAndServe(":4568", router)    
}

func GetCities(response http.ResponseWriter, request *http.Request, params httprouter.Params) {
	planetName := params.ByName("name") 
	planets := LoadSomeData()
	fmt.Fprintf(response, `<html><head><title>%s</title></head><body><h1>%s</h1>`, planetName, planetName)
	for _, planet := range planets {
		if strings.ToLower(planet.Name) == strings.ToLower(planetName) {
			for _, city := range planet.Cities {
				fmt.Fprintf(response, `<p><b>%s</b>-%s</p>`, city.Name, city.Affiliation)
			}
			break
		}
	}
	fmt.Fprintf(response, `</body></html>`)
}

func GetPlanets(response http.ResponseWriter, request *http.Request, params httprouter.Params) {
	planets := LoadSomeData() 
	fmt.Fprintf(response, `<html><head><title>Planets</title></head><body><h1>Planets</h1>`)
	for _, planet := range planets {
		fmt.Fprintf(response, `<p><a href='planets/%s'>%s-%s (%d)</p>`, planet.Name, planet.Name, planet.Sector, planet.Population)
	}
	fmt.Fprintf(response, `</body></html>`)
}

func Index(response http.ResponseWriter, request *http.Request, params httprouter.Params) {
	fmt.Fprintf(response, `<html><body><head><title>Star Wars Planets</title></head><body><h1>Star Wars Planets</h1><a href="http://www.buraksenyurt.com/planets">Planets</a><br/><p>Planet list updates every morning with new planets</p></body></html>`)
}

func LoadSomeData() []Planet {
	var planets []Planet

	planets = append(planets, Planet{Name: "Naboo", Sector: "Chommel", Population: 4500000,
		Cities: []City{
			City{Id: 1, Name: "Theed", Affiliation: "Galactic Empire"},
			City{Id: 2, Name: "Umberbool City", Affiliation: "Gungan Grand Army"},
			City{Id: 3, Name: "Spinnaker", Affiliation: "Galactic Empire"},
			City{Id: 4, Name: "Otoh Gunga", Affiliation: "Trade Federation"},
		}})

	planets = append(planets, Planet{Name: "Coruscant", Sector: "Corusca", Population: 1000000000,
		Cities: []City{
			City{Id: 1, Name: "Galactic City", Affiliation: "Rebellian"},
		}})

	planets = append(planets, Planet{Name: "Mustafar", Sector: "Atravis", Population: 20000,
		Cities: []City{
			City{Id: 1, Name: "Fralideja", Affiliation: "Rise of Empire"},
		}})

	return planets
}

type Planet struct {
	Name       string
	Sector     string
	Population int64
	Cities     []City
}

type City struct {
	Id          int
	Name        string
	Affiliation string
}

Kodda Neler Oluyor?

Öncelikle bu go dosyasının github üzerinden bir paketi import ettiğini belirtelim. LiteIDE kullananlar bu anlamda şanslılar. Nitekim paketin adını doğru şekilde yazdıysak, Build-Get menü seçeneğini kullanarak referans edilen paketlerin sisteme otomatik olarak indirilmesini ve kurulmasını sağlayabiliyoruz(Hatta LiteIDE'nin otomatik formatlama özelliğinin github paketinin önüne boş bir satır koyduğunu fark ettim. Ben üste çıkartsam da o boşluk konuldu. Fark ettim ki bu şekilde bakınca built-in paketler ile harici paketleri gözle ayırt etmek çok kolaylaşıyor. İnce ve güzel düşünülmüş) main fonksiyonunda ilk olarak bir Router nesnesi örnekleniyor. Bu nesnenin GET, POST, PUT, DELETE, PATCH gibi çeşitli HTTP taleplerine cevap verebilecek fonksiyonları bulunuyor. Örnekte sadece GET talepleri ele alınıyor ki bu sayede basit bir REST servisinin yolu da açılmış oluyor. Lakin biz örneğimizde istemcilere HTML çıktısı vereceğiz.

Yukarıda bahsettiğimiz tüm adresleri GET fonksiyonunun ilk parametresi olarak kullanıyoruz. Eğer dinamik bir url parametresi söz konusu ise :ParametreAdı notasyonunu kullanıyoruz. Bu notasyon tahmin edileceği üzere yönlendirmenin yapıldığı fonksiyonda ele alınacak. İkinci parametreler ile Index, GetPlanets ve GetCities fonksiyonlarına yönlendirmeler yapmaktayız. Pek tabii son olarak yerel makinedeki bir portu dinlemek üzere sunucuyu ayağa kaldırıyoruz. Burada standart http.ListenAndServe fonksiyonunu kullanmaktayız. Dikkat edilmesi gereken nokta ikinci parametreye router değişkeninin yazılmış olması. Dolayısıyla ayağa kalkan sunucuya gelen taleplerin Router nesne örneği tarafından ele alınacağını bildirmiş oluyoruz(Basit bir injection yapıldı sanki?)

Index, GetPlanets ve GetCities fonksiyonlarının parametre yapıları aynı. 3ncü parametrelerde url üzerinden gelecek :ParametreAdi formasyonundaki değişkenleri yakalayabiliyoruz ki GetCities fonksiyonunda bir gezegen adını alıp o gezegendeki şehirleri listelemek için kullanmaktayız. Index fonksiyonu çok çok basit bir HTML içeriğini fmt paketinin Fprintf yordamı ile ResponseWriter üzerinden istemciye basmakta. Burada esprilektüel bir şey yok diyebiliriz. GetPlanets fonksiyonunda ise, LoadSomeData ile yüklediğimiz slice içeriğini for döngüsü yardımıyla hazırlayarak istemciye göndermekteyiz. for döngüsünün önünde ve sonrasında tipik olarak HTML paketini hazırlıyoruz(ki çok daha şekilli hale getirilebilir. CSS giydirmeyi deneyin) Döngü içerisinde ise her bir gezegenin adını, hangi sektörde bulunduğunu ve toplam nüfusunu yazdırıyoruz. Bunu yaparken de bir hyperlink haline getiriyoruz. Link elementinin bağlantısı ise dikkate değer. planets/[gezegenAdı] şeklinde bir yazım söz konusu. Bu yazım ile GetCities fonksiyonu tarafından ele alınacak url bilgisini oluşturmaktayız. GetCities fonksiyonunda, gezegen adını url'den belirtildiği şekilde almak için params.GetName fonskiyonuna başvurmaktayız. Yine for döngüsü ile slice içeriğini dolaşıp aranan gezegene geldikten sonra ilgili gezegene ait şehirler dizisinde dolaşıyor ve belirli HTML elementlerini üretip ekrana bastırıyoruz.

Çalışma Zamanı

Geldik işin eğlenceli kısmına. Önce go uygulamasını build edip çalıştıralım. Sonrasında ilk talebi 4568 nolu porta göndererek ilerleyelim. Aşağıdakine benzer bir sonuçla karşılaşmalıyız.

Görüldüğü gibi Index sayfasının içeriğini başarılı bir şekilde yolladık. Planets bağlantısına basarsak bu kez GetPlanets fonksiyonunun devreye girip aşağıdaki çıktıyı ürettiğini görürüz. Örnek gezegenlerimizin tamamı geldi.

Bu gezegen linklerine bastığımızda da önemli şehirlerinin listesini getirecek GetCities fonksiyonunun çıktıları ile karşılaşırız. Örnek iki tanesini aşağıda görebilirsiniz.

Naboo

ve Mustafar

Elbette olmayan bir gezegen adını bilinçli olarak girersek aşağıdaki çıktıyı

ya da /Planets yerine hatalı bir giriş yaparsak da "404 page not found" cevabını alırız.

Neler Eksik?

Aslında kurguladığımız düzenek modern(querystring kullanmayan, okunaklı url içerikleri olarak düşünebiliriz) HTTP Get taleplerini alıp HTML içerikleri üreten basit bir web sunucusu. Çok doğal olarak eş zamanlı gelecek milyonlarca talep söz konusu olduğunda bunu farklı bir şekilde ele almak gerekir. Kodda kullandığımız Planet-City ilişkili yapılar bellekte tutulan nesnel koleksiyonlar. Bu içeriklerin bir veritabanı sisteminden(SQLite olur, NoSQL tabanlı bir küme olur, Google'ın Cloud çözümlerinden Firebase olur vs) alınması daha çok tercih edilir bir durumdur. SQLite ile ilgili çalışmaları da öğrenmeye başladım. Umarım burada paylaşabilirim. Kullanılan HTML içeriklerinin dinamik üretimleri için şablonlardan(Templates) faydalanıp görselliğin CSS'ler ile daha da keyifli hale getirilmesi sağlanabilir. Gezegen ve şehir fotoğraflarının koyulması bile acayip fark yaratabilir. Bu yönlendirme tekniklerinden yola çıkarak tamamen veri-odaklı bir REST servisinin geliştirilmesi de mümkündür. Veriler pekala JSON formatında kolayca basılabilir. Bu güzel konuları siz değerli okurlarıma armağan ediyorum. Bir başka makalemizde görüşmek üzere hepinize mutlu günler dilerim.

Python - PEP8 Uyumlu Kod Geliştirmek

$
0
0

Merhaba Arkadaşlar,

Geçtiğimiz günlerde senelik kişisel gelişim döngümün ikinci yarısının ilk konusu olan Python'a tekrardan başladım. Bir önceki yılın aynı dönemlerinde Raspberry Pi ile ilgili olarak Python üzerine bir şeyler yapmaya çalışmıştım. Ruby ve Go ile devam eden iterasyonun sıradaki adımında Python'u bir başucu kitabını kullanarak tekrar etmekteyim. Bu amaçla Head First Python, A Brain Friendly Guilde isimli kitaptan yararlanıyorum. Her örneği tek tek yapmaya çalışıyorum. Bugün ilgimi çeken bir konu ile karşılaştım. Yazdığımız python kodlarının PEP8(Python Enhancement Proposals) adı verilen standartlara uygun olup olmadığının tespiti. PEP konusu ile ilgili detaylı bilgilere şu adresten ulaşabilirsiniz. Hatta bu kısa yazıya konu olan PEP8 içeriğine de bu adresten bakabilirsiniz.

Tahminlerime göre bloğumu okuyan hemen herkes yazılım geliştiriyor ve bu işte kodlama standartlarının ne kadar önemli olduğunu da biliyor. Bu standartlarda değişken isimlendirmelerinden yorum satırlarının nasıl olması gerektiğine kadar pek çok yazım stili önerisi de bulunuyor. Bu öneriler kodun daha okunabilir ve diğer meslektaşlarımız ile aynı stilde içerik üretilmesi açısından mühim. Python dili içinde kodun standard kütüphanenin yazım stiline uygun olup olmadığının kontrolünü yapabileceğimiz bir kılavuz mevcut. Gerçi içerik olarak bakıldığında daha çok yazım stilinin ön plana çıktığı ve bu noktada bir tutarlılığın sağlanmaya çalışıldığı anlaşılabiliyor. Gelin Python ile yazdığımız kodların(örneğin basit bir modül içeriğinin) PEP8 standartlarına uygunuluğunu kontrol edelim.

Gerekli Araçlar

Öncelikle bu kontrol işlerini üstlenecek yardımcı araçları çalışmakta olduğumuz sistemimize kurmamız gerekiyor. pytest isimli araçla pep8 standartlarını içeren paketleri sistemimize almalıyız. Örnekleri Windows tarafında geliştirdiğimden pip yükleyicisini py komutu ile birlikte kullanmalıyım. -3 ile Python'un 3 ve sonrası sürümü için bir yükleme işlemi yapılmasını belirtiyoruz. Sırasıyla önce pytest ardından da pytest-pep8 araçlarını kuruyoruz.

py -3 -m pip install pytest

py -3 -m pip install pytest-pep8

Windows tarafındaki yükleme sonrası py.test aracının bulunamadığına dair bir hata mesajı ile karşılaşabilirsiniz. Bu, Python'un script klasörüne ait Path bildiriminin olmayışından kaynaklanmaktadır. Script klasörünü Environment Variables-Path tanımına eklediğiniz takdirde sorun çözümlenecektir. 

Bir Test Kodu

Pek tabii test için örnek bir kod parçasına ihtiyacımız var. İçeriğinin çok büyük bir önemi yok. Bizim için kobay olacak diyebiliriz.

import math,os

# Bu fonksiyon iki sayısal değerin toplamını hesaplamak için kullanılır. İki int gibi.
def sum(x,y):
    return x+y

# Variadic function sample
def sum(*numbers):
    if not len(numbers)>0:
        return 0
    total=0
    for n in numbers:
        total+=n
    return total

# Calculate a Circle space
def circleSpace(r):
    return math.pi*r*r


class player:
   def __init__(self, name, level):
      self.name = name
      self.level = level
   
   def display(self):
      print("Name : ", self.name,  ", Level: ", self.level)

# test scripts
print(sum(2,3))
print(sum(1,3,5,7,9,11))
print(circleSpace(10))
throll=player("Throll",22)
throll.display()

Aslında üç basit fonksiyonumuz ve bir de sınıfımız var. sum isimli fonksiyonun aşırı yüklenmiş iki versiyonu bulunuyor. Birisi iki değerin toplamını almakta iken diğeri Variadic özelliğe sahip. Bir başka deyişle n sayının toplamını hesaplatabiliriz. Son fonksiyonumuz ise math modülündeki pi sabitini kullanarak daire alanının hesaplanmasında kullanılıyor. Her ne kadar kullanmıyor olsak da iki modül bildirimi var. math ve os(Eğer GoLang tarafında olsaydık derleme hatası alırdık biliyorsunuz değil mi? "Kullanmadığın paketi niye koyuyorsun" oraya derdi) player isimli sınıfımız bir oyuncuyu temsilen yazılmış durumda. Kodların IDLE üzerinden F5 ile test edersek çalıştığını görebiliriz.

Peki ya PEP8 Kontrolü

Kodlar güzel bir şekilde çalışıyor peki yazım tarzı Guido van Rossum, Barry Warsaw, Nick Coghlan abilerimizin istediği gibi mi? Nitekim PEP8 dokümantasyonunda onların imzası var. Haydi bir bakalım. Komut satırından py.test aracını kullanarak testi çalıştırdığımızda beklenmedik sonuçlarla karşılaşabiliriz.

py.test --pep8 algebra.py

Liste aşağıya doğru uzayıp gitmekte. Temel olarak yazım stili ile ilgili kızılan şeyler var. Örneğin math ve os paket bildirimlerinin aynı satırda olmasına kızılıyor. Fonksiyon bildirimlerinden önce iki boş satır bekleniyor. Operatorlerden önce ve sonra birer boşluk isteniyor. Bir satırdaki karakter sayısının çok fazla olduğu ifade ediliyor(72 harfi geçen bir yorum satırımız var) Sınıfın içeriğini diğer bir dosyadan kopyalarken girintilerde kaymalar olduğu için 4 boşluklu tab kuralının bozulduğu dile getiriliyor(Aslında PEP8 dokümanına bakıldığında sınıf adları veya if kullanımları ile ilgili öneriler de var. Lakin test aracından bunları çıkarttıramadım)Şimdi kodun yazım stilini belirtilen uyarı mesajlarına göre yeniden düzenleyelim.

import math
import os


# Bu fonksiyon iki sayısal değerin toplamını hesaplamak için kullanılır.
# İki int gibi.
def sum(x, y):
    return x+y


# Variadic function sample
def sum(*numbers):
    if not len(numbers) > 0:
        return 0
    total = 0
    for n in numbers:
        total += n
    return total


# Calculate a Circle space
def circleSpace(r):
    return math.pi*r*r


class player:
    def __init__(self, name, level):
        self.name = name
        self.level = level

    def display(self):
        print("Name : ", self.name,  ", Level: ", self.level)

# test scripts
print(sum(2, 3))
print(sum(1, 3, 5, 7, 9, 11))
print(circleSpace(10))
throll = player("Throll", 22)
throll.display()

import bildirimlerini iki ayrı satıra koydum. 72 karakteri geçen yorum satırını da iki satıra böldüm. Fonksiyoların öncesinde ikişer satır boşluk bıraktım. Eşitlik, büyüktür gibi operatörlerin önüne ve arkasına birer boşluk dahil ettim. Fonksiyon parametrelerinde ve çağırımlarındaki virgüllerden sonrasına da birer boşluk koydum. Sınıfın içerisindeki bozulan girinti yapısını tekrar elden geçirdim. Sonuç şöyle oldu.

Görüldüğü gibi test başarılı. Elbette yapılan test sadece kodun yazım stilini denetleyen ve standart python kütüphanesindeki ile tutarlı hale gelinmesini sağlayan türde. Sonuçta PEP8 ile tutarlılık sağlandı diyebiliriz. Lakin test aracı bir metodun parametre sayısının belli bir değerin üstünde olması haline sınıf kullanın gibisinden bir öneri sunmadı. İnsan Juval Lowy'nin eşsiz C# kodlama standartlarındaki gibi bir şeyler bekliyor ancak konu daha çok göze hoş gelen yazım stili gibi duruyor. Dokümantasyona bakıldığında yine de güzel öneriler var. Şahsen GoLang tarafındaki bir takım stil kurallarının build mekanizmasına dahil edilmesinin çok daha iyi olduğu kanaatindeyim. Nitekim bazı stillere uyulmadığı takdirde(örneğin fonksiyon süslü açma parantezinin alt satırda olması hali) derleme hatası fırlatabilen, pek çoğumuza katı görünen ama standartlığı en baştan sağlayan bir derleme mekanizması mevcut. Bu öz eleştirimi de yaptıktan sonra huzurlarınızdan ayrılıyorum. Python kitabıma devam etmem lazım. Gitmem gereken yaklaşık 700 sayfa daha var. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

GoLange - Static Web İçeriği ve JSON Üretimi

$
0
0

Merhaba Arkadaşlar,

Bir önceki yazımızda web programlamada önemli bir yere sahip olan yönlendiricileri(Router) tanımak adına github üzerinden sunulan httpRouter paketini kullanarak dinamik HTML içeriği ürettiğimiz bir örnek geliştirmiştik. Bu hafta içinde HttpRouter paketi hakkında öğrendiklerimi çeşitli örnekler ile pekiştirmeye çalıştım. Bu sefer hem static web içeriğinin hem de talebe göre JSON formatlı veri sunumu yapacak dinamiklerin yer alacağı konu üzerinde durdum. Web programlama konusunda acemi olduğum için örneği sonuca ulaştırırken takıldığım bir kaç nokta da oldu. Yazımızda bu konulara da değinmeye çalışacağım.

Bu tip örneklerde işe yarar bir veri kümesi ile çalışmak her zaman için tercih ettiğim yöntemlerden birisi. Hatırlıyorum da Microsoft'un bu alanda oldukça başarılı veri kümeleri bulunuyordu. AdventureWorks ve Northwind veritabanları bize yıllarca yardımcı oldu. Eminim ki çoğumuzun kobay taboları vardır. Product ve Category gibi. Ben bu örneğimizde kendi hafif veri setimi bellekte konuşlandırdım. Pratik geldi :) Başrol oyuncusu olarak Star Wars dünyasından bir kaç modeli kullanmaya çalıştım. Kategori bazlı olacak şekilde bu evrendeki araçları basit nitelikleri ile ele aldım. Bu kez şu adresten yararlandım. 

Entity İçeriğini Paket Olarak Tutmak

Önceki örnekten farklı olarak bu kez kullanacağımız entity tiplerini ayrı bir paket içerisinde toplamaya karar verdim. Sistemimde yüklü olan GOPATH bilgisine göre c:\go works\samples\src klasörü altında aşağıdaki hiyerarşiyi kurguladım.

- entity
- entity\starwars
- entity\starwars\starwars.go

Bundan sonra sistemde kullanacağım başka entity tipleri olursa burada alt klasörler içerisinde toplamayı düşünüyorum. Bir paketi oluşturduğumuzda bunun GOPATH'in tanımladığı lokasyonlarda inşa edilmesi oldukça önemli. Aksi takdirde ilgili paket sistemde bulunamaz ve dolayısıyla kullanılamaz. starwars.go içeriğini aşağıdaki gibi oluşturabiliriz. Model ve Category isimli iki yapı barındırıyor.

package starwars

type Model struct {
	Id       int
	Title    string
	Price    float32
	Category Category
}

type Category struct {
	Id   int
	Name string
}

Paketi LiteIDE ile oluşturup build edebiliriz. Sonrasında pkg klasöründe build edilmiş içeriğin ikili(binary) formattaki çıktısını görebiliriz.

Artık makinedeki diğer go örneklerimizde entity/starwars şeklinde paket tanımlayıp kullanabiliriz.

Örneğin Klasör Yapısı

Uygulamamızda statik web içeriğini ve REST tadındaki GET taleplerini karşılayacak yönlendirmeleri bir arada sunacağız. Burada dikkat edilmesi gereken nokta statik içeriğin bir alt alanda oluşturulması. Aksi halde joker karakter kullanımı(*filepath şeklinde olan) tüm yönlendirme taleplerini karşılayacağından build işleminde hata alırız. Bu yüzden örneğin klasör yapısını aşağıdaki gibi oluşturabiliriz.

-\
-\main.go
-\static
-\static\index.html
-\static\cover.jpg
-\static\common.css

Static klasörü tahmin edeceğiniz üzere alt alanımız olarak görev yapacak.

Ön Hazırlıklar

main.go içeriğine geçmeden önce static klasöründeki index.html ve common.css içeriklerini tasarlayalım. Açılış sayfası gibi düşünebileceğimiz index.html içeriği şu şekildedir.

<html><head><title>Starwars Models</title><link rel="stylesheet" href="common.css"></head><body><h1>My Starwars Collection</h1><img src="cover.gif"/><p>This is a JSON based data service<br>Try this url : <a href="http://www.buraksenyurt.com/category/fighter">category/fighter</a></p><a href="http://www.buraksenyurt.com/category">All Categories</a></body>

ve common.css

body{
	border:3px solid #BA4A00;
	border-radius: 16px;
	border-width: 10px;
	text-align: center;
	width: 420px;
	height: 500px;
	margin: auto;
	font-family:Tahoma, sans-serif;
	font-size:18px;
	color:#212F3D;
}

Sonuçta aşağıdaki ekran görüntüsündeki gibi bir içerik oluşturmaya çalışıyoruz. Benim için örneği biraz daha keyifli hale getirdiğini söyleyebilirim.

Dikkat edilmesi gereken nokta index.html'e ulaşırken http://localhost:4569/static adresi üzerinden gidiyor olmamız. main paketinde buna göre bir kodlama yapacağız. 

main.go

Nihayet yazımızın en önemli kısmına geldik. İşte main paketimize ait kodlarımız.

package main

import (
	"encoding/json"
	"entity/starwars"
	"fmt"
	"net/http"
	"strings"

	"github.com/julienschmidt/httprouter"
)

func main() {
	router := httprouter.New()

	router.ServeFiles("/static/*filepath", http.Dir("static"))
	router.GET("/category", GetCategories)
	router.GET("/category/:name", GetModelsByCategoryName)

	http.ListenAndServe(":4569", router)
}

func GetCategories(response http.ResponseWriter, request *http.Request, params httprouter.Params) {
	c, _ := loadDataSet()
	cJson, _ := json.Marshal(c)
	response.Header().Set("Content-Type", "application/json")
	response.WriteHeader(200)
	fmt.Fprintf(response, "%s", cJson)
}

func GetModelsByCategoryName(response http.ResponseWriter, request *http.Request, params httprouter.Params) {
	_, models := loadDataSet()
	var result []starwars.Model
	for _, m := range models {
		if strings.ToLower(m.Category.Name) == strings.ToLower(params.ByName("name")) {
			result = append(result, m)
		}
	}
	cJson, _ := json.Marshal(result)
	response.Header().Set("Content-Type", "application/json")
	response.WriteHeader(200)
	fmt.Fprintf(response, "%s", cJson)
}

func loadDataSet() (categories []starwars.Category, models []starwars.Model) {
	fighter := starwars.Category{Id: 1, Name: "Fighter"}
	cruiser := starwars.Category{Id: 2, Name: "Cruiser"}

	vwing := starwars.Model{Id: 1, Title: "V-Wing Fighter", Price: 45.50, Category: fighter}
	n1 := starwars.Model{Id: 2, Title: "Naboo N-1 Starfighter", Price: 250.45, Category: fighter}
	republicCruiser := starwars.Model{Id: 3, Title: "Republic Cruiser", Price: 450.00, Category: cruiser}
	attackCruiser := starwars.Model{Id: 4, Title: "Republic Attack Cruiser", Price: 950.00, Category: cruiser}
	eta2 := starwars.Model{Id: 5, Title: "ETA-2 Jedi Starfighter", Price: 650.50, Category: fighter}
	delta7 := starwars.Model{Id: 6, Title: "Delta-7 Jedi Starfighter", Price: 250.35, Category: fighter}
	bwing := starwars.Model{Id: 7, Title: "B-Wing", Price: 195.50, Category: fighter}
	ywing := starwars.Model{Id: 8, Title: "Y-Wing", Price: 45.50, Category: fighter}
	monCalamari := starwars.Model{Id: 9, Title: "Mon Calamari Star Crusier", Price: 1500.00, Category: cruiser}

	categories = append(categories, fighter, cruiser)
	models = append(models, vwing, n1, republicCruiser, attackCruiser, eta2, delta7, bwing, ywing, monCalamari)

	return categories, models
}

JSON çıktısı üreteceğimiz için encoding/json, web sunucusu dinlemesi yapacağımız için net/http, yönlendirme işlemleri için github.com/julienschmidt/httprouter, küçük harfe çevirerek kıyaslama yapmak için strings, star wars tiplerini kullanmak için entity/starwars ve son olarak Fprintf fonksiyonlliği ile HTML çıktısını oluşturmak için fmt paketlerini kullandığımızı söyleyelim.

En sonda yer alan loadDataSet fonksiyonu Category ve Model tipinden slice örnekleri oluşturup döndürüyor. Onunla ilgili olarak söyleyebileceğimiz en güzel şey n sayıda parametre döndüren fonksiyonlara bir örnek olması. append çağrıları ile n sayıda tipi ilave ettiğimiz de dikkatten kaçmamalı. main fonksiyonu içerisinde Router nesnesini örnekleyerek işe başıyoruz. Bu sefer bir önceki yazımızda ele aldığımız GET çağrıları dışında ServeFiles isimli bir kullanım da söz konusu. Bu fonksiyon ilk parametre olarak statik sayfalarımızı tuttuğumuz adreslemeyi alıyor. *filepath kullanımına dikkat etmek lazım. Case-Sensitive bir ifade olduğunu belirtelim. * işareti nedeniyle ikinci parametre ile bildirilen klasördeki her dosyanın ele alınacağını bildiriyoruz. Bu tanımlama ile static adresine gelecek taleplerin hangi fiziki adresten karşılanacağını belirtmiş olduk. ServeFiles bildiriminde daha geniş imkanlara da sahibiz. Nitekim statik dosyaların ele alınması sırasında araya girip HTTP paketine müdahale edebilir Header kısımlarını kurcalayabiliriz (Şu adresteki tartışmayı incelemenizi öneririm. Statik sayfalarda Cache-Control header bilgisinin nasıl ilave edilebileceği incelenmiş)

Eğer /static/*filepath bildirimi yerine /*filepath şeklinde bir tanımlama yaparsak kodun derlenmesi sırasında aşağıdaki hataları alırız.

panic: '/category' in new path '/category' conflicts with existing wildcard '/*filepath' in existing prefix '/*filepath'

goroutine 1 [running]:
panic(0x5fd040, 0x125624d0)
	C:/Go/src/runtime/panic.go:500 +0x331
github.com/julienschmidt/httprouter.(*node).addRoute(0x1254a630, 0x6379cc, 0x9, 0x65ead8)
	c:/go works/samples/src/github.com/julienschmidt/httprouter/tree.go:162 +0x6db
github.com/julienschmidt/httprouter.(*Router).Handle(0x125d0340, 0x636829, 0x3, 0x6379cc, 0x9, 0x65ead8)
	c:/go works/samples/src/github.com/julienschmidt/httprouter/router.go:236 +0x1b2
github.com/julienschmidt/httprouter.(*Router).GET(0x125d0340, 0x6379cc, 0x9, 0x65ead8)
	c:/go works/samples/src/github.com/julienschmidt/httprouter/router.go:180 +0x4a
main.main()
	C:/Go Works/Samples/book/Web Programming/Lesson_26/Server.go:25 +0xe2
Error: process exited with code 2.

 

GET ile yapılan fonksiyon yönlendirmelerinde JSON üretimi için gerekli adımlar atılıyor. JSON çıktısı için Marshal fonksiyonunu kullanmamız yeterli. Bunların dışında çıktıyı üretirken Header'a içerik tipinin JSON formatında olduğunu belirtiyoruz ki istemciler gelen içeriğin ne olduğunu anlayabilsinler. WriteHeader fonksiyonuna verilen 200 değeri tahmin edeceğiniz üzere HTTP 200 kodunu belirtmekte. JSON çıktısını cevap olarak yazmak için Fprintf fonksiyonunu ele alıyoruz. GetModelsByCategoryName fonksiyonunda parametreye gelen kategori adını kullanarak bir sonuç kümesi oluşturmaktayız. Kategori adını params.ByName çağrısını kullanarak yakalıyoruz. Buna göre belli bir kategorideki modelleri elde ederek JSON çıktısı üretiyoruz.

Sonuçlar

Eğer doğrudan / lokasyonuna gidersek pek tabii HTTP 404 not found hatası alırız. Nitekim bu adres için bir yönlendirme yapmadık. Statik içeriklerimiz /static altında yer alıyor. Diğer yandan index.html'deki All Categories bağlantısına basarsak veya URL bilgisi olarak /category şeklinde bir talep gönderirsek aşağıdaki JSON içeriğini elde ettiğimizi görebiliriz.

Eğer fighter ya da cruiser kategorisindeki modelleri görmek istersek göndereceğimiz taleplere karşın aşağıdaki sonuçları alırız.

/category/fighter

/category/cruiser için 

Pek tabii olmayan kategori için slice içeriği boş olacağından null bir JSON çıktısına ulaşırız.

Bu noktada belki de çok daha şık bir HTML hata sayfasına yönlendirme yaptırabiliriz ne dersiniz? Görüldüğü gibi Router nesne örnekleri üzerinden ServeFiles, GET gibi fonksiyonları bir arada kullanarak static içerik sunabilen ve REST davranış gösterip HTTP taleplerine JSON çıktılarla cevap veren bir web uygulaması geliştirmek oldukça kolay. Bu tekniği kullanarak veri içeriği sunan basit REST servisleri help sayfaları ile birlikte geliştirmeniz mümkün. Yine de kaçırdığım çok şey olduğundan adım gibi eminim. Şu konuda HTTP Post, Put, Delete, Patch gibi fonksiyonellikleri bir deneyimlemek lazım. Bunları da ilerleyen zamanlarda incelemeye çalışacağım. Şimdilik bu kadar. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

GoLang - REST Servisimizi SQLite'a Bağlayalım

$
0
0

Merhaba Arkadaşlar,

Son yazılarımızda GoLang ile web uygulamalarının geliştirilmesi üzerinde durduk. Yeni şeyler öğrendikçe bunları farklı örnekler üzerinden denemeye gayret ediyorum. Bu sefer HTTP yönlendiricimizi SQLite ile çalışan basit bir REST servisi için kullanmaya çalışacağız. Kodlara geçmeden önce sisteminize SQLite yüklemiş olduğunuzdan emin olun (Kendi sisteminiz için uygun sürümü SQLite'ın şuradaki resmi adresinden bulup indirebilirsiniz) SQLite yazımızın kapsamı dışında ama bizim için hafif bir veri saklamak fonksiyonelliğini sunacağını ifade edebiliriz. Bu tipteki veritabanları fiziki birer dosya olarak tutulmaktalar. Bu nedenle geliştireceğimiz Go örneğinin erişebileceği bir konumda ilgili veritabanı dosyasının bulunması yeterli.

Ben örnek veri kümesi için bir önceki yazıda kullandığım Star Wars çözümünü baz aldım. Yani Category ve buna bağlı Model isimli birer tablo söz konusu. Tablolar arasında bire-çok ilişkili CategoryId alanı üzerinden sağlayabiliriz. Böylece bir kategori altındaki tüm modelleri ele alacağımız basit bir senaryo üzerinde de durabiliriz. Tabii öncelikle komut satırından aşağıdaki betikler yardımıyla starwars.sdb isimli veritabanını oluşturmamız gerekiyor (Aslında istediğiniz bir uzantıyı kullanabilirsiniz nitekim SQLite dosyaları ikili-binary formatta tutulan içeriklere sahiptirler. Yani uzantısının ne olduğunu bir önemi yok) Sonrasında Category ve Model isimli iki tablo ekleyip örnek veriler ile doldurursak da güzel olabilir.

Veritabanı ve Tabloların Hazırlanması

sqlite3 starwars.sdb

.databases

.open starwars.sdb

create table Category(
Id number primary key,
Name varchar(30)
);
insert into Category (Id,Name) values (1,"fighter");
insert into Category (Id,Name) values (2,"cruiser");
select * from Category;

create table Model(
Id number primary key,
Title varchar(50),
ListPrice real,
CategoryId number
);

insert into Model values (1,"V-Wing Fighter",45.50,1);
insert into Model values (2,"Naboo N-1 Starfighter",250.45,1);
insert into Model values (3,"Republic Cruiser",450.00,2);
insert into Model values (4,"Republic Attack Cruiser",950.00,2);
insert into Model values (5,"ETA-2 Jedi Starfighter",650.50,1);
insert into Model values (6,"Delta-7 Jedi Starfighter",250.35,1);
insert into Model values (7,"B-Wing",195.50,1);
insert into Model values (8,"Y-Wing",45.50,1);
insert into Model values (9,"Mon Calamari Star Cruiser",1500.50,1);
select * from Model where CategoryId=1;

Eğer SQLite kurulumunuzda bir sorun yoksa yukarıdaki komutların hatasız çalışması gerekir. Aynen aşağıdaki ekran görüntüsündekine benzer olacak şekilde.

Servis Tarafı

Önceki yazılarımızda olduğu gibi yönlendirme işlemlerimiz için Julien Schmidt'in (bu soyadını tek seferde asla yazamadım) httpRouter paketinden yararlanacağız. Diğer yandan SQLite veritabanını kullanacağımız için yardımcı bir kütüphaneyi daha işin içine katacağız. github.com/mattn/go-sqlite3 adresinde yer alan paket SQLite üzerinde gerçekleştireceğimiz işlemlerde bize kolaylıklar sağlayacak(Paketin yazarı Japon'ya Osaka'dan. Henüz ingilizceye çeviremediğim ama oldukça merak ettiğim blog adresi de burada) Aynen httpRouter paketinin elde edilişinde olduğu gibi LiteIDE'nin Build->Get komutunu kullanarak ilgili kütüphanenin sisteme yüklenmesini sağlayabilirsiniz. (Ben yükleme işlemi sırasında 64Bit Windows'umdaki farklı MinGW ve GCC sürümleri nedeniyle hatalarla karşılatım ve güncel versiyonunu yükleyerek sorunu aştım. Şu adrese uğramanız gerekebilir)Şimdi Server.go isimli dosyamızın içeriğini aşağıdaki gibi oluşturalım.

package main

import (
	"database/sql"
	"encoding/json"
	"entity/starwars"
	"fmt"
	"log"
	"net/http"
	"strconv"

	"github.com/julienschmidt/httprouter"
	_ "github.com/mattn/go-sqlite3"
)

func main() {
	router := httprouter.New()

	router.GET("/", home)
	router.GET("/categories", getCategories)
	router.GET("/categories/:categoryId", getModelsByCategoryId)
	router.GET("/models/:firstLetter", getModelsByFirstLetter)
	router.POST("/newCategory", createCategory)

	http.ListenAndServe(":4571", router)
}

func createCategory(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	category := starwars.Category{}
	json.NewDecoder(r.Body).Decode(&category)
	log.Printf("Insert request. %d,%s\n", category.Id, category.Name)
	conn, _ := sql.Open("sqlite3", "starwars.sdb")
	defer conn.Close()
	_, err := conn.Exec("Insert into Category values (?,?)", category.Id, category.Name)
	if err == nil {
		render(w, category)
	} else {
		log.Println(err.Error())
		//404 basılabilir
	}
}

func getCategories(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	conn, _ := sql.Open("sqlite3", "starwars.sdb")
	defer conn.Close()
	rows, _ := conn.Query("Select Id,Name from Category order by Name")
	defer rows.Close()
	categories := make([]*starwars.Category, 0)
	for rows.Next() {
		category := new(starwars.Category)
		rows.Scan(&category.Id, &category.Name)
		categories = append(categories, category)
	}
	render(w, categories)
}

func getModelsByCategoryId(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	conn, _ := sql.Open("sqlite3", "starwars.sdb")
	defer conn.Close()
	id, _ := strconv.Atoi(params.ByName("categoryId"))
	cRow := conn.QueryRow("Select * from Category Where Id=?", id)
	ctgry := new(starwars.Category)
	if cRow != nil {
		cRow.Scan(&ctgry.Id, &ctgry.Name)
		rows, _ := conn.Query("Select Id,Title,ListPrice from Model where CategoryId=?", id)
		defer rows.Close()
		models := make([]*starwars.Model, 0)
		for rows.Next() {
			model := new(starwars.Model)
			model.Category = starwars.Category{Id: ctgry.Id, Name: ctgry.Name}
			rows.Scan(&model.Id, &model.Title, &model.Price)
			models = append(models, model)
		}
		render(w, models)
	}
}

func getModelsByFirstLetter(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	conn, _ := sql.Open("sqlite3", "starwars.sdb")
	defer conn.Close()
	statement := fmt.Sprintf("Select Id,Title,ListPrice from Model where Title like '%s%%'", params.ByName("firstLetter"))
	rows, _ := conn.Query(statement)
	defer rows.Close()
	models := make([]*starwars.Model, 0)
	for rows.Next() {
		model := new(starwars.Model)
		rows.Scan(&model.Id, &model.Title, &model.Price)
		models = append(models, model)
	}
	render(w, models)
}

func home(rWriter http.ResponseWriter, request *http.Request, _ httprouter.Params) {
	fmt.Fprintf(rWriter, "Star Wars universe!")
}

func render(w http.ResponseWriter, d data) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	jContent, _ := json.Marshal(d)
	fmt.Fprintf(w, "%s", jContent)
}

type data interface {
}

Bu kez kodlarımız biraz karmaşık gibi(Kendime not: Kod tekrarlarını azalt) Uygulamamız temel olarak aşağıdaki taleplere cevap verecek şekilde geliştirildi.

HTTP Get; / ; root taleb
HTTP Get ; /categories ; kategorileri getirecek
HTTP Get ; /categories/:categoryId ;belli bir kateogori numarasındaki modelleri listeleyecek(models/:categoryId de olabilirdi belki)
HTTP Get ; /models/:firstLetter ; modellerin baş harfine göre listelenmesini sağlayacak
HTTP Post; /newCategory ; yeni bir kategorinin sisteme eklenmesi için kullanılacak

Aslında önceki yazılarımızdan farklı olarak bir tane HTTP Post metodumuz bulunduğunu ifade edebiliriz. /newCategory talebimiz ile yeni bir kategoriyi Category tablosuna eklemeyi planlıyoruz. main fonksiyonumuz yönlendirici nesnenin yukarıdaki adresler için eşleştireceği metod bildirimleri ile başlıyor. Sunucumuz localhost adresinden ve 4571 nolu port üzerinden hizmet verecek şekilde ayarlanıyor.

Yeni Bir Kategori Eklemek

İlk olarak createCategory fonksiyonunu ele alalım. SQLite veritabanımızdaki Category tablosuna yeni bir kategori eklemek istiyoruz. İstemci taleplerini HTTP Post metodu ile ve JSON formatında olacak şekilde kabul edeceğiz. Category yapısı önceki yazılarımızdan da hatırlayacağınız gibi sistemde ayrı bir paket içerisinde duruyor(entity/starwars) İstemciden gönderilecek JSON içeriğinde Id ve Name alanlarının olması yeterli. NewDecoder ile üretilen çözümleyici, Request nesnesinden gelen Body içeriğini ayrıştırıyor ve sonuçları category değişkenine aktarıyor. & ile bir adres aldığımıza dikkat edelim(Pointer'ları hatırlayalım). Decode fonskiyonunun parametre yapısına baktığınızda interface kabul ettiğini göreceksiniz. Bu teoriyi kodun ilerleyen kısımlarında biz de değerlendireceğiz.

Gelen bilgileri logladıktan sonra SQLite operasyonumuza başlıyoruz. Open fonksiyonu ile sqlite3 veritabanı sürücüsünü kullanarak, sunucu ile aynı adreste yer alan starwars.sdb isimli veritabanını belleğe açıyoruz. Exec fonksiyonu basit bir Insert sorgusu içeriyor. Bu sorgunun parametrik olduğuna ve parametre bildirimleri için soru işareti kullanıldığına dikkat edelim. Eğer Exec fonksiyonunu bir hata döndürmediyse insert işleminin başarılı olduğunu düşünerek gelen JSON içeriğine göre oluşturulan category değişkenini bu kez render fonksiyonu üzerinden istemciye basıyoruz. Bu render işlemlerini diğer fonksiyonlarda da kullanacağımız için kod tekrarını biraz olsun önlemek amacıyla geliştirdik. Yanlız fonksiyonun ikinci parametresine bilhassa dikkat edelim. data isimli bir interface tipi almakta. Aslında bu sayede render fonksiyonuna JSON olarak serileşebilecek herhangibir tipi aktarabiliriz. Bu tip tek bir Category örneği olabileceği gibi Model örnekleri içeren bir slice'da olabilir. Fonksiyon Header bilgisini JSON formatında işaretleyip HTTP 200 kodunu da ekleyerek bir çıktı oluşturuyor. Bu çıktı ilk parametre ile gelen ResponseWriter üzerinden istemciye gönderiliyor. Çalışma zamanında Postman veya muadili bir uygulamayı kullanarak yeni bir kategori oluşturmayı deneyebiliriz. Aşağıdaki örnek bir POST çağrısı görüyorsunuz.

Buna göre kategorileri getiren talebi yaptığımızda aşağıdaki gibi Destroyer sınıfının da eklendiğini görebiliriz.

Kategori Listesi Nasıl Geliyor?

Tüm kategorilerin istendiği talebe karşılık gelen fonksiyonumuz getCategories. /categories şeklinde gelecek bir istemci talebi sonrası devreye giriyor. Fonksiyon yine SQLite veritabanını açarak işe başlıyor. Bu sefer Category tablosunun içeriğini Name alanına göre alfabetik sırada talep ediyoruz. Select sorgusu için Query fonksiyonu çalıştırılıyor. Fonksiyonun geriye döndürüğü sonuç kümesi üzerinde bir for döngüsü ile hareket ediyoruz. Next fonksiyonu satırlarda ileri doğru hareket etmemizi sağlıyor. Döngü öncesinde starwars.Category tipinden oluşturulan bir slice örneği mevcut. Tüm kategoriler bu listeye ekleniyorlar. Ekleme işlemi sırasında dikkat edilmesi gereken nokta ise Scan isimli fonksiyonun kullanılması. & ile adresleri üzerinden yakaladığımız Id ve Name alanlarını sorgu sonucu gelen kolon değerleri ile eşleştiriyoruz. append fonksiyonu da slice içeriğine ilgili satırı eklememizi sağlıyor. defer çağrıları ile fonksiyon sonlanırken gerekli kapatma işlemlerinin yapılmasını bildiriyoruz. 

Belli Bir Kategorideki Ürünlerin Çekilmesi

4571/categories/1 şeklinde gelecek bir talebe karşılık CategoryID alanının değeri 1 olan modelleri listelemek niyetindeyiz. Tüm kategorileri getirmekten farklı olarak params ile yakaladığımız categoryId değerinin SQL'e ait where ifadesinde parametrik kullanılması söz konusu diyebiliriz. Küçük bir de problemimiz var. Nesne modeli ilişkisinde modelleri kategorileri ile bağlarken tip kullandık. Yani bir Model aslında CategoryId değil Category nesne örneğini içeriyor. Veritabanında ki modelimizde ise bir modeli kategori numarası üzerinden ilişkilendirdik. Bu sebepten fonksiyon öncelikle ilgili kategori numarasına bağlı Category satırını buluyor. Eğer böyle bir kategori varsa nesne olarak örnekleyip bulunan modeller ile ilişkilendiriyor. Sonrasında üretilen models içeriğinin render fonksiyonuna gönderilerek JSON formatında istemciye gönderilmesi söz konusu. Aşağıda çalışma zamanına ait örnek bir görüntü yer alıyor. Aslında çekilen Model nesne topluluğu için Category nesnelerini doldurmak zorunda değiliz. İşin aslı bize güzel bir ORM(Object Relational Mapping) sistemi lazım. İlerleyen yazılarımızda bu konuyu da ele almaya çalışacağım.

Baş Harfi "A" Olan Modelleri Bulalım

getModelsByFirstLetter fonksiyonunun görevi bu. Baş harfine göre modellerin listesini JSON formatında döndürmek için çalışıyor. Aslında model depomuz oldukça fakir diyebilirim. Aynı harf ile başlayan modeller yok gibi. Ancak siz model üretmek için yazacağınız POST temelli yeni operasyonunuz ile bu test içeriklerini kolaylıkla üretebilirsiniz. Hatta belki bir .Net arabiriminden bu servisi çağırarak modelleri oluşturmayı daha kolay hale de getirebilirsiniz. .Net olmak zorunda değil, basit bir HTML sayfası bile olabilir. Sanırım size çaktırmadan bir görev verdim :) Fonksiyonumuza geri dönelim. Buradaki SQL sorgusu içerisinde Like kullanımı söz konusu. Nitekim baş harfi 'şununla' başlayanları çek gibi bir şey demek istiyoruz. Bu nedenle gelen parametreyi 'A%' gibi bir formata dönüştürmek gerekiyor. Bunun için fmt paketinin Sprintf fonksiyonundan yararlanıyoruz. Kodun kalan kısmı öncekilere benziyor. Next fonksiyonu ile dolaştığımız veri içeriğini ekrana basıyoruz. Tabii kategori ile ilgili sorunumuz var. İçeriği varsayılan değerleri ile geliyor ki bu son derece normal. Çünkü CategoryId ye karşılık gelen Category içeriğini bulup yüklemedik. Ah Burak ah :[] Demek ki bir Id'ye bağlı kategoriyi bulup geriye Category örneği olarak döndürecek bir fonksiyonellik burada işimize yarayabilir. Nitekim iki yerde ihtiyacımız oldu. Bu eklemeyi benim için yaparsınız değil mi?

Sonuç

Bu kısa araştırma yazımızın amacı REST(Representational State Transfer) tabanlı bir GO servisinde SQLite için gerekli bağlantıları nasıl tesis edebileceğimizi ilkel bir örnek üzerinden görmekti. SQLite dışında elbette farklı veri depolama ürünlerini de kullanmak isteyebiliriz. GitHub üzerinden yayınlanan şu adreste oldukça geniş bir kütüphane topluluğu bulunuyor. Firebird'ten DB2'ya, MySQL'den Oracle'a, YQL(Yahoo Query Language)'ten, SQLite'a kadar geniş bir yelpaze söz konusu diyebilirim. Örneğimizde HTTP Get taleplerinden farklı olarak HTTP Post metoduna da yer verdik. Pek tabii Push, Delete gibi operasyonlar da söz konusu. Örneğin bu kodun üstüne bir kategori veya ürünü silmek için gerekli HTTP Delete operasyonunu ilave edebilirsiniz. Başlangıç benden devam ettirmesi sizden. REST testleri yapmamız da oldukça kolay. Chrome tarayıcısına eklenti olarak da gelen Postman aracı ekran görüntülerinde de gördüğünüz üzere oldukça pratik ve basit. Post gibi HTTP gövdesinden bir şeyler göndermemiz gereken senaryoları ele almak için ideal. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

GoLang - Bir ORM Denemesi

$
0
0

Merhaba Arkadaşlar,

Yakın zamanda bir şampiyonlar ligi finali vardı. Real Madrid ve Juventus arasında oynanan maçı eflatun beyazlılar 4-1 gibi farklı bir skorla kazandı. Aslında ilk yarı Juventus çok daha iyi paslaşıyordu lakin ikinci yarı Ronaldo faktörü ön plana çıktı. Modric'in de etkili orta saha oyunu ile İspanyol ekibi kupayı üstüste ikinci kez almayı başardı. Benim gönlüm Juventus'tan yanaydı çünkü kalede 39 yaşında olan Buffon yer alıyordu. Özellikle İtalyan kulüplerinden 40lı yaşlarına kadar oynayan çok başarılı sporcular çıkıyor(Francesco Totti, Andrea Pirlo, Roberto Baggio vb) Kendilerine iyi bakıyorlar ve özellikle de mesleklerine profesyonelce yaklaşıyorlar. Bu ilham verici bir şey. Hatta pek çok genç sporcuya da örnek olmalı diye düşünüyorum. Gerçi Buffon'a bir şekilde makalemde yer vermek istediğim için bu girişi yaptım. Gelin asıl konumuza geçelim.

Veri depolamanın en popüler yolu NoSQL veya RDBMS bazlı sistemler. 90lı yıllardan beri program yazan insanlar için de özellikle Microsoft SQL Server, Oracle ve sonrasında gelen MySQL ya da SQLite gibi yapılar da oldukça fazla oranda kullanılmaktalar. E tabii bildiğiniz üzere bu serüvenin ortalarında bir yerlerde SQL dili ve RDBMS yapısının, programcıların kodlama mantığına biraz ters gelişi de vuku buldu. Sonuçta SQL tarafındaki varlıkların programatik ortamda ve özellikle nesne yönelimli(Object Oriented) dünyada nasıl daha anlamlı ele alınabileceğinin yolları araştırıldı. Artık popüler olma zamanı nesne ilişkilendirmelerini sağlayan araçlardaydı. Object Relational Mapping(O/RM) konusu gündemdeydi. Neredeyse bütün programlama çatılarının bu tip araçlarla yakın ilişkisi bulunmakta. Hibernate ve Entity Framework gibi en azından ülkemizde adını sıklıkla duyduğumuz araçlar dışında farklı pek çok ürün de bulunmakta. Ben de GoLang tarafında SQLite operasyonlarını incelemeye çalışırken "bir O/RM aracı var mıdır?" sorusuna cevap ararken buldum kendimi. Murat Hoca'nın kitabı, GoLang'in resmi dokümanları, Stackoverflow tartışmaları derken gitub üzerinden sunulan GORM ile karşlılaştım.

Geliştirici dostu olan fantastik bir ORM aracı olarak tanımlamış kendisini. Pek çok özelliği var. Eager Loading, Transaction, Auto-Migration, Callback fonksiyonları, genişletilebilirlik, tablolar arası ilişkilerin ifade edilmesi bu özelliklerden sadece bir kaçı(github adresinden kaynak kodlarına da bakabilirsiniz) Tabii benim amacım çok temel düzeyde aracı nasıl kullanabileceğimi öğrenmekti. Elimde SQLite gibi hafif ama bence inanılmaz yetenekli bir veritabanı bulunuyordu. GO dili ile ilgili bilgilerim artmaktaydı. Entity modelini baştan tasarlayabilirdim. Sonrasında Gorm'un modele ait tabloları benim için oluşturması, bir kaç satır verinin insert edilmesi, belki bir update veya delete operasyonunun gerçekleştirilmesi "Hello Gorm" demek adına yeterliydi.

Modelin Kurgulanması

İşe bir Entity paketi hazırmakla başladım. Daha önceden yaptığım gibi GOPATH'in belirttiği src klasörü altında konuşlandırıp tüm GO uygulamaları tarafından erişilebilecek bir paket yazmaya karar verdim. İçinde iki tane Entity olsa da Gorm'un model tarafındaki bir kaç yeteneğini anlamak için ideal bir seçimdi. İşte Southwind(Emektar Northwind gelir hep aklıma ki şu adresten REST tabanlı servislerine de erişebilirsiniz) paketinin basit içeriği.

package Southwind

import (
	"github.com/jinzhu/gorm"
)

type Employee struct {
	gorm.Model
	FirstName string `gorm:"not null;size:30"`
	LastName  string `gorm:"not null;size:30"`
	Emails    []Email
}

type Email struct {
	gorm.Model
	EmployeeID int    `gorm:"index"`
	Mail       string `gorm:"type:varchar(50);unique_index"`
	IsActive   bool
}

Öncelikle gorm'un import edilmesi gerekiyor. LiteIDE kullandığım için işim kolay. Paket bildiriminden sonra build->get komutunu vererek Gorm'un sisteme yüklenmesini sağlayabiliyoruz(Bunun her tür github go paketi için geçerli olduğunu hatırlayalım lütfen)İki yapı görüyorsunuz. Employee ve Email. Aslında bire çok(one to many) ilişkiyi kullandığımız bir modelleme söz konusu. Bir çalışanın birden fazla email adresi olabilir düşüncesi söz konusu. Her iki yapı içerisinde Model tipinden bir değişken tanımı yer alıyor. Hem Employee hem Email yapıları içerme tekniği ile bu tipi uygulamakta. Model aslında ana nesne olarak düşünülebilir. ID, CreatedAt, UpdatedAt ve DeletedAt şeklinde standart alanlar içermekte. Dolayısıyla tüm tiplerimiz bu özelliklere sahip olacaklar. ID otomatik artan bir Primary Key iken diğer alanlar oluşturulma, güncelleme ve silinme zamanı bilgileri için kullanılmaktalar. Bu alanlar istenirse ezilebilir. Bu tip özelleştirme yetenekleri Gorm içerisinde mevcut. 

Bire çok ilişkiyi nesne modelinde kurgulamak için Employee içerisinde Email türünden bir slice ve Email içinde de EmployeeID isminde int türünden bir alan tanımlandığına dikkat edelim. Email nesneleri hangi Employee ile ilişkilendirildiklerini EmployeeID alanı üzerinden otomatik olarak anlayabilirler. Daha doğrusu Gorm çalışma zamanı bu ilişkiyi isimlendirmelere göre kolayca kurabilir. Tabii ` işaretlerinden sonra gelen tanımlamalar da önemli. EmployeeID tarafında bu alanın bir index olduğu belirtilmekte. Peki ya diğer bildirimler. Örneğin FirstName ve LastName alanları 30ar karakter boyutunda olabilirler ve null değer içeremezler. Mail alanı 50 karakteri geçemezken benzersiz bir içeriğe sahip olmak zorundadır. Bu tanımlamalar özellikle tablolar oluşturulurken Gorm motoru tarafından değerlendirilecektir.

Paketi bu şekilde oluşturduktan sonra önce Build sonra da Install işlemlerini gerrçekeştirmek lazım. Böylece GOPATH'in belirttiği konumda yer alan pkg klasör altında a uzantılı derlenmiş hali üretilmiş olacaktır.

Asıl Kod

Artık asıl test kodları geliştirmeye başlanabilir. 

package main

import (
	"fmt"

	"entity/southwind"

	"github.com/jinzhu/gorm"
	_ "github.com/mattn/go-sqlite3"
)

func main() {
	db, err := gorm.Open("sqlite3", "db\\southwind.sdb")
	db.LogMode(true)
	defer db.Close()
	if err == nil {
		//db.SingularTable(true)
		db.AutoMigrate(&Southwind.Employee{}, &Southwind.Email{})
		db.Model(&Southwind.Employee{}).Related(&Southwind.Email{})

		burakMails := []Southwind.Email{
			Southwind.Email{Mail: "selim@buraksenyurt.com", IsActive: true},
			Southwind.Email{Mail: "burak.senyurt@southwind.com", IsActive: false},
			Southwind.Email{Mail: "burakselimsenyurt@gmail.com", IsActive: true},
		}

		burak := Southwind.Employee{FirstName: "burak", LastName: "senyurt", Emails: burakMails}
		db.Create(&burak)

		loraMails := []Southwind.Email{
			Southwind.Email{Mail: "lora@kimbilll.moon", IsActive: true},
			Southwind.Email{Mail: "kimbill.the.black.lora@southwind.com", IsActive: true},
		}
		lora := Southwind.Employee{FirstName: "Lora", LastName: "Kimbılll", Emails: loraMails}
		db.Create(&lora)

		WriteToScreen(burak)
		WriteToScreen(lora)

		var burki Southwind.Employee
		db.Find(&burki, "ID=?", 1) //Önce
		db.Model(&burki).Update("LastName", "Selim Senyurt")
		WriteToScreen(burki)

		var buffon Southwind.Employee

		db.Model(&buffon).Where("ID=?", 2).Updates(map[string]interface{}{"FirstName": "Cianluici", "LastName": "Buffon"})
		db.First(&buffon, 2) //Direkt primary key üstünden(varsayılan olarak ID) arama yapar
		WriteToScreen(buffon)
	} else {
		fmt.Println(err.Error())
	}
}

func WriteToScreen(e Southwind.Employee) {
	fmt.Printf("%d\t%s,%s,%s\n", e.ID, e.FirstName, e.LastName, e.CreatedAt)
	for _, email := range e.Emails {
		fmt.Printf("\t%d:%s\n", email.ID, email.Mail)
	}
}

southwind dışında SQLite ve Gorm için gerekli paket bildirimleri ile işe başlıyoruz. gorm'un Open fonksiyonu iki parametre alıyor. İlki kullanılan veritabanı sürücüsü ki biz örneğimizde SQLite veritabanını kullanacağız. İkincisi ise veritabanı dosyasının adı. db alt klasöründe konuşlandıracağımız southwind.sdb isimli bir dosya söz konusu. LogMode fonksiyonuna true değerini atayarak model tarafında gerçekleşen işlemlerin SQL karşılıklarının debug penceresine basılmasını istiyoruz. Böylece SQLite tarafında olan biteni görme şansına da sahibiz. AutoMigrate fonksiyonuna iki parametre geçiyoruz. Employee ve Email. Bunların adreslerini geçtiğimize dikkat edelim(& ne işe yarıyordu hatırlayın). AutoMigrate eğer yoklarsa ilgili tabloların modele bakılarak oluşturulmasını sağlayacak. Güncellemeler varsa bunlar da ilgili fonksiyon tarafından ele alınmakta. Tipik bir migration işlemi gerçekleştirildiğini ifade edebiliriz ancak bir çok işlemi(tablo oluşturmak gibi) manuel de yazabilliriz. 

Model fonksiyonu ile Employee ve Email yapıları arasındaki ilişkiyi tesis ediyoruz. Hatırlayacağınız gibi "bir çalışanın birden fazla email adresi olabilir" düşüncesinden yola çıkarak her iki model arasında bire çok ilişki olması gerektiğini yapıları yazarken belirtmiştik. Devam eden satırda bir Email listesi oluşturuyor ve örnek bir kaç veri üretiyoruz. Sonrasında burak isimli Employee tipinden bir nesne örnekleniyor. Bu nesnenin Emails niteliğini de burakMails değişkenine bağlıyoruz. Create fonksiyonuna parametre olarak gönderilen burak değişken adresi sonrası hem Email hem de Employee örnekleri için gerekli Insert işlemleri çalıştırılacak. Employee ve Email arasındaki ilişkiyi tesis ettiğimizden tüm Email'lerin EmployeeID değerleri otomatik olarak burak isimli çalışanın ID alanına bağlanacaklar.

Kodun ilerleyen kısmında bu kez lora isimli bir çalışan üretip bir kaç email adresi daha ekliyoruz. Yine Create fonksiyonundan yararlandığımızı ifade edelim. WriteToScreen fonksiyonu bir Employee nesnesini alıp çalışan bilgileri ve bu çalışana ait email adreslerini ekrana yazdırmaktan sorumlu. Dikkat edileceği üzere çalışanın maillerine giderken normal bir for döngüsü ve range işlevini kullanıyoruz. Sanki veritabanında değilmişiz gibi. Ama arka planda tüm bunlar SQL sorgusu haline gelmekteler. 

Kodun bir sonraki kısmında Find operasyonu ile ID alanının değeri 1 olan Employee satırını yakalayıp Model ve Update fonksiyonlarını peş peşe çağırarak bir güncelleme işlemi gerçekleştirmekteyiz. Find ile bulduğumuz nesneyi &burki değişkenine çıkıyoruz. Aynı değişkeni Model fonksiyonunda kullanıp arkadan gelen Update ile LastName alanının değerini değiştiriyoruz. Burada tipik bir Update sorgusu olduğunu ifade edebiliriz ve ilerleyen kod satırlarında farklı bir sürümü daha yer alıyor. Bu kez Model üzerinden Where operasyonu'na gidip ID değeri 2 olan Email satırını yakalıyoruz. Hemen arkasından Updates isimli bir fonksiyon çağrısı daha geliyor. Bu fonksiyon ile FirstName ve LastName alanlarını güncelliyoruz. First fonksiyonu ikinci parametre de aldığı değeri otomatik olarak ilgili Entity'nin ID alanı ile ilişkilendirmekte. Yani Employee tablosunda ID alanı 2 olan satırı yakalayıp içeriğini buffon değişkenine çekmekteyiz(Elbette farklı bir where kriteri de koyabilirsiniz) Sonrasında bulduğumuz sonuçları ekrana basıyoruz. Amacımız 2 numaralı çalışanın adının değiştiğini görmek.

Sonuçlar

Uygulamanın çalışma zamanı çıktısı aşağıdaki gibi olacaktır. Adım adım üretilen SQL sorgularını incelemenizi ve GO lang çıktılarına bakmanızı öneririm. DB tarafına en ufak bir SQL sorgusu göndermeden var olan Entity örnekleri üzerinde gerçekleştirdiğimiz Insert, Update, Select gibi işlemler otomatik olarak SQLite üzerinde çalıştırılmıştır. Programcının aşina olduğu kavramları veritabanı tarafına göndermiş durumdayız.

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:21) 
[2017-06-05 00:02:57]  [182.01ms]  CREATE TABLE "employees" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"deleted_at" datetime,"first_name" varchar(30) NOT NULL,"last_name" varchar(30) NOT NULL ) 

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:21) 
[2017-06-05 00:02:58]  [147.00ms]  CREATE INDEX idx_employees_deleted_at ON "employees"(deleted_at) 

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:21) 
[2017-06-05 00:02:58]  [155.00ms]  CREATE TABLE "emails" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"deleted_at" datetime,"employee_id" integer,"mail" varchar(50),"is_active" bool ) 

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:21) 
[2017-06-05 00:02:58]  [170.00ms]  CREATE INDEX idx_emails_deleted_at ON "emails"(deleted_at) 

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:21) 
[2017-06-05 00:02:58]  [184.01ms]  CREATE INDEX idx_emails_employee_id ON "emails"(employee_id) 

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:21) 
[2017-06-05 00:02:58]  [115.00ms]  CREATE UNIQUE INDEX uix_emails_mail ON "emails"("mail") 

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:22) 
[2017-06-05 00:02:58]  [1.00ms]  SELECT * FROM "emails"  WHERE "emails"."deleted_at" IS NULL AND (("employee_id" = '0'))

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:31) 
[2017-06-05 00:02:58]  [1.00ms]  INSERT INTO "employees" ("created_at","updated_at","deleted_at","first_name","last_name") VALUES ('2017-06-05 00:02:58','2017-06-05 00:02:58',NULL,'burak','senyurt')

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:31) 
[2017-06-05 00:02:58]  [0.00ms]  INSERT INTO "emails" ("created_at","updated_at","deleted_at","employee_id","mail","is_active") VALUES ('2017-06-05 00:02:58','2017-06-05 00:02:58',NULL,'1','selim@buraksenyurt.com','true')

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:31) 
[2017-06-05 00:02:58]  [0.00ms]  INSERT INTO "emails" ("created_at","updated_at","deleted_at","employee_id","mail","is_active") VALUES ('2017-06-05 00:02:58','2017-06-05 00:02:58',NULL,'1','burak.senyurt@southwind.com','false')

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:31) 
[2017-06-05 00:02:58]  [1.00ms]  INSERT INTO "emails" ("created_at","updated_at","deleted_at","employee_id","mail","is_active") VALUES ('2017-06-05 00:02:58','2017-06-05 00:02:58',NULL,'1','burakselimsenyurt@gmail.com','true')

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:38) 
[2017-06-05 00:02:58]  [1.00ms]  INSERT INTO "employees" ("created_at","updated_at","deleted_at","first_name","last_name") VALUES ('2017-06-05 00:02:58','2017-06-05 00:02:58',NULL,'Lora','Kimbılll')

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:38) 
[2017-06-05 00:02:58]  [0.00ms]  INSERT INTO "emails" ("created_at","updated_at","deleted_at","employee_id","mail","is_active") VALUES ('2017-06-05 00:02:58','2017-06-05 00:02:58',NULL,'2','lora@kimbilll.moon','true')

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:38) 
[2017-06-05 00:02:58]  [0.00ms]  INSERT INTO "emails" ("created_at","updated_at","deleted_at","employee_id","mail","is_active") VALUES ('2017-06-05 00:02:58','2017-06-05 00:02:58',NULL,'2','kimbill.the.black.lora@southwind.com','true')
1	burak,senyurt,2017-06-05 00:02:58.6894696 +0300 EEST
	1:selim@buraksenyurt.com
	2:burak.senyurt@southwind.com
	3:burakselimsenyurt@gmail.com
2	Lora,Kimbılll,2017-06-05 00:02:58.8994816 +0300 EEST
	4:lora@kimbilll.moon
	5:kimbill.the.black.lora@southwind.com

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:44) 
[2017-06-05 00:02:59]  [3.00ms]  SELECT * FROM "employees"  WHERE "employees"."deleted_at" IS NULL AND ((ID='1'))

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:45) 
[2017-06-05 00:02:59]  [2.00ms]  UPDATE "employees" SET "last_name" = 'Selim Senyurt', "updated_at" = '2017-06-05 00:02:59'  WHERE "employees"."deleted_at" IS NULL AND "employees"."id" = '1'
1	burak,Selim Senyurt,2017-06-05 00:02:58.6894696 +0300 +0300

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:50) 
[2017-06-05 00:02:59]  [4.00ms]  UPDATE "employees" SET "first_name" = 'Cianluici', "last_name" = 'Buffon', "updated_at" = '2017-06-05 00:02:59'  WHERE "employees"."deleted_at" IS NULL AND ((ID='2'))

(C:/Go Works/Samples/book/Web Programming/Lesson_28/server.go:51) 
[2017-06-05 00:02:59]  [3.00ms]  SELECT * FROM "employees"  WHERE "employees"."deleted_at" IS NULL AND (("employees"."id" = '2')) ORDER BY "employees"."id" ASC LIMIT 1
2	Cianluici,Buffon,2017-06-05 00:02:58.8994816 +0300 +0300
Success: process exited with code 0.

Şu noktada SQLite üzerinden ilgili veritabanı açılırsa kod tarafındaki işlemlerin oraya da yansıdığını görebiliriz. Ancak dikkat çekici bir kaç nokta olduğunu da vurgulamak isterim. 

Mutlaka dikkatinizi çekmiştir ki FirstName first_name, LastName last_name, EmployeeId employee_id, IsActive is_active olarak ifade edilmiş durumdalar. Yani modelde belirttiğimiz alan adlarının büyük küçük harf durumlarına göre kolon adları şekillendirilmiş. Diğer yandan tablo adlarının çoğullandığını görüyoruz. Employee yerine Employees ve Email yerine Emails şeklinde bir isimlendirme söz konusu. Elbette tüm bunlar Gorm tarafında özelleştirilebilmekte. Gelelim veri içeriğine. İşte bir kaç örnek sorgu sonucu SQLite içeriği.

Tabii Gorm aracının dokümantasyonu oldukça geniş ve zengin. Özelleştirilebilecek, ince ayarlar yapılabilecek bir çok konu var. Ben "Hello Gorm" demeye çalıştım ve istediğim bilgileri aldım sayılır. Bundan sonrasında daha önceki web sunucusu ve REST servis örneklerini Gorm paketi ile çalışacak hale getirmeyi planlıyorum. Siz bu yazıyı Gorm dokümanını inceleyerek daha da ileriye götürebilirsiniz. İşe eksik olan Delete operasyonunu ekleyerek başlayabilirsiniz. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.


WCF Servis Yolunda Debelenirken

$
0
0

Merhaba Arkadaşlar,

Geçtiğimiz günlerde çalıştığım turuncu bankadaki bölümüm değişti. İsmini halen ezberleyemediğim Yazılım Geliştirme Sistemleri ve Platform Uygulamaları bölümünde yaşamımı sürdürmeye devam ediyorum. Yeni bölümümdeki ilk görevim ise ServiceStack yerini alabilecek bir çatının oluşturulması konusunda bir takım POC çalışmalarının yapılması. Önemli hedeflerden birisi WCF(Windows Communication Foundation) servislerinin IIS(Internet Information Services) bağımsız olarak dinamik bir şekilde ayağa kaldırılması ve istemci ile sunucu arasındaki mesajların yakalanarak kayıt altına alınabildiğinin görülmesi.

Epey zamandır WCF ile çalışmadığımdan baya pas tuttuğumu itiraf etmek isterim. Yazının konusu, devam etmekte olan POC(Proof of Concept) çalışmasının tamamını anlatmak değil ancak dinamik olarak host edilen servislere gelen ve servisten dönen mesajları nasıl yakalayabiliriz bunun bir yolunu bulmaya çalışmak. Hatta bu konuda çok yakın bir zamanda sevdiğim bir dostumun da sorusu olmuştu. Entegre olunan bir servise gelip giden mesajları nasıl yakalayabiliriz. Normal şartlarda WCF'in Trace ve Logging mekanizmalarını kullanarak bu mümkün ve oldukça kolay ama hedef buradaki takibi kontrol atlına almak. Yani mesajları yakaladığımız yerlerde araya girerek başlangıç için sadece loglamak(örneğin Console'a yazdırmak)

Çözümün Kısa Bir Özeti

Solution içeriği genel hatları ile aşağıdaki gibi.

SDK klasörü içerisinde diğer servis geliştiriciler için temel bir sözleşme sunmayı planladım. Aşağıdaki gibi bir arayüz(Interface) tipim var örneğin.

using System.ServiceModel;

namespace ING.ServiceFabric.SDK
{
    [ServiceContract]
    public interface ITunnelContract
    {
        [OperationContract]
        TunnelResponse Execute(TunnelRequest request);
    }
}

Hatta ISV klasöründeki projeler bu SDK'yı kullanarak geliştirilmiş örnek servis kütüphaneleri de içermekte. Aşağıdaki kod parçasında örnek bir uygulamasını görebilirsiniz. ITunnelContract arayüzü ServiceContract ve OperationContract nitelikleri sayesinde FraudCheckService tipine WCF Servis özelliğini kazandırmakta.

using ING.ServiceFabric.SDK;

namespace DAEXServiceLibrary
{
    public class FraudCheckService
        :ITunnelContract
    {
        public TunnelResponse Execute(TunnelRequest request)
        {
            return new TunnelResponse
            {
                 Output="Fraud check for customer"
            };
        }
    }
}

TEST klasöründe tahmin edileceği üzere Unit Test ve benzeri Console uygulamaları yer almakta.

JSON Bazlı Konfigurasyon

HOST isimli klasörde yer alan ServiceFabric projesinde bir Assembly içerisinde duran servislerin ayağa kaldırılması ile ilgili işlemler yer alıyor. Ama nasıl? Kısaca neler yapmaya çalıştığımı anlatayım.

WCF'in standart konfigurasyon sistemi config uzantılı dosyaları kullanmakta. Bir Web uygulaması söz konusu ise web.config diğerleri içinse app.config ağırlıklı olarak kullanılıyor. Bu davranışı değiştirmenin bir yolu var mı henüz bilmiyorum ama ServiceHost tipi ile servisleri dinamik olarak çalışma zamanında ayağa kaldırabildiğimizi ve bir takım ayarları kod tarafında yapabildiğimizin farkındayım. Bu nedenle servislere ait çalışma zamanı ayarlarını JSON formatında bir konfigurasyon dosyası olarak tutmaya çalıştım. Aşağıdaki gibi örnek bir JSON içeriğini kullanıyorum.

İçeriği jsoneditoronline.orgüzerinden oluşturmaya çalıştım. Nesne yapısını kurgulamam şimdilik yeterliydi.

Tabii projenin ilerleyen günlerinde bu JSON içeriğini oluşturacak ve okuyacak sınıfları sisteme dahil etmeyi de ihmal etmedim.

using ING.ServiceFabric.ConfigurationTypes;
using Newtonsoft.Json;
using System.IO;

namespace ING.ServiceFabric
{
    public class HostPackManager
    {
        public static HostPack ReadPack(string packFile)
        {
            HostPack pack = JsonConvert.DeserializeObject<HostPack>(File.ReadAllText(packFile));
            Environment environment = JsonConvert.DeserializeObject<Environment>(File.ReadAllText(pack.EnvironmentConfig));
            pack.AssemblyName = Path.Combine(environment.DllRootPath, pack.AssemblyName);

            return pack;
        }

        public static string WritePack(HostPack pack, string packFilePath)
        {
            string jsonContent=JsonConvert.SerializeObject(pack);
            File.WriteAllText(packFilePath,jsonContent);
            return jsonContent;
        }
    }
}

Burada yine detaya girmeyeceğim ancak JSON içeriğini yönetimli kod tarafında daha kolay idare etmek için HostPack ve Environment gibi sınıflar da yer almakta. Aynen .Net'in XML odaklı konfigurasyon dosyalarına olan yaklaşımı gibi. Her section'a karşılık gelecek bir sınıf.

Konfigurasyon dosyasının içeriğinde tutulan bilgileri nasıl kullanmak istediğime gelince. Her şeyden önce çalışma zamanında yüklenecek olan servisleri bir klasördeki dll'lerden almak istiyorum. Yani Host uygulama kullanacağı servisleri projeye referans etmeye gerek duymadan ayağa kaldıracak. Bu nedenle içerde kullanılacak dll bilgisini ve başka çevresel değişkenleri tutan bir dosya bilgisini tutmayı düşündüm. Kullanacağım ServiceHost tipinin bir BaseAddress ihtiyacı da olacak. Bunların dışında host'un sunacağı servisleri de bir şekilde tanımlamam gerekiyor. Servisin tip adı dışında Address Binding Contract üçlemesini de burada tutuyorum. Her servis için Metadata paylaşımı olacak mı, çalışma zamanındaki Exception detayları basılacak mı gibi aşina olduğumuz bilgileri de ilgili alanlarda tutmaktayım. Bu içeriğe göre FraudCheckService WSHttpBinding ile host edilecek. Diğer yandan henüz sertifika tanımlamamalarını entegre edecek kodları yazamadığımdan BasicHttpsBinding kullanan servisi test edememekteyim.

ServiceHost Türevli TowerHost

Genel hatları ile konfigurasyon bilgisini tutmayı bu şekilde kurgulamaya çalıştım. ServiceHost türevli tipin içeriği ise aşağıdaki şekilde.

using System;
using System.ServiceModel;

namespace ING.ServiceFabric
{
    public class TowerHost
        :ServiceHost
    {
        public TowerHost(Type serviceType,params Uri[] baseAddresses)
            :base(serviceType,baseAddresses)
        {
        }
    }
}

TowerHost sınıfı tipik olarak ServiceHost tipinden türemekte ve base kullanımı ile yapıcı metoduna gelen parametreleri doğrudan ServiceHost tipinin uygun yapıcısına aktarılmakta. Burada sonradan override etmeyi düşündüğüm üst sınıf üyeleri olacak. Şimdilik bu sade haliyle kalması yeterli. Gelelim asıl işi yapan TowerHostFactory sınıfına.

using ING.ServiceFabric.ConfigurationTypes;
using ING.ServiceFabric.EndpointBehaviors;
using ING.ServiceFabric.SDK;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;

namespace ING.ServiceFabric
{
    public class TowerHostFactory
    {
        public List<TowerHost> CreateTowerHost(string packFile)
        {
            List<TowerHost> hostList = new List<TowerHost>();
            HostPack pack=HostPackManager.ReadPack(packFile);            
            var assembly = Assembly.LoadFile(pack.AssemblyName);
            foreach (var service in pack.Services)
            {
                ServiceInfo sInfo = GetServiceInfo(service);
                var host=CreateTowerHost(assembly, sInfo);
                hostList.Add(host);
            }

            return hostList;
        }

        private TowerHost CreateTowerHost(Assembly assembly,ServiceInfo serviceInfo)
        {
            object service = assembly.CreateInstance(serviceInfo.TypeName);
            
            var host = new TowerHost(service.GetType(),new Uri(serviceInfo.Address));
            var bindingTypeName = string.Format("System.ServiceModel.{0}", serviceInfo.BindingName);
            var serviceModelAssembly = Assembly.GetAssembly(typeof(BasicHttpBinding));
            Binding bindingInstance = (Binding)serviceModelAssembly.CreateInstance(bindingTypeName);             
            var endPoint=host.AddServiceEndpoint(typeof(ITunnelContract), bindingInstance,serviceInfo.Address);
            endPoint = SetMetadataBehavior(serviceInfo, host, bindingInstance, endPoint);
            //endPoint = SetServerCertificate(serviceInfo, host,endPoint);
            host.Description.Behaviors.Find<ServiceDebugBehavior>().IncludeExceptionDetailInFaults = serviceInfo.IncludeExceptionDetails;
            endPoint.EndpointBehaviors.Add(new EndpointMessageInspectorBehavior());

            return host;
        }

        private ServiceEndpoint SetServerCertificate(ServiceInfo serviceInfo,TowerHost host,ServiceEndpoint endpoint)
        {
            //host.Credentials.ServiceCertificate.SetCertificate()            
            throw new NotImplementedException();           
        }

        private static ServiceEndpoint SetMetadataBehavior(ServiceInfo serviceInfo, TowerHost host, Binding bindingInstance, ServiceEndpoint endPoint)
        {
            ServiceMetadataBehavior metadataBehavior = new ServiceMetadataBehavior();
            host.Description.Behaviors.Add(metadataBehavior);

            if (serviceInfo.BindingName != "System.ServiceModel.NetTcpBinding")
            {
                if (bindingInstance.Scheme == "https")
                {
                    metadataBehavior.HttpsGetEnabled = serviceInfo.MetadataEnabled;
                    metadataBehavior.HttpsGetUrl = new Uri(string.Format("{0}/mex", serviceInfo.Address));
                }
                else
                {
                    metadataBehavior.HttpGetEnabled = serviceInfo.MetadataEnabled;
                    metadataBehavior.HttpGetUrl = new Uri(string.Format("{0}/mex", serviceInfo.Address));
                }
            }
            else
            {
                endPoint = host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexTcpBinding(), serviceInfo.Address);
            }
            return endPoint;
        }

        private static ServiceInfo GetServiceInfo(ServiceInfo service)
        {
            ServiceInfo sInfo = new ServiceInfo();
            sInfo.Address = service.Address;
            sInfo.BindingName = service.BindingName;
            sInfo.IncludeExceptionDetails = service.IncludeExceptionDetails;
            sInfo.MetadataEnabled = service.MetadataEnabled;
            sInfo.TypeName = service.TypeName;
            return sInfo;
        }
    }
}

Bu sınıfta yapılan bazı kritik işler var ama kod epey dağınık halde diyebilirim. List<TowerHost> döndüren CreateTowerHost metodunun görevi oldukça basit. Parametre olarak gelen packFile bilgisini alıyor, JSON konfigurasyon içeriğini okuyor, tanımlı olan Assembly'ı yüklüyor ve konfigurasyon da belirtilen her bir servis tipi için birer TowerHost nesne örneği üretip listeye ekliyor. TowerHost tipini döndüren ikinci fonksiyon biraz daha karmaşık. Az biraz reflection ile parametre olarak gelen servis tipini örnekleyip, JSON dosyasından okunup ServiceInfo sınıfına alınan değerlere bakarak ayarlamalar yapmakta. Söz gelimi gerekli Binding tipini üretiyor, EndPoint oluşturuyor, Metadata Publishing değerlerini ve IncludeExceptionDetailsInFault bilgisini set ediyor. Metadata davranışının eklenmesi üzerine de halen çalışmaktayım. Nitekim NetTcpBinding söz konusu olduğunda IMetadataExchange arayüzünün kullanılarak bir publishing yapmak gerekiyor. Başka Binding tiplerinde farklı davranışlar sergilenmesi de gerekebilir. Her ne kadar if kullanmayı sevmesemde, POC olmasının verdiği rahatlıkla böyle bir kod parçası da eklemiş bulundum (:

Mesajların Yakalanması

Yazının ana konusu olan mesaj yakalama kısmı ise şu satırda gerçekleştiriliyor.

endPoint.EndpointBehaviors.Add(new EndpointMessageInspectorBehavior());

O anki EndPoint bilgisine, EndpointMessageInspectorBehavior tipinden bir nesne örneği davranış olarak ekleniyor. Yani Endpoint'e özel bir davranış ekleyerek genişletiyoruz. İçeriği basitçe aşağıdaki gibi.

using ING.ServiceFabric.Dispatchers;
using System.ServiceModel.Description;

namespace ING.ServiceFabric.EndpointBehaviors
{
    public class EndpointMessageInspectorBehavior
        : IEndpointBehavior
    {

        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
        {            
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MessageInspector());
        }

        public void Validate(ServiceEndpoint endpoint)
        {
        }
    }
}

Henüz sadece ApplyDispatchBehavior metodu kullanılmakta. Bu metoda gelen endpointDispatcher nesnesi üzerinden çalışma zamanında oluşan servis kanalına gidip araya giriyoruz. Bunu yaparken de MessageInspectors koleksiyonuna yeni bir dinleyici ekliyoruz.

 

using System;
using System.ServiceModel.Dispatcher;

namespace ING.ServiceFabric.Dispatchers
{
    public class MessageInspector
        :IDispatchMessageInspector
    {
        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
        {            
            Console.WriteLine("In AfterReceiveRequest");
            Console.WriteLine("\t{0}",request.ToString());

            return null;
        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            Console.WriteLine("In BeforeSendReply");
            Console.WriteLine("\t{0}",reply.ToString());
        }
    }
}

IDispatchMessageInspector arayüzünden türeyen MessageInspector sınıfının uyguladığı iki operasyon var. AfterReceiveRequest ve BeforeSendReply. AfterReceiveRequest ile servisin ilgili EndPoint'inden geçen mesajı yakalıyoruz. BeforeSendReply ise istemciye dönen mesaj gitmeden önce devreye girmekte. Ben sonuçları görmek için ilgili bilgileri Console'a basıyorum. Hedef pek tabii etkili bir Log mekanizması ile ilgili mesajları kayıt altına almak. Burada mesaj içeriğine bakılarak daha pek çok aksiyon da alınabilir gibime geliyor.

Aslında WCF'in çalışma zamanındaki işleyişini gösteren Microsoft dokümanının 19ncu sayfasındaki grafiğe bakınca olay daha kolay anlaşılıyor. Burada EndpointDispatcher'ın yaşamı boyunca enjekte edilebilecek bir çok enstrüman görülmekte.

Çalışma Zamanı

Unit Test projesi içerisinde pek çok test metodu var tabii ama benim için en güzel test ortamı tabii ki sevimsiz Console penceresi. Bu Console projelerinden birisi JSON dosyasından okuduğu bilgileri kullanarak servisleri ayağa kaldırırken diğeri istemci rolünü üstlenmekte ve örnek bir servise mesaj atıp cevap almakta. Host uygulamayı şu şekilde geliştirdim.

using ING.ServiceFabric;
using System;

namespace StandAloneHost
{
    class Program
    {
        static void Main(string[] args)
        {
            var packPath = "c:\\c\\ISV\\daexHost.json";
            var hostFactory = new TowerHostFactory();
            var hostList = hostFactory.CreateTowerHost(packPath);
            foreach (var host in hostList)
            {
                host.Open();
                Console.WriteLine("{0},{1}",host.Description.Name,host.State);
            }
            Console.WriteLine("{0} adet host dinlemede. Host'ları kapatmak için bir tuşa basınız",hostList.Count);
            Console.ReadLine();
            foreach (var host in hostList)
            {
                host.Close();
                Console.WriteLine("{0},{1}",host.Description.Name,host.State);
            }            
        }
    }
}

Tabii önce bu uygulamayı çalıştırıp ayağa kalkan bir servise ait WSDL içeriği geliyor mu bir bakmak ve bu içeriği kullanarak istemciye Proxy üretmek gerekiyordu. localhost:5000/daex/FraudCheckService adresinden yayın yapan WSHttpBinding bazlı servisi ayağa kaldırdığımda servise ulaşabildiğimi ve WSDL içeriğini yakalayabildiğimi gözlemledim. 

ve wsdl içeriği

Nihayetinde bir klasörde tutulan dll içerisindeki servisleri ayağa kaldırıp bunlara gelen istemci taleplerini ve dönen cevapları yakalayabilmeyi başardığımı ifade edebilirim. 

POC çalışması üzerinde halen devam etmekteyim. Yapmam gereken çok şey var. WCF'in standart konfigurasyon yapısı düşünüldüğünde çok daha hafif bir çatı kurmaya çalışıyorum. Sıradaki hedefler arasında Authentication ve Authorization gibi Cross Cutting'lerin çalışma zamanındaki servis yoluna nasıl enjekte edilebileceği konusu var.  Özetle yazılımcıların geliştireceği her bir servis kütüphanesinin kendi HostPack.json içeriğine sahip olacağı bir dünyanın peşinden koştuğumu ifade edebilirim. Sadece ihtiyaç duyduğumuz çalışma zamanı davranışlarının var olan standart WCF çatısından farklılaştırılarak entegre edildiği hafif bir çatı. İşin aslı burada daha yeni dünyaları denemek isterdim. Söz gelimi bu servis çatısını GO dilini kullanarak geliştirmek ve performansın gerçekten de söylendiği kadarı yüksek olup olmadığını görmek isterdim. Bakalım nelerle karşılaşacağım. POC üzerinde ilerledikçe pek çok sorunla karşılaşıyor ve çözmeye çalışırken yeni yeni şeyler öğreniyorum. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

GoLang - Google ProtoBuf Kullanımı

$
0
0

Merhaba Arkadaşlar,

Uygulama verilerini kullandığımız dile göre çeşitli şekillerde ifade edebiliriz. Eğer nesne yönelimli bir dil kullanıyorsak buradaki başrol oyuncumuz sınıflardır. Verinin nesnel olarak ifade edilişinde rol olan sınıf ve benzeri tipler, çalışma zamanında taşıdıkları içerikleri ile sürekli hareket halindedir. Bu hareket uygulamanın kendi alanında olabileceği gibi farklı programlar arasında da gerçekleşebilir. Veri, ağ üzerinde de hareket edebilir. Verinin bu şekilde dolaşımı sırasında belirli kriterlere göre serileştirilmesi de gerekebilir. Bu noktada karşımıza platform bağımsızlık, okunabilirlik, genişletilebilirlik, versiyonlama ve performans gibi kriterler çıkar.

Bir fabrikanın üretim hattına ait bilgileri barındıran bir veri modelini tasarladığınızı ve çalışma zamanı nesne topluluklarının belirli amaçlar doğrultusunda serileştirme işlemlerine tabii tutulacağını düşünelim. Uygulama çalışma zamanı tek bir nesne bilgisi(örneğin envanterdeki kategoriler) ile çalışabileceği gibi n sayıda nesne bilgisini tutan listelere de sahip olabilir. Hatta iç içe geçen veri kümeleri de kullanılabilir. Bu tip bir nesnel oluşum aynı ortamda çalışılırken çok soru işaretine neden olmasa da, veriyi servis olarak sunduğumuzda ya da X platformunun kullanımına açtığımızda şartlara uygun bir serileştirme modeli gerekir.

Çok yaygın olarak kullanılan XML, insan(aslında programcı diyelim) gözüyle kolayca okunabilecek veri yapılarının inşa edilebilmesine olanak sağlar. Asıl ortam içerisindeki veri tipine ait canlı örnekler kolayca XML'e dönüştürülebilir ve herhangibir platform tarafından kolaylıkla değerlendirilebilir. Ancak XML veri boyutunu arttıran bir yapıya sahiptir.

Microsoft .Net platformunda geliştirme yapıyor ve hatta sadece .Net tabanlı uygulamaların haberleştiği bir dünyada yaşıyorsak ideal seçim Binary formatta serileştirme yapmak olabilir. Binary serileştrime XML'e nazaran daha az yer tutuyor olsa da az önce belirttiğimiz üzere bu model kullanıldığında platform bağımsızlık avantajı kaybedilmektedir. Diğer yandan elimizde XML'den daha az yer tutan, programcı açısından yine okunabilir nitelikte olan JSON(JavaScript Object Notation) formatı da vardır.

Aslında veriyi basit string formatında da serileştirebiliriz. Veriye ait tüm içeriğin ardışıl olarak yazılması ile bu mümkündür. Lakin okunması çok zahmetli olacağı gibi şema yapısının(kaçıncı kolondan kaçıncıya kadar hangi alandır bilgisi gibi) da taraflara öğretilmesi gerekir. Mainframe'lerden çekilen metinsel verilerin bu tip formatlama teknikleri ile sunulduğu hizmetler halen daha vardır ancak gününümüz modern teknolojileri için XML, JSON, ProtoBuf gibi modeller ele alınmaktadır.

Bütün bunlar bir yana uzun süredir piyasada olan ve deneyimini hiçbir şekilde inkar edemeyeceğimiz Google tarafından geliştirilmiş bir serileştirme modeli daha var; Protobuf nam-ı diğer Protocol Buffers. Bu model, Google'ın kendi uygulamalarındaki sistemler arasında veri değiş tokuş işlemleri için ele aldığı bir serileştirme standardı. Var olan serileştirme tiplerine nazaran daha hızlı ve daha az yer tuttuğu ifade ediliyor. Binary formatlamayı kullanan protokol açık kaynak olarak sunulmakta. Yazının hazırlandığı tarih itibariyle C++, C#, GO, Java, Python, Javascript gibi diller tarafından da destekleniyor (Bu adresten protokol ile ilgili olarak daha detaylı bilgiye ulaşabilirsiniz)

Bu yazımızdaki amacımız ProtoBuf'ın nasıl kullanılabileceğini incelemek. Tasarlayacağımız basit bir veri yapısını, ProtoBuf standartlarında derleyecek ve GO dili ile yazılmış örnek bir kod parçasında denemeye çalışacağız. Önce sistemde gerekli hazırlıkları yapalım.

Hazırlıklar

İlk olarak sistemimize ProtoBuf derleyicisinin yüklenmesi gerekiyor. Bunun için şu adresten güncel sürümü yükleyerek ilerleyebiliriz. Ben Windows platformu için 32bitlik olan sıkıştırılmış içeriği indirdim. İçerisinde GO ile yazılıp derlenmiş protoc.exe isimli bir uygulama geldi. Bunu kullanarak proto uzantılı dosyaların derlenmesini sağlayacağız.  Bu yazımızdaki örneğimizi de GO dilini kullanarak geliştireceğiz. Bize GO tarafı için işlemlerimizi kolaylaştıracak bir de paket lazım. Bu paketi github'dan aşağıdaki komut satırı ile yükleyebiliriz.

go get -u github.com/golang/protobuf/protoc-gen-go

Bu arada sistemde Go ortamının kurulu olması gerektiğini de hatırlatalım.

.proto Dosyasının Hazırlanması

Öncelikle ProtoBuf ile kullanılacak verilerin şematik olarak inşa edilmesi gerekir. Bir başka deyişle veri yapısı tasarlanmalıdır. Sonrasında tasarlanan veri yapısı bir üretim işleminden geçirilir. Üretilen sınıfa verinin GO ile ifade edilmesini sağlayacak okuma operasyonları(getter, setter metodları) ve tip tanımlamaları otomatik olarak eklenir. Üretim işlemine geçmeden önce aşağıdaki örnek kod parçasını yazarak işlemlerimize devam edelim.

syntax="proto3";
package Southwind;

enum PlayerType{
	SEMI=0;
	PRO=1;
	MASTER=2;
}
	
message Player{
	string nickName=1;
	int32 playerId=2;
	PlayerType type=3;
	
	message Weapon{
		string name=4;
		string ability=5;
	}
	
	repeated Weapon weapons=6;
}

message Game{
	repeated Player player=1;
}

Proto içeriği aslında JSON formatında yazılmış ve belirli kurallara sahip olan bir yapıda tasarlanır. İlk satırda hangi ProtoBuf versiyonunun kullanılacağı ifade edilmektedir(Örneğimizde 3.0 sürümünü ele almaktayız)İçerik mutlaka bir paket adıyla tanımlanmalıdır(Southwind gibi) Veri tipleri birer message olarak ifade edilirler. Örnekte 3 mesaj ve bir enum sabiti yer almakta. Weapon, Player tipi, Player tipi de Game tipi içerisinde kullanılmaktadır. Dikkat edilmesi gereken nokta Weapon tipinin Player içerisinde dahili tip olarak tasarlanmış olmasıdır. PlayerType bir enum sabitidir(Evet yanlış görmediniz. Hani .Net servislerinde platform bağımsızlık adına kullanılmasını pek de önermediğimiz Enum sabiti kavramı burada yer almakta. İlginç değil mi? Aslında üretim işlemi sonrası o da GO'nun anlayacağı hale getirilecek)

Mesajlar içerisinde yer alan alanlara bir takım sayısal değerler atandığı görülmektedir. Bunlar serileştirme sonrası üretilecek içerikte ilgili alanların Unique olmalarını sağlayacak tanımlayıcılardır. repeated ile yazılmış ifadeler ilgili tiplerin birden fazla sayıda tekrar edileceğini belirtilir. Aynı ağaç içerisinde olmadığı sürece benzer id değerleri kullanılabilir. Örnek tasarımda string, int32 gibi veri tiplerine yer verilmiştir. Ancak daha pek çok veri tipi vardır(int64, double, float, bool vb) Detaylı bilgi için şu adrese bakabilirsiniz. Şimdi proto dosyasını derleyelim.

Derleme/Üretim

Yazılan Proto dosyasının kullanılması için bir üretim adımından geçirilmesi gerekmektedir. protoc aracını kullanarak yukarıda oluşturduğumuz dosyayı derleyebiliriz. Aynen aşağıdaki şekilde görüldüğü gibi.

Derleme sonrasında Southwind.pd.go isimli bir dosya oluşacaktır. Bu dosyanın içeriği aşağıdaki gibidir.

// Code generated by protoc-gen-go. DO NOT EDIT.
// source: Southwind.proto

/*
Package Southwind is a generated protocol buffer package.

It is generated from these files:
	Southwind.proto

It has these top-level messages:
	Player
	Game
*/
package Southwind

import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package

type PlayerType int32

const (
	PlayerType_SEMI   PlayerType = 0
	PlayerType_PRO    PlayerType = 1
	PlayerType_MASTER PlayerType = 2
)

var PlayerType_name = map[int32]string{
	0: "SEMI",
	1: "PRO",
	2: "MASTER",
}
var PlayerType_value = map[string]int32{
	"SEMI":   0,
	"PRO":    1,
	"MASTER": 2,
}

func (x PlayerType) String() string {
	return proto.EnumName(PlayerType_name, int32(x))
}
func (PlayerType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }

type Player struct {
	NickName string           `protobuf:"bytes,1,opt,name=nickName" json:"nickName,omitempty"`
	PlayerId int32            `protobuf:"varint,2,opt,name=playerId" json:"playerId,omitempty"`
	Type     PlayerType       `protobuf:"varint,3,opt,name=type,enum=Southwind.PlayerType" json:"type,omitempty"`
	Weapons  []*Player_Weapon `protobuf:"bytes,6,rep,name=weapons" json:"weapons,omitempty"`
}

func (m *Player) Reset()                    { *m = Player{} }
func (m *Player) String() string            { return proto.CompactTextString(m) }
func (*Player) ProtoMessage()               {}
func (*Player) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }

func (m *Player) GetNickName() string {
	if m != nil {
		return m.NickName
	}
	return ""
}

func (m *Player) GetPlayerId() int32 {
	if m != nil {
		return m.PlayerId
	}
	return 0
}

func (m *Player) GetType() PlayerType {
	if m != nil {
		return m.Type
	}
	return PlayerType_SEMI
}

func (m *Player) GetWeapons() []*Player_Weapon {
	if m != nil {
		return m.Weapons
	}
	return nil
}

type Player_Weapon struct {
	Name    string `protobuf:"bytes,4,opt,name=name" json:"name,omitempty"`
	Ability string `protobuf:"bytes,5,opt,name=ability" json:"ability,omitempty"`
}

func (m *Player_Weapon) Reset()                    { *m = Player_Weapon{} }
func (m *Player_Weapon) String() string            { return proto.CompactTextString(m) }
func (*Player_Weapon) ProtoMessage()               {}
func (*Player_Weapon) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} }

func (m *Player_Weapon) GetName() string {
	if m != nil {
		return m.Name
	}
	return ""
}

func (m *Player_Weapon) GetAbility() string {
	if m != nil {
		return m.Ability
	}
	return ""
}

type Game struct {
	Player []*Player `protobuf:"bytes,1,rep,name=player" json:"player,omitempty"`
}

func (m *Game) Reset()                    { *m = Game{} }
func (m *Game) String() string            { return proto.CompactTextString(m) }
func (*Game) ProtoMessage()               {}
func (*Game) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }

func (m *Game) GetPlayer() []*Player {
	if m != nil {
		return m.Player
	}
	return nil
}

func init() {
	proto.RegisterType((*Player)(nil), "Southwind.Player")
	proto.RegisterType((*Player_Weapon)(nil), "Southwind.Player.Weapon")
	proto.RegisterType((*Game)(nil), "Southwind.Game")
	proto.RegisterEnum("Southwind.PlayerType", PlayerType_name, PlayerType_value)
}

func init() { proto.RegisterFile("Southwind.proto", fileDescriptor0) }

var fileDescriptor0 = []byte{
	// 246 bytes of a gzipped FileDescriptorProto
	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0x41, 0x4b, 0xc3, 0x30,
	0x1c, 0xc5, 0xcd, 0x9a, 0xa5, 0xdb, 0x13, 0xb4, 0xfe, 0x41, 0x08, 0x3b, 0x95, 0x9d, 0x3a, 0x85,
	0x82, 0x15, 0xbc, 0x7b, 0x18, 0xb2, 0xc3, 0x74, 0xa4, 0x03, 0xcf, 0x9d, 0x0b, 0x58, 0x9c, 0x6d,
	0x98, 0x95, 0xd1, 0x4f, 0xea, 0xd7, 0x91, 0xfe, 0xbb, 0x75, 0x87, 0xde, 0xf2, 0x5e, 0x7e, 0xe4,
	0xfd, 0x08, 0xae, 0xd3, 0xf2, 0xb7, 0xfa, 0x3c, 0xe4, 0xc5, 0x36, 0x76, 0xfb, 0xb2, 0x2a, 0x69,
	0xdc, 0x15, 0xd3, 0x3f, 0x01, 0xb5, 0xda, 0x65, 0xb5, 0xdd, 0xd3, 0x04, 0xa3, 0x22, 0xff, 0xf8,
	0x7a, 0xcd, 0xbe, 0xad, 0x16, 0xa1, 0x88, 0xc6, 0xa6, 0xcb, 0xcd, 0x9d, 0x63, 0x6a, 0xb1, 0xd5,
	0x83, 0x50, 0x44, 0x43, 0xd3, 0x65, 0x9a, 0x41, 0x56, 0xb5, 0xb3, 0xda, 0x0b, 0x45, 0x74, 0x95,
	0xdc, 0xc6, 0xe7, 0xb5, 0xf6, 0xe1, 0x75, 0xed, 0xac, 0x61, 0x84, 0x12, 0xf8, 0x07, 0x9b, 0xb9,
	0xb2, 0xf8, 0xd1, 0x2a, 0xf4, 0xa2, 0xcb, 0x44, 0xf7, 0xe8, 0xf8, 0x9d, 0x01, 0x73, 0x02, 0x27,
	0x4f, 0x50, 0x6d, 0x45, 0x04, 0x59, 0x34, 0x72, 0x92, 0xe5, 0xf8, 0x4c, 0x1a, 0x7e, 0xb6, 0xc9,
	0x77, 0x79, 0x55, 0xeb, 0x21, 0xd7, 0xa7, 0x38, 0x7d, 0x80, 0x7c, 0x69, 0x88, 0x19, 0x54, 0xab,
	0xaa, 0x05, 0x4f, 0xde, 0xf4, 0x26, 0xcd, 0x11, 0xb8, 0xbb, 0x07, 0xce, 0xca, 0x34, 0x82, 0x4c,
	0xe7, 0xcb, 0x45, 0x70, 0x41, 0x3e, 0xbc, 0x95, 0x79, 0x0b, 0x04, 0x01, 0x6a, 0xf9, 0x9c, 0xae,
	0xe7, 0x26, 0x18, 0x6c, 0x14, 0xff, 0xe5, 0xe3, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7c, 0x25,
	0x4e, 0xe1, 0x5e, 0x01, 0x00, 0x00,
}

Aslında kod okunmaya ve yorumlanmaya değer. (Bir GO paketi söz konusu ve bu tip kodları okuyup anlamaya çalışmak GO öğrenenler için önemli bir mevzu) Yazılan proto uzantılı dosyadaki tanımlamalara göre bir üretim gerçekleştirilmiştir. Yorum satırlarında dosyanın neyle üretildiği, değiştirilmemesi gerektiği, hangi mesajları sunduğu gibi bilgiler belirtilir. proto, fmt ve math gibi GO paketlerini kullanır. PlayerType ismiyle tanımlanan enum sabitinin koda bir değişmez(Constant) olarak aktarıldığında dikkat edelim. Aslında GO için bu enum sabiti int32 tipinden bir değişkendir. Bununla birlikte enum sabitinin içeriğine isimle(name) veya değerle(value) ulaşabilmek için bir map değişkeni(PlayerType_name,PlayerType_value) tanımlandığı görülür. Kodda enum sabiti olmak üzere diğer mesajlar için metodlar yazıldığına da dikkat edelim. Player ve Game birer yapı(struct) olarak oluşturulmuşlardır. Alanların değerlerini okumak için Get kelimesi ile başlayan metodlar vardır. Oyuncunun sahip olabileceği silahlar için Player tipinden bir slice değişkeni yer almaktadır. Oyun sahasındaki oyuncular için de benzer şekilde Game yapısı içinde Player tipinden bir slice değişkenini işaret eden pointer'a yer verilmiştir. Bu bir GO uygulamasına ilave edilerek kullanılacak bir paket olduğundan pek tabii main fonksiyonu içermez. Ancak başlangıçta ilgili mesaj tiplerinin proto katmanına enjekte edilmesi için bir takım fonksiyon çağrıları gerçekleşir.

Ana Uygulama

Gelelim bu içeriği uygulamada nasıl kullanabileceğimize. Öncelikle oluşan Southwind.pb.go dosyasını paket olarak konuşlandırmamız lazım. Sistemdeki GOPATH bildirimine göre uygun bir klasör içerisine atabiliriz. Ben GOPATH'in işaret ettiği src klasöründe aşağıdaki gibi bir yapılandırma oluşturdum. Southwind.pd.go dosyasının Southwind isimli bir klasörde olması mühim. Aksi halde kod ilgili paketin yerini bulamayacaktır.

C->Go Works
----Samples
--------------Src
------------------Message
----------------------------Southwind
----------------------------------------Southwind.pb.go

Şimdi istediğimi lokasyondan bu paketi kullanabiliriz. İşte örnek bir kod parçası.

package main

import (
	"fmt"

	data "message/Southwind"

	"github.com/golang/protobuf/proto"
)

func main() {
	taverna := data.Game{Player: []*data.Player{
		{
			NickName: "Leksar",
			PlayerId: 10,
			Type:     data.PlayerType_SEMI,
			Weapons: []*data.Player_Weapon{
				{Name: "Sword", Ability: "High level sword"},
				{Name: "Machine Gun", Ability: "7.65mm"},
			},
		},
		{
			NickName: "Valira",
			PlayerId: 12,
			Type:     data.PlayerType_SEMI,
			Weapons: []*data.Player_Weapon{
				{Name: "Poison Bottle", Ability: "Dangeres green"},
			},
		},
	},
	}
	sData, err := proto.Marshal(&taverna)
	if err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println(sData)
		fmt.Println(string(sData))
	}

	dsData := &data.Game{}
	err = proto.Unmarshal(sData, dsData)
	if err != nil {
		fmt.Println(err.Error())
	} else {
		for _, p := range dsData.Player {
			fmt.Println(p.NickName)
			for _, w := range p.Weapons {
				fmt.Printf("\t%s\t%s\n", w.Name, w.Ability)
			}
		}
	}
}

Öncelikle kodda neler yaptığımız bir bakalım. ProtoBuf formatında serileştirme ve ters serileştirme işlemleri için github.com/golang/protobuf/proto paketinde yer alan proto tipinin Marshal ve Unmarshal metodlarından yararlanılıyor. taverna isimli değişken içerisinde iki oyuncu ve bu oyuncuların silahlarına ait test verileri yer almakta. Marshal metodu ile serileştirilen içeriği hem byte array hem de string tipine dönüştürülmüş olarak ekrana bastırmaktayız. Sonrasında da serileştirilmiş bu veri içeriğinden yeni bir Game örneğine ters serileştirme yaparak elde edilen verileri yazdırmaktayız. Çalışma zamanı sonuçları aşağıdaki ekran görüntüsündeki gibidir.

Tabii ki serileştirilen içerik fiziki bir dosyaya çıkılabilir veya ağ üzerinden bir kanala yazdırılabilir. Daha az yer kapladığı ortada. Yine de gerçek benchmark testleri ve farklı serileştirme formatları ile karşılaştırılması için interneti dolaşmakta yarar var. Üretilen serileştirilmiş içeriğe bakıldığında ise sadece verinin tutulduğu görülmektedir. Tahmin edeceğiniz üzere verinin şeması paket olarak eklediğimiz Southwind.pd.go içerisinde yer alıyor. Dolayısıyla Marshal ve Unmarsal işlemlerinde bu paketten yararlanılmakta.

Böylece geldik bir GoLang maceramızın daha sonuna. Bu yazımızda Google'ın geliştirdiği serileştirme protokolü Proto Buffer'ın bir Go uygulamasında nasıl kullanılabileceğini incelemeye çalıştık. Size tavsiyem diğer dil paketlerini de işin içine katarak geliştirme yapmaya çalışmanız olacaktır. Bir başka yazımızda görüşünceye dek hepinize mutlu günler dilerim.

GoLang - Unit Test Yazmak

$
0
0

Merhaba Arkadaşlar,

Aranızda hala birim test(Unit Test) yazmayan/yazmamış olan var mı? diyerek konuya giriş yapmak istiyorum. Yazdığımız atomik fonksiyonelliklerin taşınan ortamlarda başımızı ağrıtmasını istemiyorsak birim testlerini mutlaka yazmalıyız. Üstelik iyi yazmalıyız. Belki birim testler uygulama geliştirme süresini uzatabilirler ancak uzun vadede kalp krizi geçirme riskini de azaltırlar. Üstelik test senaryoları sayesinde gerçekten ne yapmak istediğimizin farkında olarak da hareket edebiliriz. Eğer test güdümlü yaklaşımla(Test Driven Development) ilerliyorsak bilinçli olarak yaptırılan hata sonrası kodun çalışır hale getirilmesi ve iyileştirilmesi(Refactoring) de önemli kazanımlarımızdır(Red-Green-Blue konusuna bir bakın) En önemlisi de beklenen testleri başarılı bir şekilde aşmış temiz bir kodun ortaya çıkmasıdır.

Neredeyse her programlama dilinin Unit Test yazılmasına yönelik imkanları vardır. Geliştirme IDE'lerinde pek çok kolaylık bulunmaktadır. Çoğu ortam zaten standart kütüphaneler veya paketlerler ile bizleri olabildiğince Unit Test yazmaya yönlendirir. GO tarafında bu iş için dahili paketlerden olan testing kullanılmakta. Pek tabii github üzerinden bulabileceğiniz farklı test paketleri de mevcut. Bu kısa yazımızda basit bir Unit Test'in nasıl yazılabileceğini incelemeye çalışacağız.

Önce Anlamsız İki Fonksiyon

İşe ilk olarak anlamsız iki fonksiyon içeren aşağıdaki kod parçasını yazarak başlayabiliriz (Amacımız GO tarafında Unit Test'lerin nasıl yazıldığını kurcalamak) Operations.go içerisinde daire alanı hesaplayan ve n sayıda float32 tipinden sayının toplamını bulan birer fonksiyon(Variadic) bulunmaktadır.

package operations

import (
	"math"
)

func CircleSpace(r float32) float32 {
	return math.Pi * (r * r)
}

func Sum(numbers ...int) int {
	var total int = 0
	for _, n := range numbers {
		total += n
	}
	return total
}

Tasarladığımız paketteki CircleSpace ve Sum isimli operasyonlar için birer test metodu yazalım.

Test Paketinin Yazılması

GO'nun alışageldiğimiz kurallarına göre bir paket içerisinde yer alan fonksiyonların testini içeren ayrı bir dosyanın _test şeklinde isimlendirilerek oluşturulması gerektiğini söylesem sanıyorum yadırgamazsınız. Bu bana çok mantıklı geliyor. Paketlerin adlarına baktığımızda kimin test dosyası olduğunu görmemiz kolay. Anlamsal bir bütünlük oluşuyor ve herkes aynı stilde test dosya adı vermek durumunda. Güzel bir standart oluşturulduğu kesin. Örneğimize göre bu dosyanın adı operations_test.go şeklinde olmalı. Farklı bir isim verip test etmek istersek aşağıdaki gibi bir sonuçla karşılaşma ihtimalimiz oldukça yüksek.

Gelelim operations_test.go içeriğine.

package operations

import (
	"testing"
)

func TestCircleSpace(t *testing.T) {
	//var expected float32 = 314.159271
	var expected float32 = 314.15
	calculated := CircleSpace(10)
	if expected != calculated {
		t.Errorf("Test Fail : Calculated [%f]\tExpected [%f]\n", calculated, expected)
	}
}

func TestSum(t *testing.T) {
	//expected := 13
	expected := -1
	calculated := Sum(3, 4, 1, 5)
	if expected != calculated {
		t.Errorf("Test Fail : Calculated [%d]\tExptected [%d]\n", calculated, expected)
	}
}

Öncelikle test paketinin adının test edeceğimiz paket ile aynı olduğuna dikkat edelim. Diğer yandan testing paketini de import ediyoruz. TestCircleSpace ve TestSum isimli fonksiyonların testing.T tipinden olan değişken ile test ortamına log bildiriminde bulunmamız mümkün ki bunu Errorf fonksiyon çağrıları ile sağlamaktayız. Test akışı son derece pratik. Beklenen ve hesaplanan değerleri alıp karşılaştırıyoruz. Eğer aynı değillerse testin hatalı sonlandığını ifade edecek şekilde log çıktısı bırakıyoruz. Hepsi bu.

Sonuçlar

İlk olarak senaryomuzu beklenmeyen sonuçlar için test edelim. Bu durumda iki fonksiyon testinin de Fail olmasını bekliyoruz. LiteIDE üzerinden Test seçeneği ile veya komut satırından go test ile gerçekleştirilen işlemlerin sonucu aşağıdaki ekran görüntüsündeki gibi olacaktır.

Komut satırından test yaparken o klasörde sadece go test yazmamız yeterlidir. Test dosyasının adını vermeye gerek yoktur. _Test uzantısı onu ele veriyor diyebiliriz. Buna göre bir klasörde n sayıda test dosyası varsa tamamını tek seferde çalıştırma imkanımız da olabilir.

Görüldüğü gibi elde edilen sonuçlar istenen sonuçlar olmamış ve Fail bildirimleri alınmıştır. Şimdi beklediğimiz değerlerin yorum satırlarını kaldıralım. Bu durumda her iki testte başarılı olmalı. Aynen aşağıdaki ekran görüntüsünde olduğu gibi.

Dikkat edileceği üzere GO tarafında birim testler yazmak oldukça kolay. O zaman bundan sonraki ilk geliştirmenizde elinizdeki atomikleri önce TDD ilkelerine uyarak yazmaya gayret edin. Hatta FizzBuzz kod katasını baz alıp GO ile yazmayı deneyebilirsiniz. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

GoLang - Redis ile Anlaşmak

$
0
0

Merhaba Arkadaşlar,

Bir haziran gecesiydi. Dışarıda hava nemli ve sıcaktı. Bir süre önce başlayan yağmurun sesi çalışma odamın pencersinden kulaklarıma tatlı tatlı geliyordu. Biraz da toprak kokusu vardı tabii. Evde el ayak çekilmiş sakin bir ortam oluşmuştu. Bol kafein dolu bardağım elimde internetten bir şeyler okuyordum. İnsanlar çıldırmıştı. Javascript Framework'ler, yapay zeka'lar, react'ler, cordova'lar, .net core'lar, sanayi 4.0'lar, tesla'lar ve daha niceleri. Eskisinden daha hızlı bir şekilde geride kaldığımı hissediyordum. Sanırım sonum örümcek adamın amcası gibi evde bozuk ampülü tamir edip gazetede iş arayan ama bulamayan biri gibi olacaktı. Ama direniyordum. Önce Ruby, sonra Pyhton ve derken GO. Amatör seviyede başlamış biraz ilerlemiştim. Kendime bir çalışma döngüsü kurmuştum. Bir süre Ruby bakıyor, sonra Pyhton bakıyor, sonra GO bakıyor sonra bu döngüyü tekrar başa sarıyordum. GO'nun ikinci iterasyonundaydım.

Bu sefer elimde oldukça güzel de bir Türkçe kaynak vardı(Murat Özalp'in Go Programlama isimli kitabını şiddetle tavsiye ederim) Kitabın son sayfalarına gelmiştim. Döngünün bir sonraki adımına geçmeden önce biraz daha uygulama yapmam gerekiyordu. Nitekim dile hakim olabilmek için bol bol kod yazmam şarttı. Mesela GoLangWeekly' de yayımlanan başlıklara göz gezdirdiğimde çoğunu anlamakta güçlük çekiyordum. Bu yüzden seçmece ilerlemek durumundaydım. Aslında programlama dilini ufak ufak kavramaya başlamıştım ama örnek senaryolar işleterek ilerlemem gerektiğini de biliyordum. Gelişebilmem için bu şarttı. Bu karman çorman düşünceler altında devam ederken aklıma bir pratik geldi. Eskiden .Net tarafında kullandığım NoSQL sistemlerinden olan Redis ile ilgili bir şeyler yapmak istiyordum.

2014 yılında onu kısaca incelemeye çalışmış ve bir şeyler karalamıştım. Hatta Redis'in genel özelliklerini oradan okuyarak hatırlamaya çalıştım. Bu yazı için kısaca özetlemek gerekirse bellekte çalışan, key-value tipinde ve dağıtık yapıda sunulabilen bir NoSQL sistemi olduğunu ifade edebiliriz. Redis'in key-value tipinde bir veri tabanı olması bizi yanıltmamalıdır. key'ler string olsa da value olarak kullanabileceğimiz beş temel tip bulunur. string, hash, list, set ve sortedSet. Dolayısıyla oldukça geniş bir veri yapısını kullanma şansımız vardır.

Zaman hızla geçmiş bir çok şey de değişmişti tabii. Bir süredir gözde dillerimden birisi olan Go'da Redis'i nasıl kullanabileceğimi merak ediyordum. O gece Windows makinesinde çalışmaktaydım. Aradan geçen zamana rağmen Redis'in Windows sürümü halen çalışmaktaydı. Yeni Redis versiyonlarına göre yeni sürümleri de yayınlanıyordu. Dolayısıyla siz de Windows sürümündeyseniz şu adresten uygun MSI ile yüklemeyi yapabilir ve yazının kalanında bana eşlik edebilirsiniz.

Komut Satırında Kısa Bir Tur

Yükleme işlemi sonrası hemen komut satırına geçtim ve redis-server.exe' yi kurulumun yapıldığı lokasyondan çalıştırdım. Sonra bir kaç deneme yapmak için redis-client ile açılan kısma geçtim. Redis varsayılan olarak yerel makinede 6379 numaralı porttan hizmet veren bir servis. Tabii farklı node'lar söz konusu olursa farklı port'lar ile de haberleşebilmemiz mümkün. Örneğimiz şimdilik tek bir sunucu örneği üzerinden çalışıyor.

Saatler ilerlerken konunun verdiği heyecanla komut satırından bir kaç deneme yapmayı da ihmal etmedim.

İlk olarak redis ile ping-pong oynadım :) Siz ping yazdığınızda O da PONG diyorsa bu konuşabildiğiniz anlamına gelir. Ardından ilk iş players:reksar isimli bir key oluşturmak oldu. Değeri ise JSON formatında bir içerikten ibaretti. get komutunu kullanarak belleğe atılan bu key içeriğini okuyabiliriz.

Sonrasında hmset ile bir hash üretmeye çalıştım. hmset ile bir key için n sayıda değer içerecek alanlar(fields) tanımlayabiliriz. language:go isimli key bu şekilde yazıldı. Oluştururken bir field bir value, bir field bir value şeklinde ilerlemek gerekiyor. Daha sonra hmget ile language:go içeriğini almaya çalıştım. Ancak ilk denemede hata yaptım. Nitekim bu komut ile bir hash içerisindeki belli bir alanın değerini almaktayız. type alanının değerini okuduktan sonra tüm alanların içeriğini hgetall komutu ile elde etmeyi başardım. Dilersek sadece key değerlerini de yakalayabiliriz ki hkeys bu noktada devreye girmekte. 

İlerleyen satırlarda basit bir liste oluşturup ona elemanlar eklemeyi ve tüm içeriği ekrana yazdırmayı denedim. Bu amaçla lpush ve lrange isimli komutlardan yararlandım. Bu şekilde komut satırından çalışmaya da devam edilebilirdi tabii ama hedefim bunu Go ile gerçekleştirmekti.

GoLang Zamanı

Diğer platformlarda olduğu gibi Redis'i GoLang ile kullanmak için harici bir paketten destek almam işleri kolaylaştırıyor. Aslında bu şart değil. Sonuçta servis bazlı bir veritabanı motoru söz konusu ama şimdilik paket ile ilerlemek benim için daha iyi. Bir kaç araştırma ve blog yazısından sonra şu adresten yayınlanan bir go paketi buldum. LiteIDE'nin Get seçeneği ile ya da komut satırından ilgili paketi kolaylıkla sistemimize alabiliriz.

go get github.com/mediocregopher/radix.v2

Önce Basit Bir String Ekleyelim

Gelelim örnek kod parçalarına. İlk olarak basit string türünden bir veri eklemeye çalıştım. Value olarak da JSON içeriği kullanmaya karar verdim.

package main

import (
	"fmt"

	"github.com/mediocregopher/radix.v2/redis"
)

func main() {
	AddLudwig()
}

func AddLudwig() {
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		fmt.Println(err.Error())
	} else {
		defer conn.Close()
		pong := conn.Cmd("ping")
		fmt.Println(pong.String())

		response := conn.Cmd("set", "players:ludwig", "{\"nick\":ludwig,\"genre\":classic,\"SongCount\":98}")
		if response.Err != nil {
			fmt.Println(response.Err)
		}
		fmt.Println(response.String())
	}
}

Fonksiyon redis tipinin Dial metodu ile başlıyor. TCP protokolü ile localhost üzerindeki 6379 nolu porta bağlanacağımızı ifade ediyoruz. Yani Redis sunucusuna. Eğer bağlanabiliyorsak(ki err nesnesi nil ise bağlanıyoruz diyebiliriz)önce ping pong oynuyor ve sonrasında players:ludwig isimli bir key gönderiyoruz. Değer olarak da JSON formatında bir içerik söz konusu. Ludwig'in takma adını, bestelediği şarkı türünü ve toplam parça sayısını tutan saçma bir verimiz var. Bu kodda en kritik nokta az önce terminalden yazdığımız redis komutlarının Cmd metodunda kullanılması. İlk çağrıda ping diğerinde ise set komutunu göndermekteyiz. defer ettiğimiz Close metodu fonksiyondan çıkarken redis bağlantısını kapatacak. Çalışma zamanı sonuçlarını aşağıda görebilirsiniz. Koddan eklediğimiz veriyi redis komut satırından da elde edebildik.

Birde Hash Üretip Okuyalım

Kodları biraz daha ilerletmeye çalıştım. Acaba bir Hash nasıl üretilebilirdi ve hatta alanlarının değerlerini kod tarafından nasıl okuyabilirdim? Aşağıdaki gibi bir fonksiyon işime yarayacaktı.

func AddAndReadHash() {
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		fmt.Println(err.Error())
	} else {
		defer conn.Close()
		response := conn.Cmd("HMSET", "card:93", "nickName", "murlock", "greetings", "I'am ready, I'am not ready", "price", 5, "attack", 4, "defense", 4, "owner", "shammon")
		if response.Err != nil {
			fmt.Println(response.Err)
		}
		fmt.Println(response.String())
		read, _ := conn.Cmd("HGETALL", "card:93").Map()
		for k, v := range read {
			fmt.Printf("%s\t%s\n", k, v)
		}
	}
}

Bu kez HMSET komutunu kullanarak bir hash üretiliyor. card:93 olarak belirtilmiş bir key söz konusu. Bu verinin nickName, greetings, price, attack, defense ve owner isimli alanları bulunuyor. Bir takım test verileri koyarak redis'e gönderiyoruz. Okuma kısmında ise HGETALL komutunun çağırılması söz konusu. Ancak dikkat çekici nokta bu seferki çağrım sonrası Map isimli metodun kullanılması. Bu sayede hash içerisindeki key ve value bilgilerini dolaşabileceğimiz map türünden bir nesneyi elde edebiliyoruz. Sonrasında range fonksiyonunu kullanarak ilgili key:value çiftlerini ekrana yazdırıyoruz. İşi eğlenceli hale getirmek için farklı bir şekilde renklendirdiğim komut satırının çalışma zamanı çıktısı aşağıdaki gibi.

Go Tarafında Veriyi Yapı(Struct) Olarak Ele Alsak

Lakin bir şeyler eksik gibi. Sakladığımız verinin kendisini belki de Go tarafından başka şekilde ifade edebiliriz. Örneğin oyun kartlarına ait bilgileri taşıyan bir yapı(struct) tasarlayıp onu bu senaryoda ele almamız daha doğru olabilir. O zaman kodları aşağıdaki hale getirerek yolumuza devam edelim.

package main

import (
	"fmt"
	"strconv"

	"github.com/mediocregopher/radix.v2/redis"
)

func main() {
	aragorn := Card{NickName: "Aragorn", Greetings: "Well Met!", Price: 9, Attack: 10, Defense: 12, Owner: "Luktar"}
	AddCard(aragorn, "card:45")
	card := GetCard("card:45")
	card.ToString()
}

func AddCard(card Card, id string) {
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		fmt.Println(err.Error())
	} else {
		defer conn.Close()

		response := conn.Cmd("HMSET", id, "nickName", card.NickName, "greetings", card.Greetings, "price", card.Price, "attack", card.Attack, "defense", card.Defense, "owner", card.Owner)
		if response.Err != nil {
			fmt.Println(response.Err)
		}
		fmt.Println(response.String())
	}
}

func GetCard(id string) *Card {
	card := new(Card)
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		fmt.Println(err.Error())
	} else {
		defer conn.Close()
		response, _ := conn.Cmd("HGETALL", id).Map()
		card.NickName = response["nickName"]
		card.Greetings = response["greetings"]
		card.Owner = response["owner"]
		card.Attack, _ = strconv.Atoi(response["attack"])
		card.Price, _ = strconv.Atoi(response["price"])
		card.Defense, _ = strconv.Atoi(response["defense"])
	}
	return card
}

func (card *Card) ToString() {
	fmt.Printf("Nickname:%s\n", card.NickName)
	fmt.Printf("Greetings:%s\n", card.Greetings)
	fmt.Printf("Owner:%s\n", card.Owner)
	fmt.Printf("Price:%d\n", card.Price)
	fmt.Printf("Attack:%d\n", card.Price)
	fmt.Printf("Defense:%d\n", card.Defense)
}

type Card struct {
	NickName  string
	Greetings string
	Price     int
	Attack    int
	Defense   int
	Owner     string
}

İlk olarak Card isimli bir struct tasarladığımızı söyleyelim. İçerisinde Redis'teki Hash içeriğine karşılık gelen alanları barındırmakta. AddCard fonksiyonu parametre olarak gelen bir Card nesnesinin içeriğini kullanarak Redis üzerinde yeni bir Hash oluşturma işini üstleniyor. Fonksiyonun bir önceki örnekteki ekleme operasyonundan tek farkı değerleri almak için parametre olarak gelen Card örneğini kullanılması. card:45 benzeri key değeri için de id isimli bir parametre kullanmaktayız. GetCard metodu id bilgisine göre Redis üzerinden bir Card içeriğini çekmek üzere tasarlanmış durumda. Cmd üzerinden gidilen Map fonksiyonu ile redis tarafında tutulan içeriği almaktayız. Gelen içerikteki değerler string içerikte olacaktır. Bu nedenle strconv paketinden gerekli dönüştürme operasyonlarını kullanmamız gerekebilir. Card tipinin Price, Attack ve Defense gibi alanları int tipinden olduğu için Atoi tür dönüştürme metodundan yararlandık. Bulunan içeriğe göre değerleri atanan Card nesnesi olarak geriye döndürüyoruz. Card yapısına uygulanan ToString metodu ile de içeriği ekrana bastırıyoruz. İşte örnek çalışma zamanı çıktısı.

Pek tabii olmayan bir key değerini almaya çalışırsak içeriği boş bir yapı örneği elde ederiz. Söz gelimi card:100 sistemimizde bulunmuyor. Bu anahtar için program çıktısı aşağıdaki gibi olacaktır.

Demek ki

Teorimiz oldukça basit. Redis komutlarını çalıştırmak için paketin Cmd fonksiyonundan yararlanılabilir. Basit bir string kullanımından hash, list, set, sortedSet gibi veri yapılarına kadar gidilebilir. Dolayısıyla temel CRUD operasyonlarını basitçe ele alabiliriz. Bu noktada Redis'in komutlarını incelemekte ve detaylı bir şekilde öğrenmekte yarar olduğu kanısındayım. Go ile olan entegrasyonda işleri kolaylaştırmak ve programatik alanda veri türlerini tip bazında ifade etmek için yapılardan(struct) ve bu yapılara uygulanan yardımcı metodlardan yararlanılabilir. Yazımızda geçen örneği daha da geliştirmek elinizde. Söz gelimi Go'nun web programlama kabiliyetlerini baz alarak MVC(Model View Controller) yapısına uygun bir programı Redis ile çalışacak şekilde tasarlayabilirsiniz. Ya da arayüzle uğraşmak istemiyorsanız bir REST servis geliştirip temel veri operasyonlarının bu servis üzerinden gerçekleştirilmesini deneyebilirsiniz. Denemeye değer. Ben şimdilik dinlenmeye çekileceğim ama konuyu buradan alıp ileriye taşımak sizin elinizde. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Python - Flask ile Basit Bir Web Uygulaması Geliştirmek

$
0
0

Merhaba Arkadaşlar,

Yazıyı yazdığım şu yaz gününde hava epey sıcak. İstanbul'da öğle saatlerinde 39 dereceyi gördük. Güney tarafında yaşayan bir kaç yakın arkadaşımdan 48 dereceli rakamları duyduktan sonra ise halimize şükredelim dedim. Açtım Python kitabımı, çalışmaya devam ettim.

Bir süre önce GoLang tarafında basit web uygulamalarının nasıl geliştirilebileceğini incelemeye çalışmıştım. Daha önceden de Python tarafında Flask paketinden yararlanarak REST tabanlı bir servis geliştirmeyi denemiştim. Tabii servis bir yanaaaa web uygulamaları bir yana. Son kullanıcı çoğunlukla görsel bir şeyler bekliyor. Python camiasında Djiango Framework bu anlamda daha popüler tabii ama henüz onu inceleme fırsatım olmadı. Flask oldukça hafif bir framework olarak karşımıza çıkmakta. Bende ondan faydalanarak basit bir web uygulaması nasıl yapılabilir inceleyeyim dedim.

Yazımızın ilerleyen kısımlarında Flask paketi ve template tipinden HTML şablonlarından yararlanarak oldukça basit bir Web uygulaması geliştirmeye çalışacağız. Yazacağımız web uygulaması temel olarak iki sayının toplamını hesaplayan bir arabirim sunacak ancak öğreneceğimiz temel esaslar daha gelişmiş web uygulamalarının tasarlanmasında da kullanılmakta. Özellikle web sayfası içeriği ve Python tarafında web sunucu görevini üstlenecek kodlar ile arada kurmaya çalışacağımı bir iletişim söz konusu olacak. Dilerseniz vakit kaybetmeden uygulamamızı geliştirmeye başlayalım.

Klasör Yapısı

İşe ilk olarak klasör yapısını anlatarak başlamalıyım. Aşağıdaki kurguyu ele alabiliriz.

/hello_flask.py
/templates/
               basepage.html
               einstein.html
               result.html
/static/
              main.css

hello_flask isimli python sayfamızda http taleplerini yönledirme işlemlerini gerçekleştireceğiz. templates klasöründe bazı basit HTML içerikleri yer alıyor. Bunlar şablon sayfalarımız olarak düşünülebilirler. basepage diğerleri için ata şablon görevini üstlenmekte. Einstein.html içerisinde toplama işlemi için bir içerik sunarken işlem sonuçlarını result.html sayfasından sunmayı planlıyoruz. Tasarldığımız kurguda önemli olan noktalardan birisi HTML sayfaları ile Flask çatısının iletişimi. Şimdi şablon sayfalarını yakından tanımaya çalışalım.

Template Sayfaları

Web uygulamamızda Jinja2 standartlarında içerik sunacak HTML sayfaları bulunuyor. Bu HTML sayfaları Flask tarafı ile iletişim halinde olacak. Aslında olay {{ ve }} arasındaki kısımlarda gerçekleşmekte. Burada kullanılan değişken adları python tarafında da değerlendirilebiliyor. Bir başka deyişe python kod tarafı ile statik HTML sayfaları arasındaki veri alışverişinde bu söz dizimi değer bulacak.

basepage.html

BasePage.html .Net tarafında Web uygulaması geliştiren arkadaşlarımızca Master Page olarak düşünebilir. Kısaca diğer sayfalar için tepede yer alan bir ana şablon vazifesi görmekte.

<!doctype html><html><head><title>{{ page_title }}</title><link rel="stylesheet" href="static/main.css"/></head><body>
        {% block body %}
        {% endblock %}</body></html>

Dikkat edileceği üzere title elementinde page_title, body kısmında ise block body ve endblock isimli tanımlamalar mevcut. Bu tanımlamaların {{ ve }} arasında olduklarına dikkat edelim. Alt sayfaların block body ve endblock isimli kısımlar içerisine yerleşeceğini de söyleyebiliriz.

einstein.html

Base Page'den türeyen Einstein.html temel bir toplama operasyonunu üstlenmekte. İlk olarak extends isimli bir tanımlama ile başladığını görebiliriz. Burada basepage.html'den türetildiğini belirtiyoruz. block body ve endblock kısımları arasında bir takım tanımlamalar mevcut. h2 boyutlarında bir başlık belirttikten sonra POST metodunu kullanan bir form yer alıyor.

{% extends 'basepage.html' %}
{% block body %}<h2>{{ page_title }}</h2><form method='POST' action='/sum'><table><p>Sum of two values</p><tr><td>First Value:</td><td><input name="firstValue" type="TEXT" width="10"</td></tr><tr><td>Second Value:</td><td><input name="secondValue" type="TEXT" width="10"</td></tr></table><p><input value="Calculate" type="SUBMIT"></p></form>

{% endblock %}

Pek tabii form elementinin method ve action niteliklerine atanan değerler oldukça kıymetli. Calculate isimli butona basıldığında gerçekleşecek Submit işlemi sonrası http://localhost:5000/sum adresine gidilecek. Bu işlem HTTP protokolünün POST metoduna göre gerçekleşecek. Form üzerinde iki tane text kontrolü var. Bunlar toplama işlemine dahil edilecek değişkenleri aldığımız kontroller.

result.html

Toplama işleminin sonucunu göstereceğimiz HTML şablonu ise aşağıdaki içeriğe sahip.

{% extends "basepage.html" %}
{% block body %}<h2>{{ page_title }}</h2><p>You submitted the following data:</p><table><tr><td>First:</td><td>{{ first_value }}</td></tr><tr><td>Second:</td><td>{{ second_value }}</td></tr></table><p>Result</p><h3>{{ sum_result }}</h3>

{% endblock %}

Yine basepage sayfasından yapılan bir genişletme olduğunu ifade edebiliriz. Gövde bu kez sonuçları göstereceğimiz HTML elementlerini barındırıyor. table elementi içerisinde first_value, second_value ve sonrasında gelen sum_result isimli değişklenlerle toplama işlemine ait detayları ve sonucu gösteriyoruz. Tüm değişkenlerin Jinja'nın istediği şekilde {{ ve }} arasında yazıldığına dikkat edelim. Benzer yaklaşım GoLang tarafında da mevcuttu.

hello_plask.py

Aslında en kilit nokta bu pyhton kod dosyası diyebiliriz. Flask ile entegre çalışan bu kod parçası, http://localhost:5000 nolu porta gelecek talepleri değerlendirmek üzere çalışmakta.

from flask import Flask, render_template,request
app=Flask(__name__)

@app.route('/')
def entry_page()->'html':
    return render_template('einstein.html',page_title='Wellcome to Little Einstein Project')


@app.route('/sum',methods=['POST'])
def sum()->'html':
    x=int(request.form['firstValue'])
    y=int(request.form['secondValue'])
    return render_template('result.html',page_title='Calculation result',sum_result=(x+y),first_value=x,second_value=y,)

app.run(debug=True)

İlk olarak Flask, render_template ve request modüllerinin entegre edildiğini belirtelim. @app.route ile başlayan decorator tanımlamaları ile takip eden fonksiyonların çalışma zamanlarındaki davranışlarını değiştirmekteyiz. Buna göre / adresine gelecek talepler entry_page, /sum adresine gelecek olan talepler ise sum fonksiyonu tarafından değerlendirilecek.

Kullanıcı einstein.html sayfasındaki butona bastığında POST metodu ile /sum adresine doğru bir yönlendirme yapılmakta. Bu kısım sum fonksiyonunca ele alınıyor. app.run(debuy=True) ifadesine göre çalışma zamanındaki tüm işlemleri(HTTP talepleri, olası çalışma zamanı hataları vb) konsolda görmemiz mümkün.

entry_page ve sum metodlarının çıktıları html olacak şekilde belirtildi. İşte bu noktada tasarlanan HTML sayfalarının render edilmesi ve istemciye gönderilmesi söz konusu. render_template metodu bu noktada devreye girmekte. İlk parametre ile hangi HTML içeriğini çağıracağımızı belirtiyoruz. Bu içerikler otomatik olarak templates klasöründe aranacaklar. Metod çağrısında takip eden parametreler ise tahmin edeceğiniz üzere render edilen HTML içerisinde kullanılan değişken adları. 

Çalışma Zamanı

Yazdığımız uygulamayı IDLE üzerinden değil de komut satırından çalıştırmamız çok daha doğru olacaktır. Web sayfalarına gelecek olan talepler debug=True ataması nedeniyle komut satırına yansıtılacak. Bu sayede anlık olarak gelişmeleri takip edebiliriz. Aşağıdaki ekran görüntüsünde bunun bir örneğini görebiliriz.

İlk olarak root adrese talepte bulunalım. Aşağıdaki çıktı ile karşılamamız gerekiyor. (Burada kullanılan CSS'i kitabın önerdiği adresten kullandım. Sadece ufak değişikliklerim oldu. Siz farklı stiller uygulayabilir ve web içeriğini çok daha şık hale getirebilirsiniz)

http://localhost:5000/ adresine yapacağımız talebin karşılığında hello_flask.py içerisindeki entry_page metodu devreye girecek ve einstein.html isimli sayfanın istemciye sunulması sağlanacaktır. Gelen ekrandaki kontrollere iki sayısal değer girip Calculate butonuna basarsak Post işlemi sonrası Sum isimli operasyon çalıştırılacak ve aşağıdaki çıktı elde edilecektir.

Görüldüğü üzere Flask paketini kullanarak Python tarafında bir web uygulaması geliştirmek ve şablon HTML sayfalarını kullanmak oldukça basit. Pek tabii şablon kullanımında {{ ile }} arasına alınabilecek çok farklı teknikler de söz konusudur. Bunları kitabın ilerleyen kısımlarında bulabileceğimi düşünüyorum. Öğrendikçe sizlerle paylaşmaya çalışacağım. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Viewing all 351 articles
Browse latest View live