Quantcast
Channel: Burak Selim ŞenyurtBurak Selim Şenyurt0.0000000.000000
Viewing all 525 articles
Browse latest View live

Atlas ile Node.js Üzerinden Haberleşmek

$
0
0

Merhaba Arkadaşlar,

Atlas denilince aklımıza gelen çoğunlukla coğrafya dersleridir. Hatta bu isimde coğrafya dergisi bile var. İşin köküne indiğimizde ise aslında bu ismin Yunan mitolojisinden geldiğini görmekteyiz. Yunan mitolojisine göre Atlas, Lapetos ve Klymene'nin 13 çocuğundan en güçlü olanıdır. Hatta o kadar güçlüdür ki Olympos'a saldırmış ve bu sebepten Zeus tarafından gök kubbeyi omuzlarından taşımakla cezalandırılmıştır. Hatta bu omuzlarda taşıma olayı bizleri o kadar etkilemiştir ki, kafatasını taşıyan ilk omura da tıp dünyası tarafından Atlas adı verilmiştir. Sanırım şimdi yandaki fotoğrafın ne anlama geldiğini daha net anladınız. Oysa ki Atlas'ın taşıdığı tek yük dünya ya da kafataslarımız değil. O, Big Data konusunda da büyük bir yükün altına girmiş durumda. İşte bugün inceleyeceğimiz konumuz. MongoDB'nin bulut çözümü hizmeti olan Atlas...

Atlas'a giriş mevzum yeni iş yerimdeki genç meslektaşlarım sayesinde gerçekleşti. Beni bu tip araştırmalara ittikleri için çok memnunum. Bir kaç gün öncesinde katıldıkları Hackathon'la ilgili izlenimlerini paylaşan arkadaşlarıma ne kadar teşekkür etsem azdır. Hemen nasıl bir şeydir araştırmaya başladım ve sonunda aşağıdaki çizimde yer alan senaryonun gerçekleştirilebileceğini öğrendim. Burada Node.js istemcisinin yerine farklı platformlar da gelebiliyor hemen söyleyeyim. Dilerseniz Ruby, Python gibi dilleri de katabilirsiniz. Ben son günlerin popüler konuşmaları özelinde Node.js tipinde bir istemciyi tercih ettim.

MongoDB aşina olduğumuz üzere oldukça başarılı ve popüler olan doküman bazlı NoSQL veritabanı sistemlerinden birisi. Atlas' da onun bulut tabanlı versiyonu olarak düşünülebilir. Bu şu anlama geliyor. Dilersek MongoDB veritabanımızı Cloud üzerinde konuşlandırabiliriz. Bu sayede sunucu ve yönetim maliyetlerini azaltmış, ölçeklenebilirlik gibi işleri de kolaylaştırmış oluruz. Atlas, esas itibariyle AWS, GCP ve Azure sunucu merkezlerini kullanmakta. Normalde bulut bilişim platformları kendi bünyelerinde çeşitli veritabanlarına hizmet sunarlarken Atlas bu işi belirli bir veritabanını ele alarak yapmakta desek sanırım yeridir. MongoDB kullanacağımız belli ancak hangi platform üzerinde konuşlandıracağımızı Atlas üzerinden şekillendirebiliyoruz.

Ne demek istediğimi anlamanızın en güzel yolu şu adrese girip bir hesap açmakla olacak. Sonrasında bizi bekleyen şey proje açılması ve bir Cluster oluşturulması. Bu adımda kullanmak istediğiniz sunucu merkezini seçebiliyorsunuz. Ben 0$ maliyeti olan Free plan'ı seçerek Freelancer altında Project Zero isimli bir proje oluşturmakla işe başladım. Sandbox olarak geçen ürün içerisinde hemen varsayılan bir Cluster bir kaç dakika içerisinde oluşturuldu. Aşağıdaki ekran görüntüsünden görebileceğiniz üzere Frankfurt'ta ki AWS sunucularında 3 node'dan oluşan ve MongoDB'nin 3.4.13 sürümünü kullanan bir Cluster' ım var.

Projeyi seçip ilerledikten ve bazı basit operasyonları icra ettiktsen sonra Cluster içerisindeki node'lar da görebildim. İki secondary ve bir primary node söz konusuydu.

Benim amacım yazının başında da belirttiğimi üzere Atlas üzerinde oluşturacağım bir MongoDB örneğine, West-World'teki Node.js kodlarını kullanarak erişebilmek. Tabii bu tip bir iş için bazı ön gereklilikleri yerine getirmek lazım. Atlas üzerinde proje oluşturup Cluster yaratmak sadece bir başlangıçtı. Bunun dışında veritabanı ile ilişkili bir kullanıcının oluşturulması da gerekiyor. Ben örnek olarak scothy isimli bir kullanıcı oluşturdum ve bonkörlük edip ona DBAdmin rolünü verdim. 

Kullanıcı oluşturmakta yeterli değil. Atlas üzerinde konuşlanacak olan MongoDB örneğine nereden erişilecek? Eğer herkesin kullanımına sunulacak bir veritabanı söz konusu ise Public erişim hakkı verebiliriz. Ben örnekten sadece kendi bilgisayarımdan erişim sağlanması için var olan o anki IP adresimi IP Whitelist adı verilen bölüme eklettim. Dolayısıyla veritabanına sadece West-World üzerinden erişilebilecek şekilde bir ayarlama yaptım. Tabii bu senaryo için işin can sıkıcı tarafı statik bir IP olmadığında yaşanıyor. Bu sebepten örnekleri hazırlarken zaman zaman güncel IP bilgisini yeniden eklemek durumunda kaldığım da oldu.

Bu adımları tamamladıktan sonra artık elimde Atlas üzerinde konuşlandırılmış bir MongoDB veritabanı olduğunu söyleyebilirim. Ama tabii ki bu da yeterli değil. Bir şekilde söz konusu veritabanına bilgi atabilmeli ve en azından bunu sorgulayabilmeliyim.

Atlas üzerindeki veritabanı ve içeriğine erişmek için bir kaç alternatif yol var. Kod tarafını denemeden önce MongoDB'nin Compass isimli ürününü kullandım. Aslında beni buraya götüren yol Atlas arabirimindeki Connect düğmesi oldu. Seçenekler arasında yer alan Compass'a bakmaya karar verdim(Bunun dışında uygulama ve Shell seçenekleri de bulunuyor)Öncelikle West-World'ün Ubuntu 64bit versiyonu için gerekli deb uzantılı paketi belirtilen adresten indirdim. Kurulum işini gerçekleştirmek işin en basit kısmıydı.

Bu arada fotoğrafın altındaki bilgiye de dikkatinizi çekerim.

Tahmin edeceğiniz gibi sonrasında Atlas'a bağlanmak gerekiyor. Atlas şu an için West-World'ün oldukça uzağında taaaa Frankfurt'ta ikamet ediyor. Bu nedenle bağlantı bilgileri de önemli. Yukarıdaki ekran görüntüsünde bir de ConnectionString bilgisi olduğunu fark etmişsinizdir. Bu bilgiyi panoya kopyalamak aslında yeterli. Compass'i açtığımızda otomatik olarak host, port, username, gibi bilgiler doldurulmakta. Ama dolmayabilir de. Ki ben denemelerimi yaparken eksik olarak eklendiler. Temel olarak aşağıdaki ekran görüntüsünde yer alan bilgilerin doldurulması gerekiyor. Buradaki Hostname, Replica Set Name bilgileri önemli. Port aksi belirtilmedikçe 27017 olarak veriliyor. Authentication modunda kullanıcı adı ve şifre kullanacağımızı belirtmemiz lazım. Bu, Atlas üzerinde oluşturduğumuz kullanıcının bilgileri.

Ben bu bilgileri doldurduktan sonra Project Zero'da yer alan Cluster0'a bağlanmayı başarabildim. Sonrasında ilk işim remote isimli bir veritabanı oluşturmak ve içerisine gamers isimli bir koleksiyon(collection) koymak oldu. Hatta aşağıdaki JSON içeriğini alıp, 

{
  "primary_info": {
     "weapon": "lazer cannon",     
     "motto": "go go gooo",
     "movie": "star wars - Jedi the last"
  },
  "max_point": 385,
  "min_point": 35,
  "matches": [
     { "game_day": 1, "vs": "lord rikken", "player_score": 200, "opponent_score":145 },
     { "game_day": 2, "vs": "evelyn", "player_score": 189, "opponent_score":485 },
     { "game_day": 3, "vs": "moka pinku", "player_score": 21, "opponent_score":18 },
     { "game_day": 4, "vs": "draymmeor", "player_score": 53, "opponent_score":56 }
  ],
  "current_rank":3,
  "nickname": "lord fungus mungus",
  "player_id": "21454834"
}

gamers koleksiyonuna yeni bir doküman olarak da ekledim. Sonuç şöyleydi...

Pek tabii hedefim bir kod parçasından faydalanarak Atlas ile haberleşmekti. Hali hazırda basit bir veri içeriği de eklediğime göre en azından Atlas'a bağlanıp bu içeriği çekmeyi deneyebilirdim. Pek çok programlama ortamı için yardımcı paketler mevcut. Ben son ay içerisinde haşırneşir olduğum Node.js tarafında ilerlemek istedim. Bu nedenle West-World'de Visual Studio Code'u kullanarak aşağıdaki kodları içeren basit bir js dosyasyı hazırladım. 

var MongoClient = require('mongodb').MongoClient;

function getAllGamers(url) {
    console.log("processing...");
    return new Promise(function (resolve, reject) {

        console.log("connecting...");

        MongoClient.connect(url, function (err, db) {
            var dbo = db.db("remote");
            var query = {};
            console.log("fetching...");
            dbo.collection("gamers")
                .find(query)
                .toArray(function (err, result) {
                    if (err) {
                        reject(err);
                    } else {
                        console.log("we have gamers now...")
                        resolve(result)
                    }
                    db.close();
                });
        });
    })
};

function main() {
    url = "mongodb://scothy:tiGeR@cluster0-shard-00-00-m2yq0.mongodb.net:27017/admin?replicaSet=Cluster0-shard-0&ssl=true"
    getAllGamers(url).then(function (result) {
        console.log(result);
    }, function (err) {
        console.log(err);
    });
    console.log("mongodb examples...")
};

main();

İlerlemeden önce kodda neler olup bitttiğini anlatsam iyi olacak. Temel olarak iki fonksiyonumuz bulunuyor. main ve getAllGamers. getAllGamers Atlas üzerinde konuşlandırdığımız koleksiyonun tüm içeriğini sorgulamak için kullanılmakta(Select * from'un hallicesinden) Tabii node.js'in npm paketini kullandığımızı da fark etmişsinizdir. Bu da

npm install mongodb

terminal komutunun gerekli olabileceği anlamına geliyor. connect fonksiyonu içerisindeki ilgili metodlarla, remote veritabanındaki gamers koleksiyonuna kadar gidiyor ve tüm içeriği bir array'e alıyoruz. Tabii kod tarafında dikkatinizi çeken bir şey de olmuştur. getAllGamers içerisinde promise isimli bir nesne örneği döndürülmekte. Bu benim yeni duyduğum ve henüz idrak etmeye çalıştığım farklı bir asenkron uygulama yaklaşımı. Normalde node.js asenkron çalışma prensiplerini benimsiyor ancak callback tarzı kullanımların bir takım sıkıntıları olduğu dillendirilmekte. Callback fonksiyonunun hiç çağırılmaması, az ya da çok çağırılması, parametrelerinin doğru alınamaması, hataların kaybolması gibi sorunlardan bahsedilmekte. Promise adı verilen yapıda ise belli başlı avantajlar var. Örneğin istenilen görev tamamlandığında promise değişikliğe uğramıyor. Immutable bir tip olduğunu ve kararlı bir nesne yapısı bahşettiğini anlayabiliriz. İşlem sonucu sadece bir kereliğine başarıya ulaşabilir veya ulaşamaz. Bu, resolved ve rejected durumları ile ele alınmakta. Öngörülemeyen hatalarda promise otomatikman rejected moduna geçiyor. 

Koddan da fark edeceğiniz üzere toArray fonksiyonunun parametresindeki isimsiz metodda reject ve resolve çağrıları söz konusu. Veri sonucu alındığında tetiklenen resolve fonksiyonu sonucun getAllGamers.then metodundan ele alınabiliyor olmasını sağlamakta. Tabii bir hata olması halinde reject operasyonunun tetikleneceği de aşikar. Biraz kafa karıştırıcı değil mi? En azından benim için öyle. Oysaki promise kullanımını hamburger siparişi ve siparişin hazır olması sonrası çalan masa zili ile gayet güzel bir tasvirleyerek anlatan esaslı bir yazı var. Özgün Bal'ın şu yazısını mutlaka okumanızıöneririm.

Sonuçta uygulamayı çalıştırdığımda gamers koleksiyonunun içeriğini çekebildiğimi fark ettim.

Tüm bu işlemler olurken Atlas üzerinde de veri hareketlilikleri olmakta tabii ki. Onları izlemek mümkün, sonuçlara göre ölçeklendirme ile ilgili bir takım yönetimsel işlemleri yapmak mümkün. Eğer kullanılan cluster'lara bakılırsa aşağıdaki ekran görüntüsünde olduğu gibi gayet güzel grafikler sunuluyor. 

Bu güzel gelişmelerden sonra kod tarafını biraz daha kurcalamak istedim. İlk olarak yeni bir koleksiyonu nasıl oluşturabilirim buna baktım. Promise konusunu şimdilik işe katmadan(ki kullanımına alışana kadar kafamı karıştırsın istemiyorum) koda aşağıdaki createCollection fonksiyonunu ekledim.

function createCollection(url, name) {
    console.log("db create process...");
    MongoClient.connect(url, function (err, db) {
        if (err) throw err;
        var dbOwner = db.db("remote");
        dbOwner.createCollection(name, function (err, res) {
            if (err) throw err;
            console.log("collection created!");
            db.close();
        });
    });
};

url ve name isimli iki parametre alan createCollection fonksiyonu MongoClient.connect ile Atlas'a bağlantı sağladıktan sonra üzerinde işlem yapabilmek için remote veritabanını işaret eden bir db nesnesi örnekliyor. Sonrasında dbOwner isimli nesne örneği üzerinden createCollection metodu yardımıyla ilgili koleksiyonun oluşturulması sağlanıyor. Kodun main fonksiyonu içeriği ise şu şekilde.

function main() {
    url = "mongodb://scothy:tiGeR@cluster0-shard-00-00-m2yq0.mongodb.net:27017/admin?replicaSet=Cluster0-shard-0&ssl=true"
    createCollection(url,"designers");
    console.log("mongodb examples...")
};

main();

Çalışma zamanı çıktısı tam da istediğim gibiydi. gamers dışında designers isimli yeni bir koleksiyon daha oluşmuştu.

E o zaman birde bu yeni koleksiyona doküman eklemeyi denesek mi? İşte bu işi üstlenecek insertDesigner isimli fonksiyonumuz.

function insertDesigner(url,content){
    console.log("inserting...");
    MongoClient.connect(url,function(err,db){
        if(err) throw err;
        var dbOwner=db.db("remote");
        dbOwner.collection("designers").insertOne(content,function(err,res){
            if(err)throw err;
            console.log("a new designer inserted");
            console.log("ID : %s",res.insertedId);
            db.close();
        });
    });
};

function main() {
    url = "mongodb://scothy:tiGeR@cluster0-shard-00-00-m2yq0.mongodb.net:27017/admin?replicaSet=Cluster0-shard-0&ssl=true"
    var vanDyk={fullName:"Yurri van dayk de la rossa",country:"green age",system:"Tatuin",expLevel:980};
    insertDesigner(url,vanDyk);
    console.log("mongodb examples...")
};

main();

Bu sefer collection fonksiyonu ile yakaladığımız koleksiyon için insertOne metodu yardımıyla yeni bir JSON nesnesini göndermekteyiz. Kodun West-World tarafındaki çalışma zamanı sonuçları aşağıdaki gibi.

Dikkat edileceği üzere insertOne metodunun ikinci parametresi olarak kullanılan fonksiyondaki res değişkeni üzerinden, henüz eklenen dokümanın MongoDB tarafında üretilen objectId bilgisine ulaşabildik.

Örnekte yer alan insertOne metodu dışında kullanabileceğimiz bir diğer alternatif de insertMany. Adından da anlaşılacağı üzere koleksiyona birden fazla doküman eklemek istediğimiz durumlarda kullanılabilir. remote veritabanındaki designers koleksiyonu için ben şöyle bir deneme yaptım.

function insertDesigners(url,content){
    console.log("inserting...");
    MongoClient.connect(url,function(err,db){
        if(err) throw err;
        var dbOwner=db.db("remote");
        dbOwner.collection("designers").insertMany(content,function(err,res){
            if(err)throw err;
            console.log("%i documents inserted",res.insertedCount);
            db.close();
        });
    });
};

function main() {
    url = "mongodb://scothy:tiGeR@cluster0-shard-00-00-m2yq0.mongodb.net:27017/admin?replicaSet=Cluster0-shard-0&ssl=true"
    var designers=[
        {fullName:"blue man",country:"ingland",system:"oceanic continent",expLevel:256},
        {fullName:"Reddick",country:"red sun",system:"world",expLevel:128},
        {fullName:"mari dö marş",country:"pari",system:"moon",expLevel:45}
    ];
    insertDesigners(url,designers);

    console.log("mongodb examples...")
};

main();

Bu sefer bir JSON nesne dizisini parametre olarak kullanmaktayız. Her birisi ayrı birer doküman olarak değerlendirilecek. İşte benim elde ettiğim sonuçlar,

Örnekler çoğaltılabilir. Doküman sorgulanması, veritabanı üretilmesi, çeşitli kriterlere göre sorgulamalar yapılması gibi temel pek çok operasyon kolaylıkla gerçekleştirilebilir. Söz konusu kodlar birer WebAPI servisi gibi tasarlanabilir de. Siz görevinizi anladınız değil mi?

Eğer konusunda veri kullanılması gereken bir startup denemesi veya bir proje ya da tez ödevi olsa sanırım Atlas gibi platformların Free Plan statüsündeki versiyonlarını kullanmayı tercih edebiliriz. Maliyeti sadece internet ücreti ile sınırlı olacaktır. Üstelik istemci tarafını geliştirmekte oldukça basit. Senaryolar çokça çeşitlendirilebilir. Node.js uygulamasının yine bulut üzerinde bir yerlde servisleştirilmesi ve bu servisin Atlas ile konuşarak MongoDB tabanlı veri yapılarını kullanması pekala mümkün. Günümüzde birbirine bağlanamayan sistem neredeyse kalmadı gibi ne dersiniz :) Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.


http://www.buraksenyurt.com/post/Google-Cloud-Storage-KullanımıGoogle Cloud Storage Kullanımı

$
0
0

Merhaba Arkadaşlar,

Vakti zamanında sıkı bir Instagram kullanıcısıydım. En güzel fotoğrafları yakalamaya çalışır, anı görüntüleyip tüm bağlantılarımla paylaşırdım. Derken bir gün "ne yapıyorum ben yahu?" oldum. Neden o anı ille de herkesle paylaşma ihtiyacı hissediyordum. Bazen o anın fotoğrafını çekmek gerekmiyordu. Hatta hiç çekmediğim zamanlarda aklıma nasıl kazıdığımı bile unutmuştum. Üstelik ona ayıracağım zamanı pekala başka değerli şeylere de ayırabilirdim. Örneğin yeni şeyler öğrenmeye, makale yazmaya vs...

Görmekte olduğunuz fotoğraf Instagram arşivimden bir kare. O zamanlar denediğim aynasız fotoğraf makinem(Sony Alpha idi. Hafif ve kullanışlıydı) ile çekmiştim. Her zaman ki gibi büyük bir keyifle tamamladığım Ghostbusters'ın efsane arabası Ecto...Bugün işleyeceğimiz makaleye konu olacak olan fotoğraf. Haydi gelin başlayalım.

Google Cloud Platform üzerinden sunulan hizmetleri incelemeye devam ettiğim bir Cumartesi gecesi yolum Storage isimli RESTful API'ye düştü. Bu API sayesinde Google Cloud Platform üzerinde konuşlandırdığımız verilere(her tür içerik söz konusu olabilir anladığım kadarıyla) erişmemiz mümkün. Fotoğraf, makale, doküman vb içerikler depolama denince aklımıza ilk gelen çeşitler.

Storage API'sinin sunduğu fonksiyonellikler sayesinde bu tip içerikleri bize ait bucket alanlarına taşımamız ve okumamız mümkün. Hatta bu içerikleri genele veya kişilere özel olacak şekilde sunabiliriz de. Elbette Google Cloud Platform içerisinde bu API'yi uygulamalar arası değerlendirmek de mümkün ki asıl amaçlarından birisi de bu zaten. Söz gelimi Bucket'a(Kova gibi de düşünülebilir) bir fotoğraf attıktan sonra, Pub/Sub API'den de yararlanarak(geçenlerde incelemiştik hatırlarsanız) yüklenen dokümanın bir diğer akıllı Google servisi tarafından işlenmesini sağlayabiliriz. Ya da platforma taşıdığımız bir web uygulamasının css, image gibi çeşitli kaynaklarını bu alanlardan sağlayabiliriz. Bunlar tabii benim için uç senaryolar. Şimdilik tekil parçaları nasıl kullanabilirimin peşindeyim.

Storage API'sini kullanmak oldukça kolay. Komut satırından gsutil aracı(Google Cloud SDK'ye sahip olduğunuzu düşünüyorum) yardımıyla tüm temel işlemleri gerçekleştirebiliriz. .Net Core, Python, Go, Ruby, Node.js, Java gibi pek çok dilin de söz konusu API'yi kullanabilmek için geliştirilmiş paketleri bulunuyor. Dolayısıyla kod tarafından da ilgili servisi kullanmak mümkün.

Ben tahmin edileceği üzere öncelikle komut satırından ve sonrasında da .Net Core tarafından söz konusu servisi kullanmaya çalışacağım. Senaryom oldukça basit. Bir bucket oluştur, buraya fotoğraf at, attığın fotoğrafı oku, bucket'ı sil vb...Şunu unutmamak lazım ki, bu servis diğer Google servisleri gibi ücretlendirmeye tabii olabilir. O nedenle denemelerden sonra oluşturulan bucket veya içeriği silmek gerekiyor.

gsUtil ile Storage İşlemleri

İlk olarak gsutil ile söz konusu işlemleri nasıl yapabileceğimize bir bakalım. Tabii işe başlamadan önce Google Cloud Platform üzerinde bir proje oluşturmamız ve özellikle Storage API'yi kullanacak geçerli bir servis kullanıcısı üreterek bu kullanıcıya ait Credential bilgilerini taşıyan json formatlı içeriği kendi ortamımızda işaret etmemiz gerekiyor.

Ben bu işlemlere önceki makalelerde değindiğim için tekrar üstünden geçmiyorum. Diğer yandan Google dokümantasyonuna göre sitemde Python'un en azından 2.7 sürümünün yüklü olması gerekiyor. West-World' de bu sürüm mevcut. Ben çalışmam sırasında aşağıdaki komutları denedim.

gsutil mb gs://article-images-bucket/
gsutil cp legom.jpg gs://article-images-bucket
gsutil ls gs://article-images-bucket
gsutil ls -l gs://article-images-bucket
gsutil acl ch -u AllUsers:R gs://article-images-bucket/legom.jpg gsutil acl ch -d AllUsers gs://article-images-bucket/legom.jpg
gsutil rm gs://article-images-bucket/legom.jpg
gsutil rm -r gs://article-images-bucket

mb komutu ile Storage üzerinde article-images-bucket isimli bir bucket oluşturuyoruz. Bu işlem sonrasında Google kontrol paneline gittiğimde bucket örneğinin oluşturulduğunu da gördüm(Sevindirdi)

cp parametresini kullanarak bir bucket'a dosya yüklememiz de mümkün. Ben legom.jpg dosyasını yükledim(farklı dosya tiplerinden içerikler de kullanılabilir) Sonuç aşağıdaki resimde görüldüğü gibiydi.

ls parametresi ile bucket içerisindeki dosyaları görmek mümkün. Eğer bu dosyaların detay bilgisine ulaşmak istiyorsak bu durumda -l anahtarını kullanmak gerekiyor. acl kullanılan kısımda internetteki tüm kullancıların legom.jpg dosyasını okuyabileceğini belirtiyoruz. Bir nevi Access Control List için AllUsers rolüne Read hakkı verdiğimizi ifade edebiliriz. Burada belirli kullanıcılara çeşitli haklar vermemiz de mümkün. Söz gelimi ilgli Storage içerisindeki dosyaları startup takımızdaki kişilerin kullanımına açabiliriz. Nasıl yapılabileceğini bulmaya ne dersiniz? ;)

rm kullanılan komutlar ile bucket içerisinden öğe veya bucket'ın kendisini silebiliriz. Ben fiyatlandırma korkusu nedeniyle, denememi yapar yapmaz ilgili içerikleri sildim :) 

Credential Mevzusu

Bir Google Cloud servisini kullanacağımız zaman, bu servis özelinde çalışacak bir servis kullanıcısı oluşturulması ve üretilen json dosyasının kullanılması öneriliyor. Bunu API Services -> Credentials kısmından New Service Account ile yapabiliriz. Önemli olan oluşturulan veya var olan kullanıcı için Storage servisi(ya da hangi servisi kullandırtmak istiyorsak onun için) bir role belirlenmesidir. 

Ben bu senaryo için my-storage-master isimli bir kullanıcı oluşturup Storage API servisinde Admin rolü ile ilişkilendirdim. Kullanıcı oluşturulduktan sonra da yetkilendirme bilgilerini taşıyan JSON formatlı dosyayı ürettirdim.

Bu dosyayı kod tarafındaki servise ait Credential bilgilerini yüklemek için kullanacağız.

Aslında Credentials vakasına baktığımızda kullanılabilecek bir çok yol mevcut. Sistemin Google_Application_Credentials anahtar değerine sahip path bilgisini bu dosya ile eşleştirebileceğimiz gibi kod tarafında farklı şekillerde Credential bilgisini yüklememiz de mümkün. Detaylar için buradaki yazıya bakmanızıöneririm. 

.Net Core Tarafı

Görüldüğü üzere komut satırında gsutil aracını kullanarak Storage API ile konuşmak oldukça basit ve pratik. Neler yapabileceğimizi az çok anladık. Şimdi kod yoluyla Storage API'sini nasıl kullanabileceğimizi incelemeye çalışalım. Her zaman ki gibi konuyu basit şekilde ele almak adına bir Console uygulaması oluşturarak işe başlayabiliriz. Sonrasında Google.Cloud.Storage.V1 paketini uygulamaya dahil etmek gerekiyor. Bu paket REST modelli Storage servisi ile konuşmamızı kolaylaştıracak(Düşündüm de günümüzde her yer RESTful servis sanki) Dolayısıyla ilk terminal komutlarımız şunlar...

dotnet new console -o howtostorage
dotnet add package Google.Cloud.Storage.V1

Program.cs içeriğini aşağıdaki gibi geliştirebiliriz.

using System;
using System.IO;
using Google.Apis.Auth.OAuth2;
using Google.Cloud.Storage.V1;

namespace howtostorage
{
    class Program
    {
        static StorageClient storageClient;

        static void Main(string[] args)
        {
            var credential = GoogleCredential.FromFile("my-starwars-game-project-credentials.json");
            storageClient = StorageClient.Create(credential);
            var projectId = "subtle-seer-193315";
            var bucketName = "article-images-bucket";
            CreateBucket(projectId, bucketName);
            WriteBucketList(projectId);
            UploadObject(bucketName, "legom.jpg", "legom");
            UploadObject(bucketName, "pinkfloyd.jpg", "pink-floyd");
            WriteBucketObjects(bucketName);
            DownloadObject(bucketName, "legom","lego-from-google.jpg");
            DownloadObject(bucketName, "pink-floyd","pink-floyd-from-google.jpg");
            
            Console.WriteLine("Yüklenen nesneler silinecek");
            Console.ReadLine();

            DeleteObject(bucketName, "pink-floyd");
            DeleteObject(bucketName, "legom");
            DeleteBucket(bucketName);
        }
        static void CreateBucket(string projectId, string bucketName)
        {
            try
            {
                storageClient.CreateBucket(projectId, bucketName);
                Console.WriteLine($"{bucketName} oluşturuldu.");
            }
            catch (Google.GoogleApiException e)
            when (e.Error.Code == 409)
            {
                Console.WriteLine(e.Error.Message);
            }
        }
        static void WriteBucketList(string projectId)
        {
            foreach (var bucket in storageClient.ListBuckets(projectId))
            {
                Console.WriteLine($"{bucket.Name},{bucket.TimeCreated}");
            }
        }
        static void UploadObject(string bucketName, string filePath,string objectName = null)
        {
            using (var stream = File.OpenRead(filePath))
            {
                objectName = objectName ?? Path.GetFileName(filePath);
                storageClient.UploadObject(bucketName, objectName, null, stream);
                Console.WriteLine($"{objectName} yüklendi.");
            }
        }
        static void WriteBucketObjects(string bucketName)
        {
            foreach (var obj in storageClient.ListObjects(bucketName, ""))
            {
                Console.WriteLine($"{obj.Name}({obj.Size})");
            }
        }
        static void DownloadObject(string bucketName, string objectName,string filePath = null)
        {
            filePath = filePath ?? Path.GetFileName(objectName);
            using (var stream = File.OpenWrite(filePath))
            {
                storageClient.DownloadObject(bucketName, objectName, stream);
            }
            Console.WriteLine($"{objectName}, {filePath} olarak indirildi.");
        }
        static void DeleteObject(string bucketName, string objectName)
        {
            storageClient.DeleteObject(bucketName, objectName);
            Console.WriteLine($"{objectName} silindi.");
        }
        static void DeleteBucket(string bucketName)
        {
            storageClient.DeleteBucket(bucketName);
            Console.WriteLine($"{bucketName} silindi.");
        }
    }
}

Neler yaptığımıza bir bakalım. Kodun akışı Credential bilgilerini içeren dosyanın okunması ve elde edilen güvenlik kriterleri ile StorageClient nesnesinin örneklenmesi ile başlıyor. Sonrasında projeId bilgisi ve örnek bir bucket adı kullanılarak işlemlere başlanıyor. Bucket'ın oluşturulması, güncel bucket listesine bakılması, iki resim dosyasının yüklenmesi, bucket içerisindeki nesnelerin çekilmesi, bir resim dosyasının Google'dan West-World'e getirilmesi ve son olarak da tüm nesnelerin platformdan silinmesi işlemleri gerçekleştiriliyor.

Aslında tüm operasyon StorageClient nesnesinin metodları ile icra edilmekte. Google'ın diğer API servisleri için geliştirilmiş Client kütüphanelerinde de benzer standartlar mevcut. Bu nedenle birisini öğrendikten sonra diğerlerini kullanmak da kolay olacaktır diye düşünüyorum. 

CreateBucket metodu ile plaform üzerinde bir bucket oluşturulması sağlanıyor. Parametre olarak hangi projeyi kullanacaksak onun ID bilgisi ve bir de bucket adı veriliyor. Pek tabii oluşturulmak istenen bucket zaten varsa 409 kodlu bir Exception alınmakta. Platform üzerinde oluşturulan bucket listesini ListBuckets metodu ile çekebiliriz. Parametre olarak projeID bilgisini vermek yeterli.

Bucket içerisine nesne atma işi aslında Stream temelli. Nitekim yükleyeceğimiz içerikler en nihayetinde bir byte dizisine tekabül etmekte. UploadObject fonksiyonunun son parametresi de bu stream nesnesi. Bucket içerisindeki nesneleri ListObjects fonksiyonu yardımıyla yakalamamız mümkün. Örnek kod parçasında bu nesnelerin isim ve boyutlarını alıp ekrana yazdırmaktayız. Nesne silme işlemi de oldukça kolay. DeleteObject ve DeleteBucket fonksiyonlarından yararlanarak bir bucket nesnesini veya içerisindeki bir öğeyi silmek mümkün.

Programı çalıştırdıktan sonra iki aşamalı olarak elde ettiğim sonuçlara baktım. Öncelikle oluşturulan bucket'a iki resim dosyasının da yüklendiğini gözlemledim.

Tuşa basıp ilerlediğimdeyse hem yüklediğim dosyaların hem de bucket'ın kendisinin silindiğini gördüm.

Görüldüğü üzere Google Cloud Platform'un kullanışlı API hizmetlerinden birisi olan Storage servisini kullanmak oldukça kolay. Artık kendi ortamınızdaki nesneleri Storage'a taşıyabilir buradan tetikleteceğiniz olaylar ile başka süreçlerin devreye girmesini sağlayabilirsiniz(diye düşünüyorum) Böylece geldik bir araştırmamızın daha sonuna. Doğruyu söylemek gerekirse Google Cloud Platform'un sunduğu API'lerle oynamak epey keyifli. Azure, AWS tarafından sunulan fonksiyonellikler için de aynı durumun söz konusu olduğunu belirtmek isterim. Bu tip fonksiyonellikleri deneyimleyerek bulut platformların imkanlarını daha iyi kavrayabilir ve çözüm üretme noktasında daha rahat hareket edebiliriz. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Google Cloud Storage Kullanımı

$
0
0

Merhaba Arkadaşlar,

Vakti zamanında sıkı bir Instagram kullanıcısıydım. En güzel fotoğrafları yakalamaya çalışır, anı görüntüleyip tüm bağlantılarımla paylaşırdım. Derken bir gün "ne yapıyorum ben yahu?" oldum. Neden o anı ille de herkesle paylaşma ihtiyacı hissediyordum. Bazen o anın fotoğrafını çekmek gerekmiyordu. Hatta hiç çekmediğim zamanlarda aklıma nasıl kazıdığımı bile unutmuştum. Üstelik ona ayıracağım zamanı pekala başka değerli şeylere de ayırabilirdim. Örneğin yeni şeyler öğrenmeye, makale yazmaya vs...

Görmekte olduğunuz fotoğraf Instagram arşivimden bir kare. O zamanlar denediğim aynasız fotoğraf makinem(Sony Alpha idi. Hafif ve kullanışlıydı) ile çekmiştim. Her zaman ki gibi büyük bir keyifle tamamladığım Ghostbusters'ın efsane arabası Ecto...Bugün işleyeceğimiz makaleye konu olacak olan fotoğraf. Haydi gelin başlayalım.

Google Cloud Platform üzerinden sunulan hizmetleri incelemeye devam ettiğim bir Cumartesi gecesi yolum Storage isimli RESTful API'ye düştü. Bu API sayesinde Google Cloud Platform üzerinde konuşlandırdığımız verilere(her tür içerik söz konusu olabilir anladığım kadarıyla) erişmemiz mümkün. Fotoğraf, makale, doküman vb içerikler depolama denince aklımıza ilk gelen çeşitler.

Storage API'sinin sunduğu fonksiyonellikler sayesinde bu tip içerikleri bize ait bucket alanlarına taşımamız ve okumamız mümkün. Hatta bu içerikleri genele veya kişilere özel olacak şekilde sunabiliriz de. Elbette Google Cloud Platform içerisinde bu API'yi uygulamalar arası değerlendirmek de mümkün ki asıl amaçlarından birisi de bu zaten. Söz gelimi Bucket'a(Kova gibi de düşünülebilir) bir fotoğraf attıktan sonra, Pub/Sub API'den de yararlanarak(geçenlerde incelemiştik hatırlarsanız) yüklenen dokümanın bir diğer akıllı Google servisi tarafından işlenmesini sağlayabiliriz. Ya da platforma taşıdığımız bir web uygulamasının css, image gibi çeşitli kaynaklarını bu alanlardan sağlayabiliriz. Bunlar tabii benim için uç senaryolar. Şimdilik tekil parçaları nasıl kullanabilirimin peşindeyim.

Storage API'sini kullanmak oldukça kolay. Komut satırından gsutil aracı(Google Cloud SDK'ye sahip olduğunuzu düşünüyorum) yardımıyla tüm temel işlemleri gerçekleştirebiliriz. .Net Core, Python, Go, Ruby, Node.js, Java gibi pek çok dilin de söz konusu API'yi kullanabilmek için geliştirilmiş paketleri bulunuyor. Dolayısıyla kod tarafından da ilgili servisi kullanmak mümkün.

Ben tahmin edileceği üzere öncelikle komut satırından ve sonrasında da .Net Core tarafından söz konusu servisi kullanmaya çalışacağım. Senaryom oldukça basit. Bir bucket oluştur, buraya fotoğraf at, attığın fotoğrafı oku, bucket'ı sil vb...Şunu unutmamak lazım ki, bu servis diğer Google servisleri gibi ücretlendirmeye tabii olabilir. O nedenle denemelerden sonra oluşturulan bucket veya içeriği silmek gerekiyor.

gsUtil ile Storage İşlemleri

İlk olarak gsutil ile söz konusu işlemleri nasıl yapabileceğimize bir bakalım. Tabii işe başlamadan önce Google Cloud Platform üzerinde bir proje oluşturmamız ve özellikle Storage API'yi kullanacak geçerli bir servis kullanıcısı üreterek bu kullanıcıya ait Credential bilgilerini taşıyan json formatlı içeriği kendi ortamımızda işaret etmemiz gerekiyor.

Ben bu işlemlere önceki makalelerde değindiğim için tekrar üstünden geçmiyorum. Diğer yandan Google dokümantasyonuna göre sitemde Python'un en azından 2.7 sürümünün yüklü olması gerekiyor. West-World' de bu sürüm mevcut. Ben çalışmam sırasında aşağıdaki komutları denedim.

gsutil mb gs://article-images-bucket/
gsutil cp legom.jpg gs://article-images-bucket
gsutil ls gs://article-images-bucket
gsutil ls -l gs://article-images-bucket
gsutil acl ch -u AllUsers:R gs://article-images-bucket/legom.jpg gsutil acl ch -d AllUsers gs://article-images-bucket/legom.jpg
gsutil rm gs://article-images-bucket/legom.jpg
gsutil rm -r gs://article-images-bucket

mb komutu ile Storage üzerinde article-images-bucket isimli bir bucket oluşturuyoruz. Bu işlem sonrasında Google kontrol paneline gittiğimde bucket örneğinin oluşturulduğunu da gördüm(Sevindirdi)

cp parametresini kullanarak bir bucket'a dosya yüklememiz de mümkün. Ben legom.jpg dosyasını yükledim(farklı dosya tiplerinden içerikler de kullanılabilir) Sonuç aşağıdaki resimde görüldüğü gibiydi.

ls parametresi ile bucket içerisindeki dosyaları görmek mümkün. Eğer bu dosyaların detay bilgisine ulaşmak istiyorsak bu durumda -l anahtarını kullanmak gerekiyor. acl kullanılan kısımda internetteki tüm kullancıların legom.jpg dosyasını okuyabileceğini belirtiyoruz. Bir nevi Access Control List için AllUsers rolüne Read hakkı verdiğimizi ifade edebiliriz. Burada belirli kullanıcılara çeşitli haklar vermemiz de mümkün. Söz gelimi ilgli Storage içerisindeki dosyaları startup takımızdaki kişilerin kullanımına açabiliriz. Nasıl yapılabileceğini bulmaya ne dersiniz? ;)

rm kullanılan komutlar ile bucket içerisinden öğe veya bucket'ın kendisini silebiliriz. Ben fiyatlandırma korkusu nedeniyle, denememi yapar yapmaz ilgili içerikleri sildim :) 

Credential Mevzusu

Bir Google Cloud servisini kullanacağımız zaman, bu servis özelinde çalışacak bir servis kullanıcısı oluşturulması ve üretilen json dosyasının kullanılması öneriliyor. Bunu API Services -> Credentials kısmından New Service Account ile yapabiliriz. Önemli olan oluşturulan veya var olan kullanıcı için Storage servisi(ya da hangi servisi kullandırtmak istiyorsak onun için) bir role belirlenmesidir. 

Ben bu senaryo için my-storage-master isimli bir kullanıcı oluşturup Storage API servisinde Admin rolü ile ilişkilendirdim. Kullanıcı oluşturulduktan sonra da yetkilendirme bilgilerini taşıyan JSON formatlı dosyayı ürettirdim.

Bu dosyayı kod tarafındaki servise ait Credential bilgilerini yüklemek için kullanacağız.

Aslında Credentials vakasına baktığımızda kullanılabilecek bir çok yol mevcut. Sistemin Google_Application_Credentials anahtar değerine sahip path bilgisini bu dosya ile eşleştirebileceğimiz gibi kod tarafında farklı şekillerde Credential bilgisini yüklememiz de mümkün. Detaylar için buradaki yazıya bakmanızıöneririm. 

.Net Core Tarafı

Görüldüğü üzere komut satırında gsutil aracını kullanarak Storage API ile konuşmak oldukça basit ve pratik. Neler yapabileceğimizi az çok anladık. Şimdi kod yoluyla Storage API'sini nasıl kullanabileceğimizi incelemeye çalışalım. Her zaman ki gibi konuyu basit şekilde ele almak adına bir Console uygulaması oluşturarak işe başlayabiliriz. Sonrasında Google.Cloud.Storage.V1 paketini uygulamaya dahil etmek gerekiyor. Bu paket REST modelli Storage servisi ile konuşmamızı kolaylaştıracak(Düşündüm de günümüzde her yer RESTful servis sanki) Dolayısıyla ilk terminal komutlarımız şunlar...

dotnet new console -o howtostorage
dotnet add package Google.Cloud.Storage.V1

Program.cs içeriğini aşağıdaki gibi geliştirebiliriz.

using System;
using System.IO;
using Google.Apis.Auth.OAuth2;
using Google.Cloud.Storage.V1;

namespace howtostorage
{
    class Program
    {
        static StorageClient storageClient;

        static void Main(string[] args)
        {
            var credential = GoogleCredential.FromFile("my-starwars-game-project-credentials.json");
            storageClient = StorageClient.Create(credential);
            var projectId = "subtle-seer-193315";
            var bucketName = "article-images-bucket";
            CreateBucket(projectId, bucketName);
            WriteBucketList(projectId);
            UploadObject(bucketName, "legom.jpg", "legom");
            UploadObject(bucketName, "pinkfloyd.jpg", "pink-floyd");
            WriteBucketObjects(bucketName);
            DownloadObject(bucketName, "legom","lego-from-google.jpg");
            DownloadObject(bucketName, "pink-floyd","pink-floyd-from-google.jpg");
            
            Console.WriteLine("Yüklenen nesneler silinecek");
            Console.ReadLine();

            DeleteObject(bucketName, "pink-floyd");
            DeleteObject(bucketName, "legom");
            DeleteBucket(bucketName);
        }
        static void CreateBucket(string projectId, string bucketName)
        {
            try
            {
                storageClient.CreateBucket(projectId, bucketName);
                Console.WriteLine($"{bucketName} oluşturuldu.");
            }
            catch (Google.GoogleApiException e)
            when (e.Error.Code == 409)
            {
                Console.WriteLine(e.Error.Message);
            }
        }
        static void WriteBucketList(string projectId)
        {
            foreach (var bucket in storageClient.ListBuckets(projectId))
            {
                Console.WriteLine($"{bucket.Name},{bucket.TimeCreated}");
            }
        }
        static void UploadObject(string bucketName, string filePath,string objectName = null)
        {
            using (var stream = File.OpenRead(filePath))
            {
                objectName = objectName ?? Path.GetFileName(filePath);
                storageClient.UploadObject(bucketName, objectName, null, stream);
                Console.WriteLine($"{objectName} yüklendi.");
            }
        }
        static void WriteBucketObjects(string bucketName)
        {
            foreach (var obj in storageClient.ListObjects(bucketName, ""))
            {
                Console.WriteLine($"{obj.Name}({obj.Size})");
            }
        }
        static void DownloadObject(string bucketName, string objectName,string filePath = null)
        {
            filePath = filePath ?? Path.GetFileName(objectName);
            using (var stream = File.OpenWrite(filePath))
            {
                storageClient.DownloadObject(bucketName, objectName, stream);
            }
            Console.WriteLine($"{objectName}, {filePath} olarak indirildi.");
        }
        static void DeleteObject(string bucketName, string objectName)
        {
            storageClient.DeleteObject(bucketName, objectName);
            Console.WriteLine($"{objectName} silindi.");
        }
        static void DeleteBucket(string bucketName)
        {
            storageClient.DeleteBucket(bucketName);
            Console.WriteLine($"{bucketName} silindi.");
        }
    }
}

Neler yaptığımıza bir bakalım. Kodun akışı Credential bilgilerini içeren dosyanın okunması ve elde edilen güvenlik kriterleri ile StorageClient nesnesinin örneklenmesi ile başlıyor. Sonrasında projeId bilgisi ve örnek bir bucket adı kullanılarak işlemlere başlanıyor. Bucket'ın oluşturulması, güncel bucket listesine bakılması, iki resim dosyasının yüklenmesi, bucket içerisindeki nesnelerin çekilmesi, bir resim dosyasının Google'dan West-World'e getirilmesi ve son olarak da tüm nesnelerin platformdan silinmesi işlemleri gerçekleştiriliyor.

Aslında tüm operasyon StorageClient nesnesinin metodları ile icra edilmekte. Google'ın diğer API servisleri için geliştirilmiş Client kütüphanelerinde de benzer standartlar mevcut. Bu nedenle birisini öğrendikten sonra diğerlerini kullanmak da kolay olacaktır diye düşünüyorum. 

CreateBucket metodu ile plaform üzerinde bir bucket oluşturulması sağlanıyor. Parametre olarak hangi projeyi kullanacaksak onun ID bilgisi ve bir de bucket adı veriliyor. Pek tabii oluşturulmak istenen bucket zaten varsa 409 kodlu bir Exception alınmakta. Platform üzerinde oluşturulan bucket listesini ListBuckets metodu ile çekebiliriz. Parametre olarak projeID bilgisini vermek yeterli.

Bucket içerisine nesne atma işi aslında Stream temelli. Nitekim yükleyeceğimiz içerikler en nihayetinde bir byte dizisine tekabül etmekte. UploadObject fonksiyonunun son parametresi de bu stream nesnesi. Bucket içerisindeki nesneleri ListObjects fonksiyonu yardımıyla yakalamamız mümkün. Örnek kod parçasında bu nesnelerin isim ve boyutlarını alıp ekrana yazdırmaktayız. Nesne silme işlemi de oldukça kolay. DeleteObject ve DeleteBucket fonksiyonlarından yararlanarak bir bucket nesnesini veya içerisindeki bir öğeyi silmek mümkün.

Programı çalıştırdıktan sonra iki aşamalı olarak elde ettiğim sonuçlara baktım. Öncelikle oluşturulan bucket'a iki resim dosyasının da yüklendiğini gözlemledim.

Tuşa basıp ilerlediğimdeyse hem yüklediğim dosyaların hem de bucket'ın kendisinin silindiğini gördüm.

Görüldüğü üzere Google Cloud Platform'un kullanışlı API hizmetlerinden birisi olan Storage servisini kullanmak oldukça kolay. Artık kendi ortamınızdaki nesneleri Storage'a taşıyabilir buradan tetikleteceğiniz olaylar ile başka süreçlerin devreye girmesini sağlayabilirsiniz(diye düşünüyorum) Böylece geldik bir araştırmamızın daha sonuna. Doğruyu söylemek gerekirse Google Cloud Platform'un sunduğu API'lerle oynamak epey keyifli. Azure, AWS tarafından sunulan fonksiyonellikler için de aynı durumun söz konusu olduğunu belirtmek isterim. Bu tip fonksiyonellikleri deneyimleyerek bulut platformların imkanlarını daha iyi kavrayabilir ve çözüm üretme noktasında daha rahat hareket edebiliriz. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

http://www.buraksenyurt.com/post/azure-ile-ilk-maceram-app-service-ve-nodejsAzure ile İlk Maceram (App Service)

$
0
0

Merhaba Arkadaşlar,

Sinema tarihinin en araştırmacı en gözüpek en maceraperest arkeoloğu kimdir desek herhalde aklımıza tek bir isim gelir; Indiana Jones. Geçenlerde DVD arşivimden şöyle yanında patlamış mısırla izleyeceğim güzel bir macera filmi bakıyordum. Bu yaşıma kadar aslında bir çok ünlü seriyi arşivime eklemiştim. Baba, Matrix, Mad Max, Star Wars, Back to the Future, Terminator, Lord of the Rings ve diğerleri. Derken Indiana Jones çıktı karşıma ve gecenin izlencesi belli oldu. Mısırlar patlatıldı, naneli limonatalar hazırlandı, DVD takıldı, perdeler indirildi ve seyir başladı. Pek tabii Indiana Jones'un bir profesör olmasından çok atıldığı maceralardı seyirciyi ekrana bağlayan. Ona can veren Harrison Ford'un ince esprileri de cabasıydı. Filmi büyük bir keyifle tamamladıktan sonra geçtim West-World'ün başına. Bir Indiana Jones değildim ama benim de kendi çapımda minik maceralarım vardı. Sıradaki serüven yüksek tepelerin ardında, ihtişamlı bulutları ile göz kamaştıran Azure hanedanlığına doğru olacaktı.

Bulut bilişim dünyasının başrol oyuncularını düşündüğümüzde karşımıza Amazon Web Services, Google Cloud Platform ve Microsoft Azure çıkıyor(İlk harflere göre sıralayarak yazdım :P) Neredeyse hepsinin benzer amaç, araç ve sunduğu hizmetler var(Düşünsenize hepsinde mutlaka tarayıcı üzerinde çalıştırabildiğimiz terminal konsolları bulunuyor) Bu nedenle herhangi birinde deneyimlediğimiz tecrübeleri diğerlerinde tatbik etmek de mümkün. Bu platformlarda hayatın nasıl işlediğini anlamak için sundukları dokümanlardan yararlanmaksa en mantıklısı. 

Bu geceki maceramızda Azure'un App Service olarak isimlendirilen ürününü kullanarak node.js ile yazılmış bir web uygulamasını bulut üzerinde yayınlamaya çalışacağız. Daha önceden benzer senaryoları AWS üzerinde deneyimleme fırsatım olmuştu. Son zamanlarda da Plursalsight'tan Azure konulu eğitimleri izlemekteyim. Tüm bunlar beni bu yazıya itmiş durumda diyebilirim.

Bu yazıdaki örneği kendiniz denemek isterseniz Azure'da bir aboneliğinizin olması gerektiğini hatırlatmak isterim. Ben, Free Tier adı verilen ücretsiz hesap ile söz konusu örneği geliştirmekteyim. Sizde ilk deneyimleriniz için bu planı değerlendirebilirsiniz.

Yazımızdaki temel amacımız Azure'un desteklediği dillerden birisini kullanarak geliştirilen uygulamayı buluta alıp yayınlamaktan ibaret. Konuyu araştırdığım tarih itibariyle PHP, Java, Ruby, Go ve pek tabii .Net Core için destek sunuluyor. Ben elimin bir süredir de sıcak durduğu Node.js dilini seçtim. Azure'un App Service hizmetini Linux tabanlı bir ortam üzerinde deneyimleyeceğiz. Aslında App Service bir web hosting hizmeti olarak düşünülebilir. Kurulumu oldukça kolaydır ve bir plana bağlandığında dağıtım gibi işlemlerde basittir. App Service üzerine Azure'da bir çok hazır şablon bulunmaktadır. Mobil uygulamalardan, medya hizmetlerine, Joomla menşeli blog alt yapısından Asp.Net başlangıç paketine kadar bir çok kullanıma hazır uygulama servisini bu kaynak altında bulabiliriz. Bu detayları bir kenara bırakarak devam edelim.

Gelelim işlemlerimizi nerede yapacağımıza? Evet son derece aptal bir web uygulamamız olacak ama işin en önemli kısmını Azure üzerinde yapacağız. Buradaki operasyonel işlemler için portal üzerindeki Cloud Shell isimli terminalden faydalanacağız. Kodun kendisi local makinemizde yazacağız(Benim için West-World oluyor) Yerel bilgisayardaki web uygulamasını Azure App Service üzerine kuracağımız ortama alınması içinse git'ten yararlanacağız.

Sıralı bir şekilde gittiğimiz takdirde çok da kafa karıştırıcı olmayan basit işlemler icra edeceğimizi ifade edebilirim. Haydi gelin işe Cloud Shell'i açarak başlayalım. Tabii öncelikle portal'a girmemiz ve geçerli bir abonelik üzerinden oturum açmamız gerekiyor. Cloud Shell iki seçenek sunan bir terminal arabirimi. Bash Shell veya Windows Powershell kullanabiliriz. Ben Bash Shell seçeneğini tercih ettim. Bu durumda aşağıdaki ekran görüntüsü ile karşılaşmalıyız.

Yazıdaki işlemlerimizi Cloud Shell aracılığı ile yapacağız ama Azure'un komut satırı aracını yerel makine üzerinden de kullanabiliriz. Bunun için şu adrese uğramanızı önerebilirim.

Operasyonel işlemlerimiz için az isimli komut satırı programından yararlanacağız. Yazıyı hazırladığım tarih itibariyle Common Language Interface'in 2.0 sürümü kullanılıyordu. Cloud Shell tahmin edeceğiniz üzere online bir terminal ve üzerinde çalışmakta olduğumuz sanal bir makine. Yapacağımız ilk iş aşağıdaki terminal komutunu vererek dağıtım operasyonunu üstlenecek bir kullanıcı oluşturmak.

az webapp deployment user set --user-name abi-wan-kenobi --password <<buraya okkalı bir şifre girin>>

Bir deployment kullanıcısı oluşturduk. Bu kullanıcı yerel makinedeki kodları Azure platformuna git üzerinden aktarırken gerekli olacak. Ancak tek yol git kullanmak da değil. FTP bağlantısı yaparak da uygulamamızı taşımamız mümkün ki bu senaryoda da yukarıda oluşturulan kullanıcıya ihtiyacımız olacaktır. Diğer platformlardakine benzer olarak kullanıcılar ve kullanıcıların yetkileri önemli. Gerçekten belli işler için sadece yapacaklarına ait yetkileri taşıyan kullanıcılar oluşturma alışkanlığını kazanmak gerekiyor. Kısacası her şeyi yapabilen tek bir süper kullanıcı kullanmamalıyız. İşte bu yüzden senaryomuza sadece dağıtım işinden sorumlu olacak bir kullanıcı tanımı ile başladık. 

Sıradaki adımda bir Resource Group oluşturacağız. Bunu n sayıda kaynağı barındıran bir taşıyıcı(container) olarak düşünebiliriz. Azure üzerindeki enstrümanlar birer kaynak(resource) olarak tanımlanmakta. Sanal makine(Virtual Machine), veri tabanı(database), dağıtım yapan kullanıcı(deployment user), depolama alanı(disk storage), bu örnekteki web uygulaması(web app) vs. Senaryoya göre bir kaynak grubunun tanımlanmasının yönetimsel açıdan avantajları bulunuyor. Kaynak gruplarını tanımlarken önemli kriterlerden birisi da lokasyon. Aslında kaynaklar Microsoft'un dünya üzerindeki farklı lokasyonlarında konuşlandırılmış da olabilirler. Dolayısıyla bir Resource Group farklı lokasyonlarda duran kaynakları içerebilir. Aslında içermekten kasıt sadece metadata'sında bu kaynakların bilgilerini tutmasıdır. Sonuçta Resource Group'un sahip olduğu metadata'nın da bir yerlerde duruyor olması gerekir. Kullanılabilecek lokasyonların listesini Cloud Shell'den öğrenmek de mümkün. Örneğin aşağıdaki terminal komutunun çıktısı olarak Linux tabanlı ve App Service desteği sunan lokasyonları görebiliriz.

az appservice list-locations --sku S1 --linux-workers-enabled

Ben East US 2 bölgesinde milano-rg isimli bir kaynak grubu tanımlamaya karar verdim. Bunun için aşağıdaki terminal komutundan yararlanılabilir.

az group create --name milano-rg --location "East US 2"

(az terminal komutlarının çıktısı dikkat edeceğiniz üzere JSON formatında) Son terminal komutundaki çıktya göre provisioningState için Succeeded değeri dönüldü. Yani grup başarılı bir şekilde oluşturuldu.

Şimdi App Service için bir plan oluşturacağız. Bir plan içerisinde genellikle ücretlendirme modeli ve taşıyıcı tipi gibi bilgiler bulunur. Söz gelimi bu örnek kapsamında en uygun ve ucuz olan kiralama modeline sahip sanal makineyi seçip taşıyıcı tipi olarak da Linux çekirdekli bir ortamı tercih etmek istersek aşağıdaki terminal komutunu çalıştırmamız yeterli olacaktır.

az appservice plan create --name milano-app-plan --resource-group milano-rg --sku S1 --is-linux

--sku S1 ile S1 kodlu fiyatlandırma modelini kullanacağımızı, --is-linux ile de Linux Container üzerinde çalışacağımızı belirtmiş olduk. Özellikle planları oluştururken gereken ücretlendirme modellerine bakmakta yarar var. Şu adresten gerekli bilgilere ulaşabilirsiniz. Bir çok plan söz konusudur. Planlarda belirtilen sku'larda makinenin çekirdek sayısı, günlük yedek alma miktarı, kaç Gb Ram'e sahip olacağı, disk kapasitesi, hangi diğer uygulama servislerini sunacağı(SQL, Biztalk vs), eş zamanlı instance değerleri gibi özellikler tanımlıdır. App Service ile App Service Plan arasında kritik bir ilişki de vardır. Birden fazla App Service'in aynı App Service Plan'a bağlanması mümkündür. Yani farklı uygulamaları barındıran farklı App Service örneklerini aynı servis planı ile ilişkilendirebiliriz. Bunun ölçeklemelerde önemli bir artısı vardır. Tek bir planı yukarı(Scale Up) veya aşağı(Scale Down)çekerek kendisine bağlı olan tüm uygulamaların bu ölçeklemeden aynı anda yararlanmasını sağlayabiliriz. Burada ister yukarı ister aşağı yönlü ölçekleme olsun, ilgili App Service Plan'a ait makine örneklerinin sayısının arttırılması veya azaltılması durumu söz konusudur.

Araya bir ekran görüntüsü koyarsam sanırım daha anlaşılır olabilir. Node.js Starter Kit tipinden bir App Service ve buna bağlı bir plan seçerken Azure...

Planımızı komut satırından oluşturarak devam edelim.

Şu ana kadar bir Deployment User, Resource Group ve App Service Plan oluşturduk. Peki uygulama nerede? Öncelikle Node.js'in çalışabileceği bir imaja ihityacımız var. Aslında Azure'casını ifade edersek bir Web App üretmemiz gerekiyor. Dilersek Azure'un desteklediği Linux tabanlı Web App çalışma zamanlarına bakabiliriz. Bunun için aşağıdaki terminal komutunu kullanmamız yeterli.

az webapp list-runtimes --linux

Ruby, Node'un epey bir sürümü, PHP, .Net Core (2.1 olmaması yazıyı hazırladığım tarih itibariyle ilginçti), Java ve Go...Node'un 9.4 versiyonunu destekleyecek bir Web App oluşturmak için terminalden aşağıdaki komutu vermek yeterli.

az webapp create --resource-group milano-rg --plan milano-app-plan --name FishingServices --runtime "NODE|9.4" --deployment-local-git

Komutta kullandığımız bir kaç parametre var. Resource Group, App Service Plan, uygulamanın adı(ki örneğimizde FishingServices olarak geçiyor), çalışma zamanı(node 9.4'ü seçtik) ve deployment seçeneği(bu da Git olarak belirtildi) Buna göre uzun bir JSON çıktısına sahip olacağız ancak içerisinde bizim için önemli bilgiler var. 

Birisi deploymentLocalGitUrl ve diğeri de defaultHostName. Çıktıya dikkat edilecek olursa fishingservices.azurewebsites.net isimli bir adres yer alıyor. Eğer bu adrese gidersek aşağıdakine benzer bir çıktı ile karşılaşmamız olası(İlk talepte cevap süresi biraz uzun olabilir)

Bu hazır bir şablon ancak şu noktada Azure App Service üzerinden bir Web Hosting işlemi gerçekleştirdiğimizi söyleyebiliriz. Tabii amacımız buraya kendi yazdığımız Node.js uygulamasını taşımak. Ben bunun için West-World'deki Visual Studio Code'u kullanarak aşağıdaki içeriğe sahip basit bir index.js dosyası oluşturdum. Internet'te konu ile ilgili örnek dokümanlara baktığınızda da benzer kod parçaları ile karşılaşabilirsiniz. Temel amacımızın Azure tarafı olduğunu hatırlatalım.

var http = require('http');

var server = http.createServer(function(request, response) {

    response.writeHead(200, {"Content-Type": "text/html"});
    response.end("<h1>Fishing services and utilities.</h1><p>Under Construction</p>");

});

var port = process.env.PORT || 4454;
server.listen(port);

console.log("Server is online http://localhost:%d", port);

Ekrana çok düz bir HTML içeriği basılıyor. Bunun için createServer metoduna alınan callback fonksiyonundan yararlanılmakta. writeHead ile istemciye HTTP 200 bilgisini döndürüyoruz. Yani her şey yolunda. end fonksiyonunun içerisindeyse tahmin edeceğiniz üzere HTML içeriğimiz yer alıyor. Oluşturulan server nesnesinin listen fonksiyonu ile de 4454 numaralı porttan yayın hizmet vereceğimizi ifade ediyoruz. Tabii bu lokal makine için geçerli. Uygulamayı Azure ortamına taşıdığımızda port bilgisi process.env.PORT üzerinden otomatik olarak elde edilecek. Bu arada kodun olduğu klasörde

npm init

ile package.json dosyasını oluşturup içeriğini aşağıdaki gibi düzenleyebiliriz.

{
  "name": "fishing-app",
  "version": "1.0.0",
  "description": "sample azure app",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js"
  },
  "author": "burak selim senyurt",
  "license": "ISC"
}

start elementini elle eklememiz gerekiyor. Bildiğiniz gibi bu sayede söz konusu uygulamayı terminalde

npm start

komutunu vererek başlatabiliyoruz ki bu aynı zamanda Azure tarafındaki ortam için de gerekli.

Peki şimdi ne olacak? Azure'a uygulamayı taşıyabileceğimiz bir Web App ekledik. Lokal makinemizde de Node.js ile yazılmış ve çalışan bir programımız var. Hazırlıklarımıza göre geliştirici makinesindeki uygulamayı git ile Azure'a alabiliriz. Bunun için ilk adım olarak yerel git deposunu Azure'a bağlamamız gerekiyor. West-World için bu işlem aşağıdaki terminal komutu ile sağlanabildi.

git remote add milano https://abi-wan-kenobi@fishingservices.scm.azurewebsites.net/FishingServices.git

Azure tarafında üretilen git adresini uzak bir bağlantı seçeneği olarak ekliyoruz. Bundan sonra tek yapılması gerekense 

git push milano master

komutunu çalıştırmak. Yani kodlarımızı milano olarak isimlendirdiğimiz uzak adrese doğru aktarmak. Bu işlem sırasında bir şifre de sorulacaktır. Bilin bakalım bu şifreyi ne zaman ve nerede belirledik :)  

Dağıtım işlemi başarılı bir şekilde tamamlandıktan sonra fishingservices.azurewebsites.net adresine tekrar gidersek içeriğin değiştiğini ve Node.js ile yazdığımız uygulamanın çalıştığını rahatlıkla görebiliriz.

Uygulama kodunda değişiklikler yapacak olursak standart commit işlemini uygulayıp ardından tekrardan push ile dağıtımı yapmamız gerektiğini hatırlatayım(Bunu bir deneyin derim. Hatta uygulamayı bir Web API servisi haline getirip commit'lemeyi ve bu şekilde dağıtmayı deneyebilirsiniz. Özellikle bu durumda uygulamanın bağımlı olduğu npm paketleri varsa bunları karşı tarafa da aktarmak gerekebilir. Acaba burada nasıl bir yol izlenmelidir?) Bu arada eğer portal üzerinden kaynaklara gidersek FishingServices isimli App Service örneğimizi de görebiliriz. Aşağıdaki ekran görüntüsünde kendi hesabımdaki anlık durum yer alıyor. 

Sonuçlar oldukça tatmin edici öyle değil mi? Biraz fazla terminal komutu kullandık ama adım adım oluşumu anladık diye düşünüyorum(En azından benim kafamda biraz daha netleşti) Pek tabii bu ücretsiz planlar bir süre sonra başa dert olabilirler. O nedenle oluşturduklarımızı silersek iyi olabilir ki Microsoft'un kendi öğreti dokümanlarında da bu önerilmekte. İşte bu nokta bir Resource Group oluşturmanın faydasını da göreceğiz. Aşağıdaki terminal komutunu Cloud Shell'den çalıştırdığımızda milano-rg ile ilişkili olarak oluşturulan ne kadar kaynak varsa otomatik olarak silinecek.

az group delete --name milano-rg

Ve tabii Fishing Services isimli balıkçı malzemeleri hizmeti veren firmanın sitesi de aşağıdaki hale gelecek.

Hepsi bu kadar :)

Bu yazımızda kendi bilgisayarımızdaki bir Node.js uygulamasının Azure App Service üzerine nasıl dağıtılabileceğini incelemeye çalıştık. Ağırlıklı olarak(hatta tamamen) terminal komutlarından yararlandık. Önce dağıtım işlemini üstlenen bir kullanıcı oluşturduk. Kaynaklara ait bilgileri içeren bir Resource Group tanımlaması ile devam ettik. App Service için gerekli planımızı belirledik ve bir Web App oluşturulmasını sağladık. Son olarak yazdığımız basit Node.js uygulamasını taşımak için git aracından faydalandık. Pekala aynı işlemleri Azure Portal üzerinden görsel olarak da gerçekleştirebiliriz. Sizler örneği çok daha uç noktalara taşıyabilirsiniz. Eğer kendi bilgisayarınızda geliştirdiğiniz güzel bir web uygulamanız varsa(hatta bloğunuz) bunu Azure üzerinde konuşlandırmanız son derece kolay. Ama bunu yaparken kiralama modellerine(planlara) bakmayı da ihmal etmeyin. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Azure ile İlk Maceram (App Service)

$
0
0

Merhaba Arkadaşlar,

Sinema tarihinin en araştırmacı en gözüpek en maceraperest arkeoloğu kimdir desek herhalde aklımıza tek bir isim gelir; Indiana Jones. Geçenlerde DVD arşivimden şöyle yanında patlamış mısırla izleyeceğim güzel bir macera filmi bakıyordum. Bu yaşıma kadar aslında bir çok ünlü seriyi arşivime eklemiştim. Baba, Matrix, Mad Max, Star Wars, Back to the Future, Terminator, Lord of the Rings ve diğerleri. Derken Indiana Jones çıktı karşıma ve gecenin izlencesi belli oldu. Mısırlar patlatıldı, naneli limonatalar hazırlandı, DVD takıldı, perdeler indirildi ve seyir başladı. Pek tabii Indiana Jones'un bir profesör olmasından çok atıldığı maceralardı seyirciyi ekrana bağlayan. Ona can veren Harrison Ford'un ince esprileri de cabasıydı. Filmi büyük bir keyifle tamamladıktan sonra geçtim West-World'ün başına. Bir Indiana Jones değildim ama benim de kendi çapımda minik maceralarım vardı. Sıradaki serüven yüksek tepelerin ardında, ihtişamlı bulutları ile göz kamaştıran Azure hanedanlığına doğru olacaktı.

Bulut bilişim dünyasının başrol oyuncularını düşündüğümüzde karşımıza Amazon Web Services, Google Cloud Platform ve Microsoft Azure çıkıyor(İlk harflere göre sıralayarak yazdım :P) Neredeyse hepsinin benzer amaç, araç ve sunduğu hizmetler var(Düşünsenize hepsinde mutlaka tarayıcı üzerinde çalıştırabildiğimiz terminal konsolları bulunuyor) Bu nedenle herhangi birinde deneyimlediğimiz tecrübeleri diğerlerinde tatbik etmek de mümkün. Bu platformlarda hayatın nasıl işlediğini anlamak için sundukları dokümanlardan yararlanmaksa en mantıklısı. 

Bu geceki maceramızda Azure'un App Service olarak isimlendirilen ürününü kullanarak node.js ile yazılmış bir web uygulamasını bulut üzerinde yayınlamaya çalışacağız. Daha önceden benzer senaryoları AWS üzerinde deneyimleme fırsatım olmuştu. Son zamanlarda da Plursalsight'tan Azure konulu eğitimleri izlemekteyim. Tüm bunlar beni bu yazıya itmiş durumda diyebilirim.

Bu yazıdaki örneği kendiniz denemek isterseniz Azure'da bir aboneliğinizin olması gerektiğini hatırlatmak isterim. Ben, Free Tier adı verilen ücretsiz hesap ile söz konusu örneği geliştirmekteyim. Sizde ilk deneyimleriniz için bu planı değerlendirebilirsiniz.

Yazımızdaki temel amacımız Azure'un desteklediği dillerden birisini kullanarak geliştirilen uygulamayı buluta alıp yayınlamaktan ibaret. Konuyu araştırdığım tarih itibariyle PHP, Java, Ruby, Go ve pek tabii .Net Core için destek sunuluyor. Ben elimin bir süredir de sıcak durduğu Node.js dilini seçtim. Azure'un App Service hizmetini Linux tabanlı bir ortam üzerinde deneyimleyeceğiz. Aslında App Service bir web hosting hizmeti olarak düşünülebilir. Kurulumu oldukça kolaydır ve bir plana bağlandığında dağıtım gibi işlemlerde basittir. App Service üzerine Azure'da bir çok hazır şablon bulunmaktadır. Mobil uygulamalardan, medya hizmetlerine, Joomla menşeli blog alt yapısından Asp.Net başlangıç paketine kadar bir çok kullanıma hazır uygulama servisini bu kaynak altında bulabiliriz. Bu detayları bir kenara bırakarak devam edelim.

Gelelim işlemlerimizi nerede yapacağımıza? Evet son derece aptal bir web uygulamamız olacak ama işin en önemli kısmını Azure üzerinde yapacağız. Buradaki operasyonel işlemler için portal üzerindeki Cloud Shell isimli terminalden faydalanacağız. Kodun kendisi local makinemizde yazacağız(Benim için West-World oluyor) Yerel bilgisayardaki web uygulamasını Azure App Service üzerine kuracağımız ortama alınması içinse git'ten yararlanacağız.

Sıralı bir şekilde gittiğimiz takdirde çok da kafa karıştırıcı olmayan basit işlemler icra edeceğimizi ifade edebilirim. Haydi gelin işe Cloud Shell'i açarak başlayalım. Tabii öncelikle portal'a girmemiz ve geçerli bir abonelik üzerinden oturum açmamız gerekiyor. Cloud Shell iki seçenek sunan bir terminal arabirimi. Bash Shell veya Windows Powershell kullanabiliriz. Ben Bash Shell seçeneğini tercih ettim. Bu durumda aşağıdaki ekran görüntüsü ile karşılaşmalıyız.

Yazıdaki işlemlerimizi Cloud Shell aracılığı ile yapacağız ama Azure'un komut satırı aracını yerel makine üzerinden de kullanabiliriz. Bunun için şu adrese uğramanızı önerebilirim.

Operasyonel işlemlerimiz için az isimli komut satırı programından yararlanacağız. Yazıyı hazırladığım tarih itibariyle Common Language Interface'in 2.0 sürümü kullanılıyordu. Cloud Shell tahmin edeceğiniz üzere online bir terminal ve üzerinde çalışmakta olduğumuz sanal bir makine. Yapacağımız ilk iş aşağıdaki terminal komutunu vererek dağıtım operasyonunu üstlenecek bir kullanıcı oluşturmak.

az webapp deployment user set --user-name abi-wan-kenobi --password <<buraya okkalı bir şifre girin>>

Bir deployment kullanıcısı oluşturduk. Bu kullanıcı yerel makinedeki kodları Azure platformuna git üzerinden aktarırken gerekli olacak. Ancak tek yol git kullanmak da değil. FTP bağlantısı yaparak da uygulamamızı taşımamız mümkün ki bu senaryoda da yukarıda oluşturulan kullanıcıya ihtiyacımız olacaktır. Diğer platformlardakine benzer olarak kullanıcılar ve kullanıcıların yetkileri önemli. Gerçekten belli işler için sadece yapacaklarına ait yetkileri taşıyan kullanıcılar oluşturma alışkanlığını kazanmak gerekiyor. Kısacası her şeyi yapabilen tek bir süper kullanıcı kullanmamalıyız. İşte bu yüzden senaryomuza sadece dağıtım işinden sorumlu olacak bir kullanıcı tanımı ile başladık. 

Sıradaki adımda bir Resource Group oluşturacağız. Bunu n sayıda kaynağı barındıran bir taşıyıcı(container) olarak düşünebiliriz. Azure üzerindeki enstrümanlar birer kaynak(resource) olarak tanımlanmakta. Sanal makine(Virtual Machine), veri tabanı(database), dağıtım yapan kullanıcı(deployment user), depolama alanı(disk storage), bu örnekteki web uygulaması(web app) vs. Senaryoya göre bir kaynak grubunun tanımlanmasının yönetimsel açıdan avantajları bulunuyor. Kaynak gruplarını tanımlarken önemli kriterlerden birisi da lokasyon. Aslında kaynaklar Microsoft'un dünya üzerindeki farklı lokasyonlarında konuşlandırılmış da olabilirler. Dolayısıyla bir Resource Group farklı lokasyonlarda duran kaynakları içerebilir. Aslında içermekten kasıt sadece metadata'sında bu kaynakların bilgilerini tutmasıdır. Sonuçta Resource Group'un sahip olduğu metadata'nın da bir yerlerde duruyor olması gerekir. Kullanılabilecek lokasyonların listesini Cloud Shell'den öğrenmek de mümkün. Örneğin aşağıdaki terminal komutunun çıktısı olarak Linux tabanlı ve App Service desteği sunan lokasyonları görebiliriz.

az appservice list-locations --sku S1 --linux-workers-enabled

Ben East US 2 bölgesinde milano-rg isimli bir kaynak grubu tanımlamaya karar verdim. Bunun için aşağıdaki terminal komutundan yararlanılabilir.

az group create --name milano-rg --location "East US 2"

(az terminal komutlarının çıktısı dikkat edeceğiniz üzere JSON formatında) Son terminal komutundaki çıktya göre provisioningState için Succeeded değeri dönüldü. Yani grup başarılı bir şekilde oluşturuldu.

Şimdi App Service için bir plan oluşturacağız. Bir plan içerisinde genellikle ücretlendirme modeli ve taşıyıcı tipi gibi bilgiler bulunur. Söz gelimi bu örnek kapsamında en uygun ve ucuz olan kiralama modeline sahip sanal makineyi seçip taşıyıcı tipi olarak da Linux çekirdekli bir ortamı tercih etmek istersek aşağıdaki terminal komutunu çalıştırmamız yeterli olacaktır.

az appservice plan create --name milano-app-plan --resource-group milano-rg --sku S1 --is-linux

--sku S1 ile S1 kodlu fiyatlandırma modelini kullanacağımızı, --is-linux ile de Linux Container üzerinde çalışacağımızı belirtmiş olduk. Özellikle planları oluştururken gereken ücretlendirme modellerine bakmakta yarar var. Şu adresten gerekli bilgilere ulaşabilirsiniz. Bir çok plan söz konusudur. Planlarda belirtilen sku'larda makinenin çekirdek sayısı, günlük yedek alma miktarı, kaç Gb Ram'e sahip olacağı, disk kapasitesi, hangi diğer uygulama servislerini sunacağı(SQL, Biztalk vs), eş zamanlı instance değerleri gibi özellikler tanımlıdır. App Service ile App Service Plan arasında kritik bir ilişki de vardır. Birden fazla App Service'in aynı App Service Plan'a bağlanması mümkündür. Yani farklı uygulamaları barındıran farklı App Service örneklerini aynı servis planı ile ilişkilendirebiliriz. Bunun ölçeklemelerde önemli bir artısı vardır. Tek bir planı yukarı(Scale Up) veya aşağı(Scale Down)çekerek kendisine bağlı olan tüm uygulamaların bu ölçeklemeden aynı anda yararlanmasını sağlayabiliriz. Burada ister yukarı ister aşağı yönlü ölçekleme olsun, ilgili App Service Plan'a ait makine örneklerinin sayısının arttırılması veya azaltılması durumu söz konusudur.

Araya bir ekran görüntüsü koyarsam sanırım daha anlaşılır olabilir. Node.js Starter Kit tipinden bir App Service ve buna bağlı bir plan seçerken Azure...

Planımızı komut satırından oluşturarak devam edelim.

Şu ana kadar bir Deployment User, Resource Group ve App Service Plan oluşturduk. Peki uygulama nerede? Öncelikle Node.js'in çalışabileceği bir imaja ihityacımız var. Aslında Azure'casını ifade edersek bir Web App üretmemiz gerekiyor. Dilersek Azure'un desteklediği Linux tabanlı Web App çalışma zamanlarına bakabiliriz. Bunun için aşağıdaki terminal komutunu kullanmamız yeterli.

az webapp list-runtimes --linux

Ruby, Node'un epey bir sürümü, PHP, .Net Core (2.1 olmaması yazıyı hazırladığım tarih itibariyle ilginçti), Java ve Go...Node'un 9.4 versiyonunu destekleyecek bir Web App oluşturmak için terminalden aşağıdaki komutu vermek yeterli.

az webapp create --resource-group milano-rg --plan milano-app-plan --name FishingServices --runtime "NODE|9.4" --deployment-local-git

Komutta kullandığımız bir kaç parametre var. Resource Group, App Service Plan, uygulamanın adı(ki örneğimizde FishingServices olarak geçiyor), çalışma zamanı(node 9.4'ü seçtik) ve deployment seçeneği(bu da Git olarak belirtildi) Buna göre uzun bir JSON çıktısına sahip olacağız ancak içerisinde bizim için önemli bilgiler var. 

Birisi deploymentLocalGitUrl ve diğeri de defaultHostName. Çıktıya dikkat edilecek olursa fishingservices.azurewebsites.net isimli bir adres yer alıyor. Eğer bu adrese gidersek aşağıdakine benzer bir çıktı ile karşılaşmamız olası(İlk talepte cevap süresi biraz uzun olabilir)

Bu hazır bir şablon ancak şu noktada Azure App Service üzerinden bir Web Hosting işlemi gerçekleştirdiğimizi söyleyebiliriz. Tabii amacımız buraya kendi yazdığımız Node.js uygulamasını taşımak. Ben bunun için West-World'deki Visual Studio Code'u kullanarak aşağıdaki içeriğe sahip basit bir index.js dosyası oluşturdum. Internet'te konu ile ilgili örnek dokümanlara baktığınızda da benzer kod parçaları ile karşılaşabilirsiniz. Temel amacımızın Azure tarafı olduğunu hatırlatalım.

var http = require('http');

var server = http.createServer(function(request, response) {

    response.writeHead(200, {"Content-Type": "text/html"});
    response.end("<h1>Fishing services and utilities.</h1><p>Under Construction</p>");

});

var port = process.env.PORT || 4454;
server.listen(port);

console.log("Server is online http://localhost:%d", port);

Ekrana çok düz bir HTML içeriği basılıyor. Bunun için createServer metoduna alınan callback fonksiyonundan yararlanılmakta. writeHead ile istemciye HTTP 200 bilgisini döndürüyoruz. Yani her şey yolunda. end fonksiyonunun içerisindeyse tahmin edeceğiniz üzere HTML içeriğimiz yer alıyor. Oluşturulan server nesnesinin listen fonksiyonu ile de 4454 numaralı porttan yayın hizmet vereceğimizi ifade ediyoruz. Tabii bu lokal makine için geçerli. Uygulamayı Azure ortamına taşıdığımızda port bilgisi process.env.PORT üzerinden otomatik olarak elde edilecek. Bu arada kodun olduğu klasörde

npm init

ile package.json dosyasını oluşturup içeriğini aşağıdaki gibi düzenleyebiliriz.

{
  "name": "fishing-app",
  "version": "1.0.0",
  "description": "sample azure app",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js"
  },
  "author": "burak selim senyurt",
  "license": "ISC"
}

start elementini elle eklememiz gerekiyor. Bildiğiniz gibi bu sayede söz konusu uygulamayı terminalde

npm start

komutunu vererek başlatabiliyoruz ki bu aynı zamanda Azure tarafındaki ortam için de gerekli.

Peki şimdi ne olacak? Azure'a uygulamayı taşıyabileceğimiz bir Web App ekledik. Lokal makinemizde de Node.js ile yazılmış ve çalışan bir programımız var. Hazırlıklarımıza göre geliştirici makinesindeki uygulamayı git ile Azure'a alabiliriz. Bunun için ilk adım olarak yerel git deposunu Azure'a bağlamamız gerekiyor. West-World için bu işlem aşağıdaki terminal komutu ile sağlanabildi.

git remote add milano https://abi-wan-kenobi@fishingservices.scm.azurewebsites.net/FishingServices.git

Azure tarafında üretilen git adresini uzak bir bağlantı seçeneği olarak ekliyoruz. Bundan sonra tek yapılması gerekense 

git push milano master

komutunu çalıştırmak. Yani kodlarımızı milano olarak isimlendirdiğimiz uzak adrese doğru aktarmak. Bu işlem sırasında bir şifre de sorulacaktır. Bilin bakalım bu şifreyi ne zaman ve nerede belirledik :)  

Dağıtım işlemi başarılı bir şekilde tamamlandıktan sonra fishingservices.azurewebsites.net adresine tekrar gidersek içeriğin değiştiğini ve Node.js ile yazdığımız uygulamanın çalıştığını rahatlıkla görebiliriz.

Uygulama kodunda değişiklikler yapacak olursak standart commit işlemini uygulayıp ardından tekrardan push ile dağıtımı yapmamız gerektiğini hatırlatayım(Bunu bir deneyin derim. Hatta uygulamayı bir Web API servisi haline getirip commit'lemeyi ve bu şekilde dağıtmayı deneyebilirsiniz. Özellikle bu durumda uygulamanın bağımlı olduğu npm paketleri varsa bunları karşı tarafa da aktarmak gerekebilir. Acaba burada nasıl bir yol izlenmelidir?) Bu arada eğer portal üzerinden kaynaklara gidersek FishingServices isimli App Service örneğimizi de görebiliriz. Aşağıdaki ekran görüntüsünde kendi hesabımdaki anlık durum yer alıyor. 

Sonuçlar oldukça tatmin edici öyle değil mi? Biraz fazla terminal komutu kullandık ama adım adım oluşumu anladık diye düşünüyorum(En azından benim kafamda biraz daha netleşti) Pek tabii bu ücretsiz planlar bir süre sonra başa dert olabilirler. O nedenle oluşturduklarımızı silersek iyi olabilir ki Microsoft'un kendi öğreti dokümanlarında da bu önerilmekte. İşte bu nokta bir Resource Group oluşturmanın faydasını da göreceğiz. Aşağıdaki terminal komutunu Cloud Shell'den çalıştırdığımızda milano-rg ile ilişkili olarak oluşturulan ne kadar kaynak varsa otomatik olarak silinecek.

az group delete --name milano-rg

Ve tabii Fishing Services isimli balıkçı malzemeleri hizmeti veren firmanın sitesi de aşağıdaki hale gelecek.

Hepsi bu kadar :)

Bu yazımızda kendi bilgisayarımızdaki bir Node.js uygulamasının Azure App Service üzerine nasıl dağıtılabileceğini incelemeye çalıştık. Ağırlıklı olarak(hatta tamamen) terminal komutlarından yararlandık. Önce dağıtım işlemini üstlenen bir kullanıcı oluşturduk. Kaynaklara ait bilgileri içeren bir Resource Group tanımlaması ile devam ettik. App Service için gerekli planımızı belirledik ve bir Web App oluşturulmasını sağladık. Son olarak yazdığımız basit Node.js uygulamasını taşımak için git aracından faydalandık. Pekala aynı işlemleri Azure Portal üzerinden görsel olarak da gerçekleştirebiliriz. Sizler örneği çok daha uç noktalara taşıyabilirsiniz. Eğer kendi bilgisayarınızda geliştirdiğiniz güzel bir web uygulamanız varsa(hatta bloğunuz) bunu Azure üzerinde konuşlandırmanız son derece kolay. Ama bunu yaparken kiralama modellerine(planlara) bakmayı da ihmal etmeyin. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

http://www.buraksenyurt.com/post/azure-azure-bu-fotografta-ne-goruyorsunAzure, Azure! Bu Fotoğrafta Neler Görüyorsun?

$
0
0

Merhaba Arkadaşlar,

Yandaki fotoğrafa baktığınızda neler görüyorsunuz? Hatta neler hissediyorsunuz? Kalın lastikleri olan koyu yeşil renkte Toyota marka bir arazi aracı. Aracın içine bakabilmesi için ufaklığı kollarıyla kaldıran arkası dönük bir kadın. Arka tarafta turuncu kapısı görünen bir başka araç. Olayla pek ilgisi olmayan ilkokul çağında sarışın kıvırcık saçlı bir kız çocuğu. Bluzundaki sembollerden çıkartıldığı kadarıyla Minion'lar. Arka tarafta yükselene sıra dağlar ve diğerleri. İnsan gözüyle fotoğraf dikkatlice incelelendiğinde söyleyebileceklerimizden sadece bazıları. Hatta insani duygularla hareket ederek empati yaptığımızda göreceğimiz farklı detaylar da var öyle değil mi? Ufaklığın yüzündeki meraklı bakışa, aracın içini görmek istercesine annesinin kollarında yukarıya doğru yükselmeye çalışmasına bir baksanıza. Ya da cansız bile olsa aracın tekerlekleri ile ne kadar agresif göründüğüne. İşte bu farklılıkları ve detayları görmek belki de biz insanları makinelerden, düşünmeye çalışan robotlardan ayıran önemli bir özellik. 

Ancak oyunun kuralları bildiğiniz gibi uzun süre önce değişmeye başladı. Önce dijital fotoğraf makinelerinin, kameraların gördükleri karelerdeki yüzleri ayırt edişlerine şahit olmaya başladık. Hatta hareket edenleri nasıl yakaladıklarını gördük. Sonra onların renklerini ayırt edebildiklerini ve az çok o fotoğraflarda neler bulunduğunu tahminlemeye çalıştıklarını. Halen daha da görmeye devam ediyoruz. Artık yapay zekanın, öğrenen makinelerin dünyasında olduğumuz için bu fotoğrafı yorumlayışımıza yakınlaşmaları çok da uzak değil gibi. Ne kadar yaklaşabileceklerini zaman içerisinde daha net göreceğiz ama bu çok uzak bir gelecek değil. Öğreniyorlar...

Bugünün gelecek teknolojilerini belirleyen büyük aktörlerin çoğu, bu tip tanıma/tanımlama operasyonlarını sunan servislere sahipler. Google, Amazon, Microsoft, IBM, Facebook ve diğerlerinin başı çektiği bir dünya var artık. Özellikle bulut hesaplamaları alanında hizmet verenlerin sahip olduğu avantajlar yukarıdaki gibi bir senaryonun saniyeler içerisinde gerçeklenmesine de olanak sağlamakta. Bende bu merakla bir şeyler araştırmayı başladım geçenlerde. Pluralsight sağolsun Azure konusundaki çalışmalarıma devam ediyorum. Ufak ufak öğretilerin üzerinden geçerken de neyin nasıl yapıldığını adım adım öğrenmeye çalışıyorum. Bu yazımızda ise uzun zamandır hepimizin varlığından haberdar olduğu Cognitive Services özelliklerinden birisine bakacağız. Azure'un yapay zeka destekli makine öğrenme hizmetlerinden olan Compture Vision enstrümanını kullanarak bir fotoğrafı bizim için nasıl yorumlayableceğini işleyeceğiz.

Microsoft Cognitive Services temel olarak 5 ana kategoriye ayrılmıştır. Vision, Speech, Language, Knowledge ve Search. Her bir kategori başlığı altında bu alana özgü farklı fonksiyonellikler sunulmaktadır. İlgili listeye şu adresten bir bakmanızı öneririm.

İlk olarak Azure platformunda bu hizmeti kullanabilmek için gerekli hazırlıkları yapıp sonrasında örnek bir kod parçası ile bir kaç fotoğrafı yorumlatacağız. Bir nevi basit How To yazısı olduğunu belirtebilirim. 

Azure Plaforumundaki Hazırlıklar

İşe Azure Platformu üzerindeki hazırlıklarla başlamamız gerekiyor. Bu aşamada sizlerin Azure hesaplarınız olduğunu kabul ediyorum. Yapmamız gereken arabirimi kullanarak Cognitive Services kısmına ulaşmak. All services penceresinden aratma usulü ile bulabiliriz.

Ardından Computer Vision enstrümanını bulmalıyız. 

AI +Machine Learning kategorisinde yer alan hizmeti bulduktan sonra yeni bir örneğini oluşturabiliriz. Ben aşağıdaki ekran görüntüsünde yer alan bilgileri kullandım.

Burada söz konusu kaynak için girmemiz gereken bazı bilgiler yer alıyor. Kaynağın adı(Name), lokasyonu(Location/Data Center), dahil olacağı grup bilgisi(Resource Group) ve tabii fiyatlandırma seçeneği(Pricing Tier). F0 planı ücretsiz olduğu ve sadece öğrenme amaçlı bir çalışma yaptığımız için yeterli. Bunun dışında bir seçeneğimiz daha var. S1'e göre 1000 çağrı başına 1 dolardan başlayan bir fiyatlandırma stratejisi söz konusu. Senaryoya göre farklı bir plana da ihtiyaç duyabiliriz. Bu tamamen işlemek istediğimiz kümenin büyüklüğü, ne kadar sık işleneceği ve benzeri kriterlerle alakalı bir konu. Örneğin S1 planı saniyede 10 çağrıma izin verirken Free plan için bu dakikada 20 çağrım ve ayda en fazla 5000 çağrım ile sınırlı (Bu arada F0 planını seçtikten sonra tekrar ikinci bir Computer Vision için ikinci bir F0 planı seçemediğimi fark ettim. Ucuz etin yahnisi misali)

F0 seçiminden sonra kaynağı oluşturabiliriz.

İşlemler başarılı bir şekilde tamamlandığında read-this-photo isimli Computer Vision örneğinin başarılı bir şekilde oluşturulduğunu görmemiz gerekiyor. Kod tarafı için kritik olan iki bilgi de bu ekrandan alınacak. Bunlardan birisi servis adresi(endpoint bilgisi) Yani fotoğraf ile ilgili analizi gerçekleştirecek olan servisin kök adresi. Kök adresi diyorum çünkü duruma göre farklı bir operasyona da gidilebilir(Örneğin fotoğraf için ünlüleri sorgulamak istiyorsak celebrities/model gibi bir operasyon eklememiz gerekir) Diğeri ise iletişim sırasında gerekli olan anahtar(Key 1) değeridir. Anahtar bilgisini Manage Keys kısmından alabiliriz.

İstemci tarafından servise gelirken KEY 1 değerine ihtiyacımız olacak. 

İstemci Tarafının Geliştirilmesi

Portal tarafındaki kaynak hazırlıklarımız artık tamamlanmış durumda. Şimdi basit bir istemci uygulaması ile söz konusu servisi deneyimleyebiliriz. Ben örnek kod parçasını Visual Studio Code üzerinde C# kullanarak yazacağım. Console tipinden bir program yeterli olacaktır. Ancak farklı programlama dillerini kullanmamız da mümkün. Ruby, Java, PHP ve diğer desteklenen dillerle geliştirme yapabiliriz. Öncelikle işe aşağıdaki komut satırı ile başlayalım.

dotnet new console -o HowToComputerVision

Gelelim program.cs içeriğine.

using System;
using System.IO;
using System.Net.Http;

namespace HowToComputerVision
{
    class Program
    {
        const string endpointAddress = "https://eastus.api.cognitive.microsoft.com/vision/v1.0/analyze";
        const string key1 = "c04df99b57b6475182748ebc47d22246";

        static void Main(string[] args)
        {
            string[] samples = { "sample1.jpg", "sample2.jpg", "sample3.png"
            , "sample4.jpg", "sample5.jpg","sample6.png" };
            foreach (var sample in samples)
            {
                Analyze(sample);
            }
            Console.ReadLine();
        }

        public static async void Analyze(string photo)
        {
            using (var client = new HttpClient())
            {
                HttpResponseMessage response = null;
                client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", key1);

                var requestParameters = "visualFeatures=Categories,Description,Color,Tags&Language=en";
                var uri = endpointAddress + "?" + requestParameters;

                var fs = new FileStream(photo, FileMode.Open, FileAccess.Read);
                var bReader = new BinaryReader(fs);
                var photoData = bReader.ReadBytes((int)fs.Length);

                using (var content = new ByteArrayContent(photoData))
                {
                    content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
                    response = await client.PostAsync(uri, content);
                    string contentStr = await response.Content.ReadAsStringAsync();
                    Console.WriteLine($"\n{contentStr}\n");
                }
            }
        }
    }
}

Dilerseniz kodda neler yaptığımıza kısaca değinelim.

Aslında Computer Vision bir REST API servisi. Dolayısıyla uygun HTTP çağrılarını yapmamız servisten hizmet almak için yeterli. Tüm iş yükü Analyze metodunda. Fonksiyon içerisinde HTTP çağrısı için HttpClient nesnesinden yararlanılıyor. Servise giderken bazı gerekli bilgileri vermemiz lazım. Örneğin Ocp-Apim-Subscription-Key isimli bir Header değerinin, Azure üzerinde oluşturduğumuz Computer Vision API kaynağı için verilen KEY1 ile POST talebine eklenmesi gerekiyor. Bunu DefaultRequestHeaders.Add satırında yapıyoruz. Bunun dışında neler istediğimizi de söylememiz lazım. API için bu istekler visualFeatures parametresine atanan terimlerle belirlenmekte. Categories, Description, Color, Tags bizim örneğimizde kullanılanlar. Bunların İngilizce olarak yorumlanmasını bekliyoruz.

Kodun takip eden kısmında fotoğrafın byte tipinden içeriğine ihtiyacımız var. Nitekim servise bu içeriği göndermemiz gerekiyor. FileStream ve BinaryReader sınıflarından yararlanarak içeriği yakaladıktan sonra bir ByteArrayContent nesnesi örnekliyoruz. Bu nesnenin içerik tipini belirtmek önemli. Örnekte application/octet-stream türünden bir içerik kullanıldığı belirtilmekte. Talebi awaitable PostAsync metodu ile yolluyoruz. İlk parametre EndPoint ve ikinci parametrede fotoğraf içeriğini taşımakta. Sonuçlar response nesne örneği üzerinden ReadAsStringAsync fonksiyonu ile yakalanıp ekrana basılmakta. Analyze foksiyonu asnekron çalışan bir metod. Bu nedenle çalışma zamanında fotoğraflardan hangisi için cevap döndüyse ona ait JSON içeriği basılıyor. İşlemeye çalıştığımız sırada değil de bittikçe JSON çıktılarını alacağımızı ifade edebiliriz.

Sonuçlar

Hemen örnek fotoğrafların sonuçlarna bir bakalım. Çok heyecanlı değil mi? :) Öncelikli olarak bize deneme için bir kaç fotoğraf gerekiyor. Internetten test amaçlı farklı tiplerde fotoğraflar buldum. İlk sırada turuncu sakallı bir Lego manyağı var :P İkinci sırada masa başında bir çok insanın bulunduğu bir tartışma ortamı yer alıyor. Üçüncü sırada son Star Wars filminden sevdiğim bir kare var. Özellikle Computer Vision servisinin Yoda'yı nasıl yorumlayacağını çok merak ediyorum. Acaba ona usta yoda'yı öğretmişler midir? Devam eden fotoğraftaki beklentim ise park yapan araca yaslanmış bir şekilde ayakta duran kadının bulunup bulunamayacağı. 5nci fotoğrafı bilhassa koydum. Gerçek dünayadan olmayan bir çizgi. Bakalım karşı tarafın tepkisi ne olacak? Son fotoğrafımız ise başta konuştuğumuz içeriğe sahip.

Programın çalışma zamanı çıktısı aşağıdakine benzer olacaktır. Dikkat edileceği üzere fotoğraflar ile ilgili yorumlar servisten JSON fortamında dönmekte. İçerikte categories, description, tags ve color kök elementleri yer alıyor ki bunları istediğimizi endpoint sonuna eklediğimiz ifadelerle biz belirtmiştik.

Bu fotoğraflardan ilki için gelen JSON çıktısını kısaca değerlendirelim mi? Örneğin benim Boba Fett'in gemisinin Lego'su ile olan fotoğrafımla ilgili şöyle bir çıktı verildi(Zaman ilerledikçe Computer Vision'un fotoğraf öğrenmesi sonucu daha farklı ve isabetli cevaplar vereceğini tahmin ediyorum)

{
  "categories": [
    {
      "name": "others_",
      "score": 0.0078125
    },
    {
      "name": "outdoor_",
      "score": 0.00390625,
      "detail": {
        "landmarks": [
        ]
      }
    },
    {
      "name": "people_",
      "score": 0.42578125,
      "detail": {
        "celebrities": [          
        ]
      }
    }
  ],
  "tags": [
    {
      "name": "person",
      "confidence": 0.99906939268112183
    },
    {
      "name": "man",
      "confidence": 0.990304172039032
    },
    {
      "name": "indoor",
      "confidence": 0.986711859703064
    },
    {
      "name": "shelf",
      "confidence": 0.63831371068954468
    },
    {
      "name": "male",
      "confidence": 0.1531662791967392
    }
  ],
  "description": {
    "tags": [
      "person",
      "man",
      "indoor",
      "computer",
      "holding",
      "laptop",
      "front",
      "shelf",
      "sitting",
      "shirt",
      "using",
      "table",
      "book",
      "food",
      "remote",
      "young",
      "dog",
      "black",
      "wearing",
      "desk",
      "control",
      "large",
      "room",
      "keyboard",
      "white",
      "pizza",
      "bed",
      "video",
      "standing"
    ],
    "captions": [
      {
        "text": "a man holding a book shelf",
        "confidence": 0.66441073283724139
      }
    ]
  },
  "color": {
    "dominantColorForeground": "Grey",
    "dominantColorBackground": "Black",
    "dominantColors": [
      "Black",
      "Grey",
      "Brown"
    ],
    "accentColor": "274562",
    "isBwImg": false
  },
  "requestId": "4efc41fa-c421-4e70-bd20-c392306f6031",
  "metadata": {
    "height": 750,
    "width": 1500,
    "format": "Jpeg"
  }
}

categories kısmına baktığımızda en yüsek skor değeri insan'da görünüyor. Yani insan temalı bir fotoğraf olarak kategorilendirebiliriz. tags bölümünde önerilen takılar bulunmakta. Kapalı mekan olduğu, rafların bulunduğu, bir adamın yer aldığı belirtilmiş. confidence değeri yüksek olanlar tahmini olarak öne çıkan bilgiler. description kısmındaki takılarda fena değil aslında. Mekan ile ilgili az çok tutarlı anahtar kelimelere yer verilmiş. Her ne kadar ben pizza'nın nerede olduğunu pek çıkartamasamda fena sayılmazlar. Ağırlıklı renkler siyah, gri ve kahve olarak belirtilmiş (Renkler aslında ön plan, arka plan ve tüm resim olarak ele alınmaktalar ve 12 dominant renk ele alınmakta; Siyah, mavi, kahverengi, gri, yeşil, turuncu, pembe, mor, kırmızı, deniz mavizi, beyaz ve sarı) Fotoğraf için önerilen başlıkta hoş aslında ki benim Computer Vision'da en çok beğendiğim özelliklerden birisi de bu; "a man holding a book shelf" Diğer fotoğraflar için neler söylediğini bilmek ister misin?

  • Örneğin bir grup insanın masa başında oturduğu ikinci fotoğraf için : "a group of people sitting at a table"
  • Master Yoda ve Luke Syk Walker'ın yanyana durduğu fotoğraf için : "a man and a woman looking at the camera" (Biraz daha öğrenmesi gerekiyor nitekim hangisi için Woman dedi anlayamadım. Belki de para vermediğim için böyle dedi)
  • Kadının garaj kapısındaki arabanın önünde ayakta durduğu fotoğraf için : "a car parked on the side of a building" (Kadını yakalayamamış belki ama tanımlayıcı tag'ler içerisinde woman kelimesi yer alıyor)
  • Ünlü animasyon filmi Cars'ın renkli afişi için : "a car parked in a parking lot" (oldukça akıllı bir öneri değil mi? Gün gelecek karakterlerin isimlerini de tek tek söyleyecek)
  • ve son olarak Toyota marka arazi aracının olduğu fotoğraf için : "a group of people riding on the back of a truck"

Servisin kullanımına ilişkin bir takım çalışma zamanı bilgilerini portal üzerinden de izleyebiliriz. Sonuç itibariyle yapılan işlemleri izlemek önemli. Ne kadar talep gitti, kaçı başarılı oldu, kaçında hata alında, geriye kalan kullanım haklarımız neler, ne kadar ödedik vs... Bu örnek senaryo için Overview kısmındaki grafikler başlangıç için yeterli bilgiler vermekte. Benim yaptığım az sayıda denemenin çıktısı aşağıdaki ekran görütüsündeki gibi oldu.

Görüldüğü üzere Azure'un Cognitive servislerinden olan Computer Vision'ı kullanarak fotoğraflar ile ilgili bir takım bilgileri hesaplatmak oldukça kolay. Söz gelimi yoğun fotoğraf kullanan bir katalog sisteminde fotoğrafların tag bilgilerinin otomatik olarak çıkartılmasında bu hizmet pekala işe yarayabilir. Arka plandaki AI+ML işbirlikteliği daha da güçlendikçe fotoğrafların yorumlanması daha da iyileşecektir. Örneğin bir kamera görüntüsündeki olası saldırganın otomatik olarak tespit edildiğini bir düşünsenize(Aslında ben bu cümleyi yazarken böyle bir şeyin yapılmadığından emin değilim. Yapılıyor da olabilir. Araştırmam lazım) Azure tarafında Computer Vision servisinin pek çok gelişmiş fonksiyonu bulunuyor. Bu fonksiyonlarla resimlerin sınıflandırılması, tanımlanması, thumbnail formatlarının oluşturulması, taxonomy(SEO tarafında önem arz eden bir konudur ve yazının hazırlandığı tarih itibariyle Microsoft 86 kategori başlığından bahsediyordu) veya domain bazında kategorilendirilmesi, clip-art statüsünde olup olmadıklarının berlilenmesi, elle çizilip çizilmediklerinin anlaşılması, cinsel içerik içermediğinin tespit edilmesi ve daha bir çok şey mümkün. İlerleyen zamanlarda elbette yeni fonksiyonellikler de eklenecektir. Dilerseniz siz bu örnekten yararlanarak kendi fotoğraf albümlerinizden seçtiğiniz görüntüleri Computer Vision'a yorumlatmayı deneyebilirsiniz. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Kaynaklar:

Cognitive Services (DevNot'tan)

Microsoft'un Quickstart Dokümanı

Microsoft'un How To Call Vision Api Dokümanı

Computer Vision API

Azure, Azure! Bu Fotoğrafta Neler Görüyorsun?

$
0
0

Merhaba Arkadaşlar,

Yandaki fotoğrafa baktığınızda neler görüyorsunuz? Hatta neler hissediyorsunuz? Kalın lastikleri olan koyu yeşil renkte Toyota marka bir arazi aracı. Aracın içine bakabilmesi için ufaklığı kollarıyla kaldıran arkası dönük bir kadın. Arka tarafta turuncu kapısı görünen bir başka araç. Olayla pek ilgisi olmayan ilkokul çağında sarışın kıvırcık saçlı bir kız çocuğu. Bluzundaki sembollerden çıkartıldığı kadarıyla Minion'lar. Arka tarafta yükselene sıra dağlar ve diğerleri. İnsan gözüyle fotoğraf dikkatlice incelelendiğinde söyleyebileceklerimizden sadece bazıları. Hatta insani duygularla hareket ederek empati yaptığımızda göreceğimiz farklı detaylar da var öyle değil mi? Ufaklığın yüzündeki meraklı bakışa, aracın içini görmek istercesine annesinin kollarında yukarıya doğru yükselmeye çalışmasına bir baksanıza. Ya da cansız bile olsa aracın tekerlekleri ile ne kadar agresif göründüğüne. İşte bu farklılıkları ve detayları görmek belki de biz insanları makinelerden, düşünmeye çalışan robotlardan ayıran önemli bir özellik. 

Ancak oyunun kuralları bildiğiniz gibi uzun süre önce değişmeye başladı. Önce dijital fotoğraf makinelerinin, kameraların gördükleri karelerdeki yüzleri ayırt edişlerine şahit olmaya başladık. Hatta hareket edenleri nasıl yakaladıklarını gördük. Sonra onların renklerini ayırt edebildiklerini ve az çok o fotoğraflarda neler bulunduğunu tahminlemeye çalıştıklarını. Halen daha da görmeye devam ediyoruz. Artık yapay zekanın, öğrenen makinelerin dünyasında olduğumuz için bu fotoğrafı yorumlayışımıza yakınlaşmaları çok da uzak değil gibi. Ne kadar yaklaşabileceklerini zaman içerisinde daha net göreceğiz ama bu çok uzak bir gelecek değil. Öğreniyorlar...

Bugünün gelecek teknolojilerini belirleyen büyük aktörlerin çoğu, bu tip tanıma/tanımlama operasyonlarını sunan servislere sahipler. Google, Amazon, Microsoft, IBM, Facebook ve diğerlerinin başı çektiği bir dünya var artık. Özellikle bulut hesaplamaları alanında hizmet verenlerin sahip olduğu avantajlar yukarıdaki gibi bir senaryonun saniyeler içerisinde gerçeklenmesine de olanak sağlamakta. Bende bu merakla bir şeyler araştırmayı başladım geçenlerde. Pluralsight sağolsun Azure konusundaki çalışmalarıma devam ediyorum. Ufak ufak öğretilerin üzerinden geçerken de neyin nasıl yapıldığını adım adım öğrenmeye çalışıyorum. Bu yazımızda ise uzun zamandır hepimizin varlığından haberdar olduğu Cognitive Services özelliklerinden birisine bakacağız. Azure'un yapay zeka destekli makine öğrenme hizmetlerinden olan Compture Vision enstrümanını kullanarak bir fotoğrafı bizim için nasıl yorumlayableceğini işleyeceğiz.

Microsoft Cognitive Services temel olarak 5 ana kategoriye ayrılmıştır. Vision, Speech, Language, Knowledge ve Search. Her bir kategori başlığı altında bu alana özgü farklı fonksiyonellikler sunulmaktadır. İlgili listeye şu adresten bir bakmanızı öneririm.

İlk olarak Azure platformunda bu hizmeti kullanabilmek için gerekli hazırlıkları yapıp sonrasında örnek bir kod parçası ile bir kaç fotoğrafı yorumlatacağız. Bir nevi basit How To yazısı olduğunu belirtebilirim. 

Azure Plaforumundaki Hazırlıklar

İşe Azure Platformu üzerindeki hazırlıklarla başlamamız gerekiyor. Bu aşamada sizlerin Azure hesaplarınız olduğunu kabul ediyorum. Yapmamız gereken arabirimi kullanarak Cognitive Services kısmına ulaşmak. All services penceresinden aratma usulü ile bulabiliriz.

Ardından Computer Vision enstrümanını bulmalıyız. 

AI +Machine Learning kategorisinde yer alan hizmeti bulduktan sonra yeni bir örneğini oluşturabiliriz. Ben aşağıdaki ekran görüntüsünde yer alan bilgileri kullandım.

Burada söz konusu kaynak için girmemiz gereken bazı bilgiler yer alıyor. Kaynağın adı(Name), lokasyonu(Location/Data Center), dahil olacağı grup bilgisi(Resource Group) ve tabii fiyatlandırma seçeneği(Pricing Tier). F0 planı ücretsiz olduğu ve sadece öğrenme amaçlı bir çalışma yaptığımız için yeterli. Bunun dışında bir seçeneğimiz daha var. S1'e göre 1000 çağrı başına 1 dolardan başlayan bir fiyatlandırma stratejisi söz konusu. Senaryoya göre farklı bir plana da ihtiyaç duyabiliriz. Bu tamamen işlemek istediğimiz kümenin büyüklüğü, ne kadar sık işleneceği ve benzeri kriterlerle alakalı bir konu. Örneğin S1 planı saniyede 10 çağrıma izin verirken Free plan için bu dakikada 20 çağrım ve ayda en fazla 5000 çağrım ile sınırlı (Bu arada F0 planını seçtikten sonra tekrar ikinci bir Computer Vision için ikinci bir F0 planı seçemediğimi fark ettim. Ucuz etin yahnisi misali)

F0 seçiminden sonra kaynağı oluşturabiliriz.

İşlemler başarılı bir şekilde tamamlandığında read-this-photo isimli Computer Vision örneğinin başarılı bir şekilde oluşturulduğunu görmemiz gerekiyor. Kod tarafı için kritik olan iki bilgi de bu ekrandan alınacak. Bunlardan birisi servis adresi(endpoint bilgisi) Yani fotoğraf ile ilgili analizi gerçekleştirecek olan servisin kök adresi. Kök adresi diyorum çünkü duruma göre farklı bir operasyona da gidilebilir(Örneğin fotoğraf için ünlüleri sorgulamak istiyorsak celebrities/model gibi bir operasyon eklememiz gerekir) Diğeri ise iletişim sırasında gerekli olan anahtar(Key 1) değeridir. Anahtar bilgisini Manage Keys kısmından alabiliriz.

İstemci tarafından servise gelirken KEY 1 değerine ihtiyacımız olacak. 

İstemci Tarafının Geliştirilmesi

Portal tarafındaki kaynak hazırlıklarımız artık tamamlanmış durumda. Şimdi basit bir istemci uygulaması ile söz konusu servisi deneyimleyebiliriz. Ben örnek kod parçasını Visual Studio Code üzerinde C# kullanarak yazacağım. Console tipinden bir program yeterli olacaktır. Ancak farklı programlama dillerini kullanmamız da mümkün. Ruby, Java, PHP ve diğer desteklenen dillerle geliştirme yapabiliriz. Öncelikle işe aşağıdaki komut satırı ile başlayalım.

dotnet new console -o HowToComputerVision

Gelelim program.cs içeriğine.

using System;
using System.IO;
using System.Net.Http;

namespace HowToComputerVision
{
    class Program
    {
        const string endpointAddress = "https://eastus.api.cognitive.microsoft.com/vision/v1.0/analyze";
        const string key1 = "c04df99b57b6475182748ebc47d22246";

        static void Main(string[] args)
        {
            string[] samples = { "sample1.jpg", "sample2.jpg", "sample3.png"
            , "sample4.jpg", "sample5.jpg","sample6.png" };
            foreach (var sample in samples)
            {
                Analyze(sample);
            }
            Console.ReadLine();
        }

        public static async void Analyze(string photo)
        {
            using (var client = new HttpClient())
            {
                HttpResponseMessage response = null;
                client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", key1);

                var requestParameters = "visualFeatures=Categories,Description,Color,Tags&Language=en";
                var uri = endpointAddress + "?" + requestParameters;

                var fs = new FileStream(photo, FileMode.Open, FileAccess.Read);
                var bReader = new BinaryReader(fs);
                var photoData = bReader.ReadBytes((int)fs.Length);

                using (var content = new ByteArrayContent(photoData))
                {
                    content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
                    response = await client.PostAsync(uri, content);
                    string contentStr = await response.Content.ReadAsStringAsync();
                    Console.WriteLine($"\n{contentStr}\n");
                }
            }
        }
    }
}

Dilerseniz kodda neler yaptığımıza kısaca değinelim.

Aslında Computer Vision bir REST API servisi. Dolayısıyla uygun HTTP çağrılarını yapmamız servisten hizmet almak için yeterli. Tüm iş yükü Analyze metodunda. Fonksiyon içerisinde HTTP çağrısı için HttpClient nesnesinden yararlanılıyor. Servise giderken bazı gerekli bilgileri vermemiz lazım. Örneğin Ocp-Apim-Subscription-Key isimli bir Header değerinin, Azure üzerinde oluşturduğumuz Computer Vision API kaynağı için verilen KEY1 ile POST talebine eklenmesi gerekiyor. Bunu DefaultRequestHeaders.Add satırında yapıyoruz. Bunun dışında neler istediğimizi de söylememiz lazım. API için bu istekler visualFeatures parametresine atanan terimlerle belirlenmekte. Categories, Description, Color, Tags bizim örneğimizde kullanılanlar. Bunların İngilizce olarak yorumlanmasını bekliyoruz.

Kodun takip eden kısmında fotoğrafın byte tipinden içeriğine ihtiyacımız var. Nitekim servise bu içeriği göndermemiz gerekiyor. FileStream ve BinaryReader sınıflarından yararlanarak içeriği yakaladıktan sonra bir ByteArrayContent nesnesi örnekliyoruz. Bu nesnenin içerik tipini belirtmek önemli. Örnekte application/octet-stream türünden bir içerik kullanıldığı belirtilmekte. Talebi awaitable PostAsync metodu ile yolluyoruz. İlk parametre EndPoint ve ikinci parametrede fotoğraf içeriğini taşımakta. Sonuçlar response nesne örneği üzerinden ReadAsStringAsync fonksiyonu ile yakalanıp ekrana basılmakta. Analyze foksiyonu asnekron çalışan bir metod. Bu nedenle çalışma zamanında fotoğraflardan hangisi için cevap döndüyse ona ait JSON içeriği basılıyor. İşlemeye çalıştığımız sırada değil de bittikçe JSON çıktılarını alacağımızı ifade edebiliriz.

Sonuçlar

Hemen örnek fotoğrafların sonuçlarna bir bakalım. Çok heyecanlı değil mi? :) Öncelikli olarak bize deneme için bir kaç fotoğraf gerekiyor. Internetten test amaçlı farklı tiplerde fotoğraflar buldum. İlk sırada turuncu sakallı bir Lego manyağı var :P İkinci sırada masa başında bir çok insanın bulunduğu bir tartışma ortamı yer alıyor. Üçüncü sırada son Star Wars filminden sevdiğim bir kare var. Özellikle Computer Vision servisinin Yoda'yı nasıl yorumlayacağını çok merak ediyorum. Acaba ona usta yoda'yı öğretmişler midir? Devam eden fotoğraftaki beklentim ise park yapan araca yaslanmış bir şekilde ayakta duran kadının bulunup bulunamayacağı. 5nci fotoğrafı bilhassa koydum. Gerçek dünayadan olmayan bir çizgi. Bakalım karşı tarafın tepkisi ne olacak? Son fotoğrafımız ise başta konuştuğumuz içeriğe sahip.

Programın çalışma zamanı çıktısı aşağıdakine benzer olacaktır. Dikkat edileceği üzere fotoğraflar ile ilgili yorumlar servisten JSON fortamında dönmekte. İçerikte categories, description, tags ve color kök elementleri yer alıyor ki bunları istediğimizi endpoint sonuna eklediğimiz ifadelerle biz belirtmiştik.

Bu fotoğraflardan ilki için gelen JSON çıktısını kısaca değerlendirelim mi? Örneğin benim Boba Fett'in gemisinin Lego'su ile olan fotoğrafımla ilgili şöyle bir çıktı verildi(Zaman ilerledikçe Computer Vision'un fotoğraf öğrenmesi sonucu daha farklı ve isabetli cevaplar vereceğini tahmin ediyorum)

{
  "categories": [
    {
      "name": "others_",
      "score": 0.0078125
    },
    {
      "name": "outdoor_",
      "score": 0.00390625,
      "detail": {
        "landmarks": [
        ]
      }
    },
    {
      "name": "people_",
      "score": 0.42578125,
      "detail": {
        "celebrities": [          
        ]
      }
    }
  ],
  "tags": [
    {
      "name": "person",
      "confidence": 0.99906939268112183
    },
    {
      "name": "man",
      "confidence": 0.990304172039032
    },
    {
      "name": "indoor",
      "confidence": 0.986711859703064
    },
    {
      "name": "shelf",
      "confidence": 0.63831371068954468
    },
    {
      "name": "male",
      "confidence": 0.1531662791967392
    }
  ],
  "description": {
    "tags": [
      "person",
      "man",
      "indoor",
      "computer",
      "holding",
      "laptop",
      "front",
      "shelf",
      "sitting",
      "shirt",
      "using",
      "table",
      "book",
      "food",
      "remote",
      "young",
      "dog",
      "black",
      "wearing",
      "desk",
      "control",
      "large",
      "room",
      "keyboard",
      "white",
      "pizza",
      "bed",
      "video",
      "standing"
    ],
    "captions": [
      {
        "text": "a man holding a book shelf",
        "confidence": 0.66441073283724139
      }
    ]
  },
  "color": {
    "dominantColorForeground": "Grey",
    "dominantColorBackground": "Black",
    "dominantColors": [
      "Black",
      "Grey",
      "Brown"
    ],
    "accentColor": "274562",
    "isBwImg": false
  },
  "requestId": "4efc41fa-c421-4e70-bd20-c392306f6031",
  "metadata": {
    "height": 750,
    "width": 1500,
    "format": "Jpeg"
  }
}

categories kısmına baktığımızda en yüsek skor değeri insan'da görünüyor. Yani insan temalı bir fotoğraf olarak kategorilendirebiliriz. tags bölümünde önerilen takılar bulunmakta. Kapalı mekan olduğu, rafların bulunduğu, bir adamın yer aldığı belirtilmiş. confidence değeri yüksek olanlar tahmini olarak öne çıkan bilgiler. description kısmındaki takılarda fena değil aslında. Mekan ile ilgili az çok tutarlı anahtar kelimelere yer verilmiş. Her ne kadar ben pizza'nın nerede olduğunu pek çıkartamasamda fena sayılmazlar. Ağırlıklı renkler siyah, gri ve kahve olarak belirtilmiş (Renkler aslında ön plan, arka plan ve tüm resim olarak ele alınmaktalar ve 12 dominant renk ele alınmakta; Siyah, mavi, kahverengi, gri, yeşil, turuncu, pembe, mor, kırmızı, deniz mavizi, beyaz ve sarı) Fotoğraf için önerilen başlıkta hoş aslında ki benim Computer Vision'da en çok beğendiğim özelliklerden birisi de bu; "a man holding a book shelf" Diğer fotoğraflar için neler söylediğini bilmek ister misin?

  • Örneğin bir grup insanın masa başında oturduğu ikinci fotoğraf için : "a group of people sitting at a table"
  • Master Yoda ve Luke Syk Walker'ın yanyana durduğu fotoğraf için : "a man and a woman looking at the camera" (Biraz daha öğrenmesi gerekiyor nitekim hangisi için Woman dedi anlayamadım. Belki de para vermediğim için böyle dedi)
  • Kadının garaj kapısındaki arabanın önünde ayakta durduğu fotoğraf için : "a car parked on the side of a building" (Kadını yakalayamamış belki ama tanımlayıcı tag'ler içerisinde woman kelimesi yer alıyor)
  • Ünlü animasyon filmi Cars'ın renkli afişi için : "a car parked in a parking lot" (oldukça akıllı bir öneri değil mi? Gün gelecek karakterlerin isimlerini de tek tek söyleyecek)
  • ve son olarak Toyota marka arazi aracının olduğu fotoğraf için : "a group of people riding on the back of a truck"

Servisin kullanımına ilişkin bir takım çalışma zamanı bilgilerini portal üzerinden de izleyebiliriz. Sonuç itibariyle yapılan işlemleri izlemek önemli. Ne kadar talep gitti, kaçı başarılı oldu, kaçında hata alında, geriye kalan kullanım haklarımız neler, ne kadar ödedik vs... Bu örnek senaryo için Overview kısmındaki grafikler başlangıç için yeterli bilgiler vermekte. Benim yaptığım az sayıda denemenin çıktısı aşağıdaki ekran görütüsündeki gibi oldu.

Görüldüğü üzere Azure'un Cognitive servislerinden olan Computer Vision'ı kullanarak fotoğraflar ile ilgili bir takım bilgileri hesaplatmak oldukça kolay. Söz gelimi yoğun fotoğraf kullanan bir katalog sisteminde fotoğrafların tag bilgilerinin otomatik olarak çıkartılmasında bu hizmet pekala işe yarayabilir. Arka plandaki AI+ML işbirlikteliği daha da güçlendikçe fotoğrafların yorumlanması daha da iyileşecektir. Örneğin bir kamera görüntüsündeki olası saldırganın otomatik olarak tespit edildiğini bir düşünsenize(Aslında ben bu cümleyi yazarken böyle bir şeyin yapılmadığından emin değilim. Yapılıyor da olabilir. Araştırmam lazım) Azure tarafında Computer Vision servisinin pek çok gelişmiş fonksiyonu bulunuyor. Bu fonksiyonlarla resimlerin sınıflandırılması, tanımlanması, thumbnail formatlarının oluşturulması, taxonomy(SEO tarafında önem arz eden bir konudur ve yazının hazırlandığı tarih itibariyle Microsoft 86 kategori başlığından bahsediyordu) veya domain bazında kategorilendirilmesi, clip-art statüsünde olup olmadıklarının berlilenmesi, elle çizilip çizilmediklerinin anlaşılması, cinsel içerik içermediğinin tespit edilmesi ve daha bir çok şey mümkün. İlerleyen zamanlarda elbette yeni fonksiyonellikler de eklenecektir. Dilerseniz siz bu örnekten yararlanarak kendi fotoğraf albümlerinizden seçtiğiniz görüntüleri Computer Vision'a yorumlatmayı deneyebilirsiniz. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Kaynaklar:

Cognitive Services (DevNot'tan)

Microsoft'un Quickstart Dokümanı

Microsoft'un How To Call Vision Api Dokümanı

Computer Vision API

http://www.buraksenyurt.com/post/Mochanızı-Nodejs-ile-Alır-mısınızMocha'nızı Node.js ile Alır mıydınız?

$
0
0

Merhaba Arkadaşlar,

Çalışmakta olduğum şirketin bizlere sunduğu güzel imkanlardan birisi de Pluralsight aboneliği. Hesabım açılır açılmaz ilk yol haritamı da çıkarıverdim. Kendime göre verimli olacağını düşündüğüm bir zaman planlaması yaptım. Sabah saat 07:15 sularında şirkete vardıktan sonra, kapıdaki görevliye sıcak bir tebessümle 'Günaydın' de, mutfaktan geçerken bir bardak kahve al, masana otur ve sonrasında kaldığın yerden devam et... Şu sıralar Node.js yol haritamın merkezinde yer alıyor. Mesai başlangıç saatimiz olan 07:45'e kadar izleyebildiğim ve uygulayabildiğim kadarıyla ilerliyorum. Yer yer videoyu durduruyor, örnek kod parçalarını satır satır yazıyor, gerektiği yerlerde notlar alıyorum. Amacım şu an için sadece ve sadece Node.js'i tanımak, biraz daha iyi anlamak. Sonuçta node.js konusunda uzman olabilmem için yüzlerce saat gerçek hayat projelerinde çalışmam gerekiyor.

İşte bu tempoyu devam ettirdiğim sabahlardan birisinde işlediğim modülde test kodları yazılması üzerine faydalanılan iki paketten bahsedildi. Modülü çalışırken çok güzel şeyler öğrendiğimi fark ettim. Hafiften behavioral driven development(BDD) yaklaşımına yönlendiren, kullanımı kolay ve eğlenceli bir test ortamının nasıl oluşturulabileceği gözler önüne seriliyordu (Aslında şu güdümlü geliştirme konsepti bağlamında sevgili dostum Görkem Özdoğan'ın şuradaki yazısını okumanızışiddetle tavsiye ederim) Modülde Mocha ve Should isimli iki çatıdan(framework) bahsediliyordu. Öğrendiklerimi pekiştirmek için bir yerlere yazmam gerektiğini gayet iyi biliyordum.

Her zaman ki gibi en basit örneğimi hazırladım, konuyu yalın haliyle benimsemeye çalıştım ve işte burada karşınızdayım. Dilerseniz vakit kaybetmeden konumuza başlayalım.

Modülde bahsedilen Mocha bir test çatısı(testing framework) olarak düşünülebilir. Test yazmayı keyifli hale getirdiği belirtilmekte. Ancak onu Should.js çatısı ile birlikte kullanınca çok daha eğlenceli bir ortam oluştuğunu şimdiden söyleyebilirim. Günlük konuşma dilimizdeki ifadeleri bir araya getirerek test iddalarını oluşturabiliyoruz. Ne demek istediğimi anlatabilmek için örnek üzerinden gitmemizde yarar var.

Yazılımcı olarak kulak arkası etmememiz gereken ve bize düşen önemli görevlerden birisi de geliştirdiğimiz en ilkel fonksiyonelliklerin bile olası test senaryolarından başarılı şekilde geçmiş olması. Bu nedenle harfiyen uygulayamıyor olsak bile Test Driven Development'a göz kırpamkta yarar var. Önce sınıfları ve fonksinelliklerini geliştirmek yerine, test senaryolarımıza göre bunları yazmak daha sağlam temellerin atılmasına neden olan bir metodolojidir diye düşünüyorum. Örneğimizde kulağımızı tersten tutuyoruz. Amacımız mocha ve should çatılarının temel olarak nasıl kullanıldıklarını kavramak olduğu için...

Ben klasik olarak her cumartesi gecesi yaptığım gibi West-World'de tatildeyim. Kısa süre önce buraya Node.js'i getirmiştim. Visual Studio Code ve terminal pencerem her zaman olduğu gibi hazır. Kasabanın en meşhur müzik lokalinde Lissie, Rae Moris, Ane Brun ve Katie Melua arka arkaya en popüler şarkılarını canlı olarak seslendiriyorlar. Kahvemden küçük bir yudum alıyor ve klavyemde kodları yazmaya başlıyorum.

Gerekli Kurulumlar

Örnek kod parçasında mocha ve should çatıları kullanıldığı için tahmin edeceğiniz gibi bu paketleri sisteme yüklemek gerekiyor. npm aracını kullanarak ilgili yükleme işlemlerini yapıyorum(Bu paragrafı daha uzun planlamıştım ama...Niye böyle oldu ki :S)

npm install mocha
npm install should

Test Edilecek Kod Parçası

mathForKids.js isimli bir modülümüz olacak. Bu modül içerisinden sum isimli ve iki sayıyı toplayan çok basit bir fonksiyonellik sunuyoruz. sum isimli metodun senkron çalışan bir versiyonu da mevcut(Bu cümle garibinize gitmemiş olmalı. Malum node.js doğası gereği asenkron çalışma prensiplerini benimsiyor. Senkronluk ikinci planda) mathForKids isimli dosyanın içeriğini aşağıdaki gibi geliştirebiliriz.

var maxTime = 1000;

var sum = function (x, y, callback) {
    var waitTime = Math.floor(Math.random() * (maxTime + 1));

    if (x < 0 || y < 0) {
        setTimeout(function () {
            callback(new Error('be positive!'));
        }, waitTime);
    } else {
        setTimeout(function () {
            callback(null, x + y, waitTime);
        }, waitTime);
    }
};

var sumSync = function (x, y) {
    if (x < 0 || y < 0) {
        throw (new Error("be positive!"));
    } else {
        return (x + y);
    }
};

module.exports.sum = sum;
module.exports.sumSync = sumSync;
module.exports.description = "Math is fun";

Kodda neler oluyor bir bakalım değil mi? sum ve sumSync isimli iki metod söz konusu. sum metodu rastgele bir geciktirme süresi esas alınaraktan asenkron çalışma senaryosunu gerçekleştiriyor. Bunun için setTimeout fonksiyonundan nasıl yararlandığımıza dikkat edin lütfen. Çocuklar için toplama işlemi yapan bir fonksiyon söz konusu ki şimdilik onların negatif sayılar dünyasına girmesini istemiyoruz(Gülmeyin cidden ilk bir kaç sene hayatlarında sadece pozitif doğal sayılar var) Bu nedenle her iki fonksiyonumuz içerisinde x ve y değerlerinin 0 dan küçük olma halleri kontrol edilmekte. Eğer bir ihlal söz konusuysa ortama hata mesajı fırlatılmasını sağlamaktayız. Modülümüzdeki fonksiyonellikleri exports bildirimi ile dışarıya sunmayı da ihmal etmiyoruz tabii. Matematik eğlencelidir mesajımızı da gizliden bilinçaltınıza işleteyim :P

Test Dosyası

Test senaryolarını içerek kod dosyasını genel kabul görmüş klasör mantığına göre test isimli dizin altına alabiliriz(Hatta aslında uygulama kodlarını app isimli bir klasörde tutmak da önerilmekte)  Mocha bu durumda test dosyasının adını belirtmemize gerek kalmadan test klasöründe ne var ne yok çalıştırabilir.

var fermat=require('../mathForKids');
var assert=require('assert');

describe('Math for kids',function(){
    describe('#Synchronous test',function(){
        it('should return 4 when the x=2 and y=2',function(){
            assert.equal(fermat.sumSync(2,2),4);
        });
        it('should throw exception when all values are negative',function(){
            assert.throws(function(){
                fermat.sumSync(-1,3);       
            },/positive/);  
        })
    });
    describe('#Asynchronous test',function(){
        it('should return 4 when the x=2 and y=2',function(completed){
            fermat.sum(2,2,function(err,result){
                assert.equal(result,4);
                completed();
            });
        });
    });
});

Temel olarak bu senaryoda 3 test vakamız var. Bunlar iki ana başlık altında toplanıyor. Nitekim bu iki ana başlık da aslında bir program koduna ait. describe fonksiyonunun kullanımına dikkat edelim. İçiçe basit bir ağaç modeli söz konusu. Node.js veya javascript'çilerin aşina olduğu üzere biraz da Christmas Tree probleminin oluşmasına neden olabilecek türde bir yazım söz konusu belki ama üç seviyede tamamlandığını ifade edebiliriz. describe metodu ile bir test tanımı yapıyor ve içerisinde uygulanacak fonksiyonelliği bildiriyoruz(İkinci parametrelere dikkat)

Uygulamayı iki test dalına kırdık. Biri senkron diğeri asenkron fonksiyon testleri için. it fonksiyon bildirimi ile başlayan kısımlar ise "eğer böyle böyle ise şöyle bir şey olmasını bekliyorum" tadındaki açıklamalar ile başlıyor. Tahmin edeceğiniz üzere describe ve it fonksiyonlarındaki ilk parametreler arayüze yansıyacaklar

Gelelim test iddialarımızı nasıl uyguladığımıza. İlk örneğimizde Node.js ile birlikte gelen(built-in) assert modülünü kullanarak ilerledik. İlk test maddesinde 2 ve 2nin toplamını 4 olarak beklediğimizi ifade ediyoruz. İkinci test iddiamız ise x veya y değerlerinden herhangi birinin negatif olması halinde içerisinde 'positive' kelimesi geçen bir hata mesajı almayı beklediğimiz yönünde. 

Son test iddiamız da yine 2 ile 2nin toplamının 4 olduğu üzerine. Lakin burada asenkron çalışan sum metodunun bir test vakasında nasıl kullanılacağı ele alınmakta. Dikkat edileceği üzere senkron metod kullanımından farklı olarak önce asenkron fonksiyonun çağırılması ve ilgili iddianın callback fonksiyonu içerisinde değerlendirilmesi söz konusu. Malum bu asenkron çalışan bir fonksiyonellik olduğundan, ürettiği çıktıları ancak callback fonksiyonunun devreye girdiği yerde test edebiliriz.

Şimdi testimizi çalıştıralım. Tek yapmamız gereken terminalden aşağıdaki komutu işletmek.

node node_modules/.bin/mocha

,

Test sonuçlarına dikkat edecek olursak describe fonksiyonlarındaki girinti yapısına göre bir sıralama yapıldığını, it fonksiyonlarının da başarılı veya başarısız olduklarına dair işaretlendiklerini görebilirsiniz. Bence gayet hoş bir görüntü(Tabii Visual Studio'nun yıllardır kullandığımız Test penceresi ve kolaylıkları düşünüldüğünde biraz yavan kalıyor olabilir)

Bu arada testi çalıştırmak için kullandığımız komut biraz uzun. Linux gibi bir platformda bu şekilde yürütülebiliyor. Ancak daha şık bir çalıştırma tekniği de var. Öncelikle bir package.json dosyasını üretmemiz ve içerisindeki test niteliğinin değerini mocha olarak belirlememiz gerekiyor. package.json üretimi için

npm init

terminal komutundan yararlanabiliriz. Bu komutu çalıştırdığımızda bir kaç soru ile karşılaşacağız. Ben sorulara verdiğim cevaplar sonrasında aşağıdaki package.json içeriğinin üretilmesini sağladım. Uygulamanın adı, versiyonu, kısaca ne yaptığı, giriş kod dosyası, içerdiği klasörler, var olan bağımlılıklar, betikler vs...

{
  "name": "testing",
  "version": "1.0.0",
  "description": "testing with mocha and should",
  "main": "mathForKids.js",
  "directories": {
    "test": "test"
  },
  "dependencies": {
    "should": "^13.2.1"
  },
  "devDependencies": {
    "mocha": "^5.0.5"
  },
  "scripts": {
    "test": "mocha"
  },
  "keywords": [
    "testing",
    "mocha",
    "should"
  ],
  "author": "burak selim senyurt",
  "license": "ISC"
}

Konumuz gereği buradaki en önemli kısım test niteliğinin değeri aslında. Bu niteliğe atanan mocha değerine göre artık uygulama testlerini aşağıdaki basit komut ile çalıştırabiliriz.

npm test

Should İşleri Eğlenceli Hale Getiriyor

Şimdi should çatısını işin içerisine katalım ve test kodlarını daha eğlenceli hale getirelim. Çok seveceksiniz. Ben bayıldım :) Nitekim baya baya konuşur gibi test senaryolarımızı yazmamız mümküm. Bağlaçlar ve fiiller ile bir cümleyi test iddiası olarak sunabiliyoruz. Nasıl mı? İşte test kodlarımızın yeni hali.

var fermat=require('../mathForKids');
var should=require('should');
//var assert=require('assert');

describe('Math for kids',function(){
    describe('#Synchronous test',function(){
        it('should return 4 when the x=2 and y=2',function(){
            var result=fermat.sumSync(2,2);
            result.should.equal(4);
        });
        it('should throw exception when all values are negative',function(completed){
            fermat.sum(1,-3,function(err,result){
                should.exist(err);
                should.not.exist(result);
                completed();
            });
        })
    });
    describe('#Asynchronous test',function(){
        it('should return 4 when the x=2 and y=2',function(completed){
            fermat.sum(2,2,function(err,result){
                should.not.exist(err);
                (4).should.equal(4);
                completed();
            });
        });
        it('should return 8 and be a number',function(completed){
            fermat.sum(3,5,function(err,result){
                result.should.be.exactly(8).and.be.a.Number();
            });
            completed();            
        });
    });
});

Görüldüğü üzere her şey should emri ile başlıyor. should çağrısını bir nesneye uygulamaya başladığımız yerden itibaren çeşitli fonksiyon zincirleri oluşturarak test kabullerimizi geliştirebiliyoruz. Söz gelimi ilk test vakamızda sonucun 4 olmasına yönelik beklentimizi should.equal söz cümlesi ile belirtmekteyiz(Evet evet ne dediğinizi duyar gibiyim. "assert.equal ile aynı şey yahu bu") Takip eden diğer test metodunda arka arkaya iki cümle söz konusu. Buradaki senaryo eksi bir değer olması halinde hata fırlatılmasını bekliyor. İlk olarak err nesnesinin var olduğunu, sonrasında ise bir sonuç döndürülmemesini beklediğimizi should.exist ve should.not.exist cümleleri ile belirtmekteyiz. Asenkron fonksiyon testlerimizde de benzer cümleler söz konusu. Özellikle son test vakasında ki cümle gerçekten kayda değer. "sonuç kesinlikle 8 ve bir sayı olmalı" gibisinden bir ifade söz konusu. Test kodumuzu bu şekilde çalıştırdığımızda aşağıdaki ekran görüntüsünde yer alan sonuçları elde ettiğimizi görebiliriz.

Should çatısında daha pek çok yardımcı bağlaç var. Bunlar arasında .an, .of, .a, .and, .be, .have, .with, .is, .which gibi fonksiyonellikler yer alıyor.

Size tavsiyem Fluent Interface vb terimleri araştırarak bu tip bir fonksiyon zincirini nasıl yazabileceğinize bir bakmanız. Hazır çatıları kullanmak elbette işin kolay kısmı ve amaç test senaryolarını işletmekse tekerleği yeniden keşfetmeye gerek yok. Ancak işin aslı, "bu adamlar bunu nasıl yapıyor yahu?" diyerek incelemenin de bireysel gelişim anlamında çok değerli bir katkısı olacağı.

Ya Kendi Assertion Fonksiyonumuzu Eklemek İstersek

Bu noktada da should çatısı güzel bir genişleyebilirlik sunmakta. Aşağıdaki kod parçasını ele alalım.

var fermat = require('../mathForKids');
var should = require('should');

should.Assertion.add('odd', function () {
    this.params = { operator: 'is a odd number', expected: true };
    ((this.obj % 2) == 1).should.exactly(true);
});

describe('Math for kids', function () {
    describe('#custom Assertions', function () {
        it('sum should be a odd number for result 5', function () {
            var result=fermat.sumSync(1,4);
            result.should.be.a.odd();
        });
        it('sum should be a odd number for result 6', function () {
            var result=fermat.sumSync(2,4);
            result.should.be.a.odd();
        });
        it('sum should not be a odd number for result 6', function () {
            var result=fermat.sumSync(2,4);
            result.should.not.be.a.odd();
        });
    });
});

İlk olarak should.Assertion.add fonksiyonu ile yeni bir tanımlama ekliyoruz. odd isimli fonksiyonu bir sayının tek olup olmadığını anlamak istediğimiz durumlar için kullanacağız. params ile test çalışma zamanı ortamına bir takım bilgiler bırakabiliyoruz. Açıklama ve beklenen sonuç gibi. Fonksiyon içerisinde this.object kullanarak gerçekleştirdiğimiz bir kontrol söz konusu. Dikkat edeceğiniz üzere burada da should fonksiyonundan yararlanıyor ve savımızın beklediğimiz sonucunu kontrol ediyoruz. 3 durumu test ettik. Tek sayı olma hali, tek sayı beklediğimiz halde tek sayı olmama hali, tek sayı beklemediğimiz bir çift sayı olma hali ve olmak ya da olmamak...Ehm...Pardon :) Sonuçta kendi assertion fonksiyonumuzu should çatısına nasıl ekleyebileceğimiz gördük. Hatta burada odd yerine tekSayi gibi kendi dilimizde ifadeler de ekleyebiliriz diye düşünüyorum(Şimdi Seleniumcuları daha iyi anlamaya başladım)

Sizin için

Yazıyı sonlandırmadan önce aşağıdaki ekran görüntüsüne bir bakalım istiyorum.

Dikkatinizi çeken bir şeyler mutlaka var değil mi? Her şeynde önce orada küçük bir uçak figürü var. Ayrıca "1 pending" şeklinde bir ifade de bulunuyor. İşin aslı mocha paketi içerisine testi eğlenceli hale getirmek için konulmuş bir düzeneğin sonucu olarak bir uçak figürü var. Hatta uçak figürünün olduğu yer uçak pistimiz :) Peki bu nasıl mümkün oldu? Peki uçağın rengi neden kırmızı. Acaba tüm testler yeşil ışık yakarsa rengi değişecek mi? İşte size güzel biraz araştırma konusu. Uçağın nasıl çıktığna dair bir ipucu fotoğrafta var. Onu nereye yazacağınızı da bulmanız gerekiyor tabii. "1 pending" ise geçici süreliğine atlanan bir test durumu için oluştu. Bunun için de it metodunun arkasından gelebilecek fonksiyonellikleri araştırmakta yarar var. Haydi kolay gelsin.

Test Sonuçlarını Tarayıcıda Göstermek

Şimdi aşağıdaki ekran görüntüsüne bakmanızı rica ediyorum.

Bu görüntü komut satırındaki test çıktılarına göre daha hoş değil mi? Peki nasıl oluştu merak ediyor musunuz? Haydi gelin anlatayım. Mocha ve Should dokümanları arasında gidip gelirken Chai isimli alternatif bir BDD çatısının da kullanılabileceğini öğrendim. Chai çatısı için verilen örnekte test sonuçlarının tarayıcı penceresine yansıtıldığına dair bir kod parçası da bulunuyordu. Bense Should çatısını kullanarak bu işi yapmak istiyordum. Ancak denemelerimi yaparken oluşturduğum test klasörü bir şekilde beni CORS hatasına doğru sürükleyip duruyordu. Umutsuzluğa kapılmaya başlamıştım. Sonunda pes edip tüm dosyaların aynı klasörde yer aldığı yeni bir örnek üzerinde çalışmaya karar verdim.

mathForKids.js
mathForKids.test.js
index.html (Biraz sonra değineceğiz)

Yine mathForKids.js içeriğini kullanıyordum ancak bu kez en sonda yer alan module.exports bildirimlerinin tamamını kaldırarak ilerlemeyi tercih ettim. Sonrasında test dosyasını yine aynı klasörde olacak şekilde mathForKids.test olarak değiştirdim ve içeriğini güncelledim. Buna göre tüm require bildirimlerini ve fermat nesnesini kaldırdım. Module ile test dosyası aynı klasörde yer aldıklarından dosya adı formatına göre test dosyası içerisinden sum ve sumSync fonksiyonları doğrudan kullanılabilirlerdi. Sonuç olarak yeni örnek test dosyası içeriğim şuna benziyordu.

should.Assertion.add('odd', function () {
    this.params = { operator: 'is a odd number', expected: true };
    ((this.obj % 2) == 1).should.exactly(true);
});

describe('Math for kids', function () {
    describe('#custom Assertions', function () {
        it('sum should be a odd number for result 5', function () {
            var result=sumSync(1,4);
            result.should.be.a.odd();
        });
        it('sum should be a odd number for result 6', function () {
            var result=sumSync(2,4);
            result.should.be.a.odd();
        });
        it('sum should not be a odd number for result 6', function () {
            var result=sumSync(2,4);
            result.should.not.be.a.odd();
        });
    });
    describe('#Synchronous test', function () {
        it.skip('should return 4 when the x=2 and y=2', function () {
            var result = sumSync(2, 2);
            result.should.equal(4);
        });
        it('should throw exception when all values are negative', function (completed) {
            sum(1, -3, function (err, result) {
                should.exist(err);
                should.not.exist(result);
                completed();
            });
        })
    });
    describe('#Asynchronous test', function () {
        it('should return 4 when the x=2 and y=2', function (completed) {
            sum(2, 2, function (err, result) {
                should.not.exist(err);
                (4).should.equal(4);
                completed();
            });
        });
        it('should return 8 and be a number', function (completed) {
            sum(3, 5, function (err, result) {
                result.should.be.exactly(8).and.be.a.Number();
            });
            completed();
        });
    });
});

Test sonuçlarını tarayıcıda göstermek için basit bir HTML sayfası oluşturulması yeterliydi. Sadece onu çalıştırdığımızda gerekli testleri yapacak ve sonuçları tarayıcıya basacak şekilde güdümlemek gerekiyordu. Bu dosyanın şablon yapısı mocha dokümantasyonunda belirtildiği gibi oluşuturulsa bu mümkündü. Bir kaç deneme ve yanılma sonrası aşağıdaki index.html içeriğinin yeterli olduğunu keşfetmeyi başardım.

<meta charset="utf-8"><title>Mocha Tests</title><link href="https://unpkg.com/mocha@4.0.1/mocha.css" rel="stylesheet" /></head><body><div id="mocha"></div><script src="https://unpkg.com/should@13.2.1/should.js"></script><script src="https://unpkg.com/mocha@4.0.1/mocha.js"></script><script>mocha.setup('bdd')</script><script src="mathForKids.js"></script><script src="mathForKids.test.js"></script><script>
        mocha.checkLeaks();
        mocha.run();</script></body></html>

Sayfada dikkat çekici noktalardan birisi unpkg adresine yapılan bağlantılar aslında. CSS, Mocha ve Should paketleri için benzer formatta tanımlamalar mevcut. unpkg'nin çok basit bir kullanımı var. Npm'de yüklü olan her paket için internetten kullanılabilecek bir adres desteği sunduğunu ifade edebiliriz. Bunun için kendi lokal dosyamızda unpkg.com/:package@:version/:fileşeklindeki formatı kullanmak yeterli. should ve mocha paketlerine ait javascript bağlantılarının nasıl verildiğine bu anlamda dikkat edelim. Tabii bir kaç başlangıç ayarı yapmak da gerekiyor. Örneğin setup çağrımı ile BDD modeline göre bir çalışma zamanı ortamı hazırlanacağını belirtiyoruz. Sonrasında mathForKids ve mathForKids.test dosyalarının bildirimi yapılıyor ki bu sayede hangi test içeriğinin çalıştırılacağı ve o test içeriğinde kullanılan ek modüller varsa onların neler olduğunu belirtmiş oluyoruz. Bu HTML dosyasını tarayıcıda açtığımızdaysa son script bloğunda yer alan kodlar işletiyor ve test, başlatılıyor. Hepsi bu :)

Pek tabii mevzu benim burada anlattığım kadar yalın değil. Yapılabilecek bir çok şey var. Tabii hangi çatı olursa olsun test yazma alışkanlığı kazanmak da mühim bir mesele bana kalırsa. Bu dediğimi lütfen dikkate alın. Hem siz hem de sizden sonra o pozisyonda görev alacak insanlar zorlanmasınlar ;) Böylece geldik bir makalemizin daha sonuna. Pluralsight'tan bakalım bana ne kadar ekmek çıkacak. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Kaynaklar :

https://github.com/shouldjs/should.js/ 

https://mochajs.org/


Mocha'nızı Node.js ile Alır mıydınız?

$
0
0

Merhaba Arkadaşlar,

Çalışmakta olduğum şirketin bizlere sunduğu güzel imkanlardan birisi de Pluralsight aboneliği. Hesabım açılır açılmaz ilk yol haritamı da çıkarıverdim. Kendime göre verimli olacağını düşündüğüm bir zaman planlaması yaptım. Sabah saat 07:15 sularında şirkete vardıktan sonra, kapıdaki görevliye sıcak bir tebessümle 'Günaydın' de, mutfaktan geçerken bir bardak kahve al, masana otur ve sonrasında kaldığın yerden devam et... Şu sıralar Node.js yol haritamın merkezinde yer alıyor. Mesai başlangıç saatimiz olan 07:45'e kadar izleyebildiğim ve uygulayabildiğim kadarıyla ilerliyorum. Yer yer videoyu durduruyor, örnek kod parçalarını satır satır yazıyor, gerektiği yerlerde notlar alıyorum. Amacım şu an için sadece ve sadece Node.js'i tanımak, biraz daha iyi anlamak. Sonuçta node.js konusunda uzman olabilmem için yüzlerce saat gerçek hayat projelerinde çalışmam gerekiyor.

İşte bu tempoyu devam ettirdiğim sabahlardan birisinde işlediğim modülde test kodları yazılması üzerine faydalanılan iki paketten bahsedildi. Modülü çalışırken çok güzel şeyler öğrendiğimi fark ettim. Hafiften behavioral driven development(BDD) yaklaşımına yönlendiren, kullanımı kolay ve eğlenceli bir test ortamının nasıl oluşturulabileceği gözler önüne seriliyordu (Aslında şu güdümlü geliştirme konsepti bağlamında sevgili dostum Görkem Özdoğan'ın şuradaki yazısını okumanızışiddetle tavsiye ederim) Modülde Mocha ve Should isimli iki çatıdan(framework) bahsediliyordu. Öğrendiklerimi pekiştirmek için bir yerlere yazmam gerektiğini gayet iyi biliyordum.

Her zaman ki gibi en basit örneğimi hazırladım, konuyu yalın haliyle benimsemeye çalıştım ve işte burada karşınızdayım. Dilerseniz vakit kaybetmeden konumuza başlayalım.

Modülde bahsedilen Mocha bir test çatısı(testing framework) olarak düşünülebilir. Test yazmayı keyifli hale getirdiği belirtilmekte. Ancak onu Should.js çatısı ile birlikte kullanınca çok daha eğlenceli bir ortam oluştuğunu şimdiden söyleyebilirim. Günlük konuşma dilimizdeki ifadeleri bir araya getirerek test iddalarını oluşturabiliyoruz. Ne demek istediğimi anlatabilmek için örnek üzerinden gitmemizde yarar var.

Yazılımcı olarak kulak arkası etmememiz gereken ve bize düşen önemli görevlerden birisi de geliştirdiğimiz en ilkel fonksiyonelliklerin bile olası test senaryolarından başarılı şekilde geçmiş olması. Bu nedenle harfiyen uygulayamıyor olsak bile Test Driven Development'a göz kırpamkta yarar var. Önce sınıfları ve fonksinelliklerini geliştirmek yerine, test senaryolarımıza göre bunları yazmak daha sağlam temellerin atılmasına neden olan bir metodolojidir diye düşünüyorum. Örneğimizde kulağımızı tersten tutuyoruz. Amacımız mocha ve should çatılarının temel olarak nasıl kullanıldıklarını kavramak olduğu için...

Ben klasik olarak her cumartesi gecesi yaptığım gibi West-World'de tatildeyim. Kısa süre önce buraya Node.js'i getirmiştim. Visual Studio Code ve terminal pencerem her zaman olduğu gibi hazır. Kasabanın en meşhur müzik lokalinde Lissie, Rae Moris, Ane Brun ve Katie Melua arka arkaya en popüler şarkılarını canlı olarak seslendiriyorlar. Kahvemden küçük bir yudum alıyor ve klavyemde kodları yazmaya başlıyorum.

Gerekli Kurulumlar

Örnek kod parçasında mocha ve should çatıları kullanıldığı için tahmin edeceğiniz gibi bu paketleri sisteme yüklemek gerekiyor. npm aracını kullanarak ilgili yükleme işlemlerini yapıyorum(Bu paragrafı daha uzun planlamıştım ama...Niye böyle oldu ki :S)

npm install mocha
npm install should

Test Edilecek Kod Parçası

mathForKids.js isimli bir modülümüz olacak. Bu modül içerisinden sum isimli ve iki sayıyı toplayan çok basit bir fonksiyonellik sunuyoruz. sum isimli metodun senkron çalışan bir versiyonu da mevcut(Bu cümle garibinize gitmemiş olmalı. Malum node.js doğası gereği asenkron çalışma prensiplerini benimsiyor. Senkronluk ikinci planda) mathForKids isimli dosyanın içeriğini aşağıdaki gibi geliştirebiliriz.

var maxTime = 1000;

var sum = function (x, y, callback) {
    var waitTime = Math.floor(Math.random() * (maxTime + 1));

    if (x < 0 || y < 0) {
        setTimeout(function () {
            callback(new Error('be positive!'));
        }, waitTime);
    } else {
        setTimeout(function () {
            callback(null, x + y, waitTime);
        }, waitTime);
    }
};

var sumSync = function (x, y) {
    if (x < 0 || y < 0) {
        throw (new Error("be positive!"));
    } else {
        return (x + y);
    }
};

module.exports.sum = sum;
module.exports.sumSync = sumSync;
module.exports.description = "Math is fun";

Kodda neler oluyor bir bakalım değil mi? sum ve sumSync isimli iki metod söz konusu. sum metodu rastgele bir geciktirme süresi esas alınaraktan asenkron çalışma senaryosunu gerçekleştiriyor. Bunun için setTimeout fonksiyonundan nasıl yararlandığımıza dikkat edin lütfen. Çocuklar için toplama işlemi yapan bir fonksiyon söz konusu ki şimdilik onların negatif sayılar dünyasına girmesini istemiyoruz(Gülmeyin cidden ilk bir kaç sene hayatlarında sadece pozitif doğal sayılar var) Bu nedenle her iki fonksiyonumuz içerisinde x ve y değerlerinin 0 dan küçük olma halleri kontrol edilmekte. Eğer bir ihlal söz konusuysa ortama hata mesajı fırlatılmasını sağlamaktayız. Modülümüzdeki fonksiyonellikleri exports bildirimi ile dışarıya sunmayı da ihmal etmiyoruz tabii. Matematik eğlencelidir mesajımızı da gizliden bilinçaltınıza işleteyim :P

Test Dosyası

Test senaryolarını içerek kod dosyasını genel kabul görmüş klasör mantığına göre test isimli dizin altına alabiliriz(Hatta aslında uygulama kodlarını app isimli bir klasörde tutmak da önerilmekte)  Mocha bu durumda test dosyasının adını belirtmemize gerek kalmadan test klasöründe ne var ne yok çalıştırabilir.

var fermat=require('../mathForKids');
var assert=require('assert');

describe('Math for kids',function(){
    describe('#Synchronous test',function(){
        it('should return 4 when the x=2 and y=2',function(){
            assert.equal(fermat.sumSync(2,2),4);
        });
        it('should throw exception when all values are negative',function(){
            assert.throws(function(){
                fermat.sumSync(-1,3);       
            },/positive/);  
        })
    });
    describe('#Asynchronous test',function(){
        it('should return 4 when the x=2 and y=2',function(completed){
            fermat.sum(2,2,function(err,result){
                assert.equal(result,4);
                completed();
            });
        });
    });
});

Temel olarak bu senaryoda 3 test vakamız var. Bunlar iki ana başlık altında toplanıyor. Nitekim bu iki ana başlık da aslında bir program koduna ait. describe fonksiyonunun kullanımına dikkat edelim. İçiçe basit bir ağaç modeli söz konusu. Node.js veya javascript'çilerin aşina olduğu üzere biraz da Christmas Tree probleminin oluşmasına neden olabilecek türde bir yazım söz konusu belki ama üç seviyede tamamlandığını ifade edebiliriz. describe metodu ile bir test tanımı yapıyor ve içerisinde uygulanacak fonksiyonelliği bildiriyoruz(İkinci parametrelere dikkat)

Uygulamayı iki test dalına kırdık. Biri senkron diğeri asenkron fonksiyon testleri için. it fonksiyon bildirimi ile başlayan kısımlar ise "eğer böyle böyle ise şöyle bir şey olmasını bekliyorum" tadındaki açıklamalar ile başlıyor. Tahmin edeceğiniz üzere describe ve it fonksiyonlarındaki ilk parametreler arayüze yansıyacaklar

Gelelim test iddialarımızı nasıl uyguladığımıza. İlk örneğimizde Node.js ile birlikte gelen(built-in) assert modülünü kullanarak ilerledik. İlk test maddesinde 2 ve 2nin toplamını 4 olarak beklediğimizi ifade ediyoruz. İkinci test iddiamız ise x veya y değerlerinden herhangi birinin negatif olması halinde içerisinde 'positive' kelimesi geçen bir hata mesajı almayı beklediğimiz yönünde. 

Son test iddiamız da yine 2 ile 2nin toplamının 4 olduğu üzerine. Lakin burada asenkron çalışan sum metodunun bir test vakasında nasıl kullanılacağı ele alınmakta. Dikkat edileceği üzere senkron metod kullanımından farklı olarak önce asenkron fonksiyonun çağırılması ve ilgili iddianın callback fonksiyonu içerisinde değerlendirilmesi söz konusu. Malum bu asenkron çalışan bir fonksiyonellik olduğundan, ürettiği çıktıları ancak callback fonksiyonunun devreye girdiği yerde test edebiliriz.

Şimdi testimizi çalıştıralım. Tek yapmamız gereken terminalden aşağıdaki komutu işletmek.

node node_modules/.bin/mocha

,

Test sonuçlarına dikkat edecek olursak describe fonksiyonlarındaki girinti yapısına göre bir sıralama yapıldığını, it fonksiyonlarının da başarılı veya başarısız olduklarına dair işaretlendiklerini görebilirsiniz. Bence gayet hoş bir görüntü(Tabii Visual Studio'nun yıllardır kullandığımız Test penceresi ve kolaylıkları düşünüldüğünde biraz yavan kalıyor olabilir)

Bu arada testi çalıştırmak için kullandığımız komut biraz uzun. Linux gibi bir platformda bu şekilde yürütülebiliyor. Ancak daha şık bir çalıştırma tekniği de var. Öncelikle bir package.json dosyasını üretmemiz ve içerisindeki test niteliğinin değerini mocha olarak belirlememiz gerekiyor. package.json üretimi için

npm init

terminal komutundan yararlanabiliriz. Bu komutu çalıştırdığımızda bir kaç soru ile karşılaşacağız. Ben sorulara verdiğim cevaplar sonrasında aşağıdaki package.json içeriğinin üretilmesini sağladım. Uygulamanın adı, versiyonu, kısaca ne yaptığı, giriş kod dosyası, içerdiği klasörler, var olan bağımlılıklar, betikler vs...

{
  "name": "testing",
  "version": "1.0.0",
  "description": "testing with mocha and should",
  "main": "mathForKids.js",
  "directories": {
    "test": "test"
  },
  "dependencies": {
    "should": "^13.2.1"
  },
  "devDependencies": {
    "mocha": "^5.0.5"
  },
  "scripts": {
    "test": "mocha"
  },
  "keywords": [
    "testing",
    "mocha",
    "should"
  ],
  "author": "burak selim senyurt",
  "license": "ISC"
}

Konumuz gereği buradaki en önemli kısım test niteliğinin değeri aslında. Bu niteliğe atanan mocha değerine göre artık uygulama testlerini aşağıdaki basit komut ile çalıştırabiliriz.

npm test

Should İşleri Eğlenceli Hale Getiriyor

Şimdi should çatısını işin içerisine katalım ve test kodlarını daha eğlenceli hale getirelim. Çok seveceksiniz. Ben bayıldım :) Nitekim baya baya konuşur gibi test senaryolarımızı yazmamız mümküm. Bağlaçlar ve fiiller ile bir cümleyi test iddiası olarak sunabiliyoruz. Nasıl mı? İşte test kodlarımızın yeni hali.

var fermat=require('../mathForKids');
var should=require('should');
//var assert=require('assert');

describe('Math for kids',function(){
    describe('#Synchronous test',function(){
        it('should return 4 when the x=2 and y=2',function(){
            var result=fermat.sumSync(2,2);
            result.should.equal(4);
        });
        it('should throw exception when all values are negative',function(completed){
            fermat.sum(1,-3,function(err,result){
                should.exist(err);
                should.not.exist(result);
                completed();
            });
        })
    });
    describe('#Asynchronous test',function(){
        it('should return 4 when the x=2 and y=2',function(completed){
            fermat.sum(2,2,function(err,result){
                should.not.exist(err);
                (4).should.equal(4);
                completed();
            });
        });
        it('should return 8 and be a number',function(completed){
            fermat.sum(3,5,function(err,result){
                result.should.be.exactly(8).and.be.a.Number();
            });
            completed();            
        });
    });
});

Görüldüğü üzere her şey should emri ile başlıyor. should çağrısını bir nesneye uygulamaya başladığımız yerden itibaren çeşitli fonksiyon zincirleri oluşturarak test kabullerimizi geliştirebiliyoruz. Söz gelimi ilk test vakamızda sonucun 4 olmasına yönelik beklentimizi should.equal söz cümlesi ile belirtmekteyiz(Evet evet ne dediğinizi duyar gibiyim. "assert.equal ile aynı şey yahu bu") Takip eden diğer test metodunda arka arkaya iki cümle söz konusu. Buradaki senaryo eksi bir değer olması halinde hata fırlatılmasını bekliyor. İlk olarak err nesnesinin var olduğunu, sonrasında ise bir sonuç döndürülmemesini beklediğimizi should.exist ve should.not.exist cümleleri ile belirtmekteyiz. Asenkron fonksiyon testlerimizde de benzer cümleler söz konusu. Özellikle son test vakasında ki cümle gerçekten kayda değer. "sonuç kesinlikle 8 ve bir sayı olmalı" gibisinden bir ifade söz konusu. Test kodumuzu bu şekilde çalıştırdığımızda aşağıdaki ekran görüntüsünde yer alan sonuçları elde ettiğimizi görebiliriz.

Should çatısında daha pek çok yardımcı bağlaç var. Bunlar arasında .an, .of, .a, .and, .be, .have, .with, .is, .which gibi fonksiyonellikler yer alıyor.

Size tavsiyem Fluent Interface vb terimleri araştırarak bu tip bir fonksiyon zincirini nasıl yazabileceğinize bir bakmanız. Hazır çatıları kullanmak elbette işin kolay kısmı ve amaç test senaryolarını işletmekse tekerleği yeniden keşfetmeye gerek yok. Ancak işin aslı, "bu adamlar bunu nasıl yapıyor yahu?" diyerek incelemenin de bireysel gelişim anlamında çok değerli bir katkısı olacağı.

Ya Kendi Assertion Fonksiyonumuzu Eklemek İstersek

Bu noktada da should çatısı güzel bir genişleyebilirlik sunmakta. Aşağıdaki kod parçasını ele alalım.

var fermat = require('../mathForKids');
var should = require('should');

should.Assertion.add('odd', function () {
    this.params = { operator: 'is a odd number', expected: true };
    ((this.obj % 2) == 1).should.exactly(true);
});

describe('Math for kids', function () {
    describe('#custom Assertions', function () {
        it('sum should be a odd number for result 5', function () {
            var result=fermat.sumSync(1,4);
            result.should.be.a.odd();
        });
        it('sum should be a odd number for result 6', function () {
            var result=fermat.sumSync(2,4);
            result.should.be.a.odd();
        });
        it('sum should not be a odd number for result 6', function () {
            var result=fermat.sumSync(2,4);
            result.should.not.be.a.odd();
        });
    });
});

İlk olarak should.Assertion.add fonksiyonu ile yeni bir tanımlama ekliyoruz. odd isimli fonksiyonu bir sayının tek olup olmadığını anlamak istediğimiz durumlar için kullanacağız. params ile test çalışma zamanı ortamına bir takım bilgiler bırakabiliyoruz. Açıklama ve beklenen sonuç gibi. Fonksiyon içerisinde this.object kullanarak gerçekleştirdiğimiz bir kontrol söz konusu. Dikkat edeceğiniz üzere burada da should fonksiyonundan yararlanıyor ve savımızın beklediğimiz sonucunu kontrol ediyoruz. 3 durumu test ettik. Tek sayı olma hali, tek sayı beklediğimiz halde tek sayı olmama hali, tek sayı beklemediğimiz bir çift sayı olma hali ve olmak ya da olmamak...Ehm...Pardon :) Sonuçta kendi assertion fonksiyonumuzu should çatısına nasıl ekleyebileceğimiz gördük. Hatta burada odd yerine tekSayi gibi kendi dilimizde ifadeler de ekleyebiliriz diye düşünüyorum(Şimdi Seleniumcuları daha iyi anlamaya başladım)

Sizin için

Yazıyı sonlandırmadan önce aşağıdaki ekran görüntüsüne bir bakalım istiyorum.

Dikkatinizi çeken bir şeyler mutlaka var değil mi? Her şeynde önce orada küçük bir uçak figürü var. Ayrıca "1 pending" şeklinde bir ifade de bulunuyor. İşin aslı mocha paketi içerisine testi eğlenceli hale getirmek için konulmuş bir düzeneğin sonucu olarak bir uçak figürü var. Hatta uçak figürünün olduğu yer uçak pistimiz :) Peki bu nasıl mümkün oldu? Peki uçağın rengi neden kırmızı. Acaba tüm testler yeşil ışık yakarsa rengi değişecek mi? İşte size güzel biraz araştırma konusu. Uçağın nasıl çıktığna dair bir ipucu fotoğrafta var. Onu nereye yazacağınızı da bulmanız gerekiyor tabii. "1 pending" ise geçici süreliğine atlanan bir test durumu için oluştu. Bunun için de it metodunun arkasından gelebilecek fonksiyonellikleri araştırmakta yarar var. Haydi kolay gelsin.

Test Sonuçlarını Tarayıcıda Göstermek

Şimdi aşağıdaki ekran görüntüsüne bakmanızı rica ediyorum.

Bu görüntü komut satırındaki test çıktılarına göre daha hoş değil mi? Peki nasıl oluştu merak ediyor musunuz? Haydi gelin anlatayım. Mocha ve Should dokümanları arasında gidip gelirken Chai isimli alternatif bir BDD çatısının da kullanılabileceğini öğrendim. Chai çatısı için verilen örnekte test sonuçlarının tarayıcı penceresine yansıtıldığına dair bir kod parçası da bulunuyordu. Bense Should çatısını kullanarak bu işi yapmak istiyordum. Ancak denemelerimi yaparken oluşturduğum test klasörü bir şekilde beni CORS hatasına doğru sürükleyip duruyordu. Umutsuzluğa kapılmaya başlamıştım. Sonunda pes edip tüm dosyaların aynı klasörde yer aldığı yeni bir örnek üzerinde çalışmaya karar verdim.

mathForKids.js
mathForKids.test.js
index.html (Biraz sonra değineceğiz)

Yine mathForKids.js içeriğini kullanıyordum ancak bu kez en sonda yer alan module.exports bildirimlerinin tamamını kaldırarak ilerlemeyi tercih ettim. Sonrasında test dosyasını yine aynı klasörde olacak şekilde mathForKids.test olarak değiştirdim ve içeriğini güncelledim. Buna göre tüm require bildirimlerini ve fermat nesnesini kaldırdım. Module ile test dosyası aynı klasörde yer aldıklarından dosya adı formatına göre test dosyası içerisinden sum ve sumSync fonksiyonları doğrudan kullanılabilirlerdi. Sonuç olarak yeni örnek test dosyası içeriğim şuna benziyordu.

should.Assertion.add('odd', function () {
    this.params = { operator: 'is a odd number', expected: true };
    ((this.obj % 2) == 1).should.exactly(true);
});

describe('Math for kids', function () {
    describe('#custom Assertions', function () {
        it('sum should be a odd number for result 5', function () {
            var result=sumSync(1,4);
            result.should.be.a.odd();
        });
        it('sum should be a odd number for result 6', function () {
            var result=sumSync(2,4);
            result.should.be.a.odd();
        });
        it('sum should not be a odd number for result 6', function () {
            var result=sumSync(2,4);
            result.should.not.be.a.odd();
        });
    });
    describe('#Synchronous test', function () {
        it.skip('should return 4 when the x=2 and y=2', function () {
            var result = sumSync(2, 2);
            result.should.equal(4);
        });
        it('should throw exception when all values are negative', function (completed) {
            sum(1, -3, function (err, result) {
                should.exist(err);
                should.not.exist(result);
                completed();
            });
        })
    });
    describe('#Asynchronous test', function () {
        it('should return 4 when the x=2 and y=2', function (completed) {
            sum(2, 2, function (err, result) {
                should.not.exist(err);
                (4).should.equal(4);
                completed();
            });
        });
        it('should return 8 and be a number', function (completed) {
            sum(3, 5, function (err, result) {
                result.should.be.exactly(8).and.be.a.Number();
            });
            completed();
        });
    });
});

Test sonuçlarını tarayıcıda göstermek için basit bir HTML sayfası oluşturulması yeterliydi. Sadece onu çalıştırdığımızda gerekli testleri yapacak ve sonuçları tarayıcıya basacak şekilde güdümlemek gerekiyordu. Bu dosyanın şablon yapısı mocha dokümantasyonunda belirtildiği gibi oluşuturulsa bu mümkündü. Bir kaç deneme ve yanılma sonrası aşağıdaki index.html içeriğinin yeterli olduğunu keşfetmeyi başardım.

<meta charset="utf-8"><title>Mocha Tests</title><link href="https://unpkg.com/mocha@4.0.1/mocha.css" rel="stylesheet" /></head><body><div id="mocha"></div><script src="https://unpkg.com/should@13.2.1/should.js"></script><script src="https://unpkg.com/mocha@4.0.1/mocha.js"></script><script>mocha.setup('bdd')</script><script src="mathForKids.js"></script><script src="mathForKids.test.js"></script><script>
        mocha.checkLeaks();
        mocha.run();</script></body></html>

Sayfada dikkat çekici noktalardan birisi unpkg adresine yapılan bağlantılar aslında. CSS, Mocha ve Should paketleri için benzer formatta tanımlamalar mevcut. unpkg'nin çok basit bir kullanımı var. Npm'de yüklü olan her paket için internetten kullanılabilecek bir adres desteği sunduğunu ifade edebiliriz. Bunun için kendi lokal dosyamızda unpkg.com/:package@:version/:fileşeklindeki formatı kullanmak yeterli. should ve mocha paketlerine ait javascript bağlantılarının nasıl verildiğine bu anlamda dikkat edelim. Tabii bir kaç başlangıç ayarı yapmak da gerekiyor. Örneğin setup çağrımı ile BDD modeline göre bir çalışma zamanı ortamı hazırlanacağını belirtiyoruz. Sonrasında mathForKids ve mathForKids.test dosyalarının bildirimi yapılıyor ki bu sayede hangi test içeriğinin çalıştırılacağı ve o test içeriğinde kullanılan ek modüller varsa onların neler olduğunu belirtmiş oluyoruz. Bu HTML dosyasını tarayıcıda açtığımızdaysa son script bloğunda yer alan kodlar işletiyor ve test, başlatılıyor. Hepsi bu :)

Pek tabii mevzu benim burada anlattığım kadar yalın değil. Yapılabilecek bir çok şey var. Tabii hangi çatı olursa olsun test yazma alışkanlığı kazanmak da mühim bir mesele bana kalırsa. Bu dediğimi lütfen dikkate alın. Hem siz hem de sizden sonra o pozisyonda görev alacak insanlar zorlanmasınlar ;) Böylece geldik bir makalemizin daha sonuna. Pluralsight'tan bakalım bana ne kadar ekmek çıkacak. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Kaynaklar :

https://github.com/shouldjs/should.js/ 

https://mochajs.org/

http://www.buraksenyurt.com/post/West-Worldun-Uzaydan-Gelen-SQL-Server-ile-TanısmasıWest-World'ün Uzaydan Gelen SQL Server ile Tanışması

$
0
0

Merhaba Arkadaşlar,

Renkler ve zevkler tartışılmaz. Hatta dünya öylesine renkli bir yerdir ki insanlar bazen neyi seçeceklerine karar veremeyebilir. Tabii ki işin içerisinde yazılım olunca bu renkler siyah ve beyaz gibi sadece iki seçeneğe de indirgenebilmişdir.

Hatta bu sadece yazılım için değil donanım için de söz konusu olmuştur. Hep bir kıyaslama vardır. PC'mi Mac'mi, RISC tabanlı mı CISC olanı mı, Intel'mi AMD'mi... Diğer yandan hepinizin bildiği üzere ben yaşlanmakta olan bir yazılımcıyım. Eski kuşak bir programcı olarak(bir başka deyişle 70li yılların ürünü bir birey olarak) benim neslin uzun yıllar içinde yer aldığı çatışmaların canlı şahitlerindenim. Hala süregelir ya bu sonu gelmez tartışmalar. Sizde içinde yer almışsınızdır mutlaka. Java'mı, .Net mi, SQL'mi Oracle'mı, OOP'mi Functional'mı, SOA'mı Microservices'mı vb diye sürer gider.

Bu ve benzeri tartışmalar daha da sürecek mi diye düşünürken şartlar uzun süre önce değişmeye başladı. Bugün bir Mac alıp üzerindeki Intel tabanlı işlemcide Windows koşturabiliyoruz. Hatta Microsoft açık kaynak dünyası ile çoktan el sıkıştı; ortaya .Net Core'u çıkardı. Yanılıyor muyum? Hatta Java Developers Day'a altın ortak bile oluverdi(Yaşıyorsa evet şu adrese girip Gold Sponsor kısmına bir bakın derim) 

Ne alıp veremedikleri vardı ki bu ayrı dünyalarda yaşadıklarını zanneden standart koyucuların, büyük oyucuların. Bu ayrı bir hikaye ama sonuçta bugün Linux üzerinde SQL Server kullanabilir ve hatta C# üzerinden onunla konuşabilir hale geldik. Barış güzel bir şey. İşte günün konusu. Linux üzerine SQL Server kurmak ve bir Web API servisi ile CRUD operasyonlarını deneyimlemek. 

SQL Server Kurulumu

Tabii ki ilk yapılması gereken Linux üzerine SQL server'ın kurulması. Her zaman ki gibi bu kurulum işleminde de terminalden yararlanacağız. Aşağıdaki komutları arka arkaya çalıştırarak işe başlayabiliriz. 

wget -qO- https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
sudo add-apt-repository "$(wget -qO- https://packages.microsoft.com/config/ubuntu/16.04/mssql-server-2017.list)"
sudo apt-get update
sudo apt-get install -y mssql-server

Bu adımlardan sonra SQL Server'ın Linux için konfigüre edilmesi gerekiyor. 

sudo /opt/mssql/bin/mssql-conf setup

Yukarıdaki komut sonrasında karşıma çıkan sorulara baktım ve Developer sürümü ile ilerleyeceğimi belirttim(Kuvvetle muhtemel ölene kadar developer olarak kalacağım :D) Bana sorulan SA(SQL'in meşhur System Admin kullanıcısı) için güzelde bir şifre belirledim.

İşlemlerin sorunsuz bir şekilde tamamlandığından emin olmanın yolu tahmin edileceği üzere SQL Server hizmetinin Linux ortamında çalışıp çalışmadığından emin olmak. Bunun için West-World'de aşağıdaki terminal komutunu vermem yeterliydi. Kısaca sistem kontrole mssql-server hizmetinin durumunu soruyoruz. Active durumunda olduğunu görmek yeterli.

systemctl status mssql-server

Sırada Command-Line Tool Kurulumu Var

Tabii ki SQL Server'ın West-World'e kurulması yeterli değil. Eğer Windows topraklarında olsaydım büyük ihtimalle Management Studio gibi bir şey de arayacaktım. Linux tarafında da bu tip bir araç kullanmak mümkün ama komut satırından da pekala bilinçli bir şekilde aynı işlemler halledilebilir. CLI aracının kurulumu için aşağıdaki komutların çalıştırılması yeterli.

wget -qO- https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
sudo add-apt-repository "$(wget -qO- https://packages.microsoft.com/config/ubuntu/16.04/prod.list)"
sudo apt-get update
sudo apt-get install -y mssql-tools unixodbc-dev
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc
source ~/.bashrc

Artık West-World üzerinde biraz SQLce(eskilerde sqlCe diye bir sürüm de vardı) konuşmaya başlayabilirim. Örneğin bir veritabanı ve tablolarını üretebilir bu tabloya veri ekleyebilirim. İşte başlamak için terminalden aşağıdaki komutu vermek yeterli.

sqlcmd -S localhost -U SA -P '...'

-S anahtarı sonrası bağlanacağımız sunucuyu, -U sonrası bağlanacak kullanıcıyı ve -P sonrası da bu kullanıcının şifresini belirtiyoruz. West-World güzel havadan olsa gerek oldukça ılımlı. Sorunsuz bir şekilde bağlantı isteklerini kabul edip komut satırını kullanımıma açtı. Ben de bu teklifi memnuniyetle karşıladım ve veritabanı oluşturmak için aşağıdaki komutu kullandım.

CREATE DATABASE azon
Go

var olan veritabanlarını görmek içinse aşağıdaki komutu. Amacım tabii ki de az önce oluşturduğum Azon isimli veritabanını sistem içerisinde görebilmekti.

SELECT Name from sys.Databases
go

Derken o an üzerinde çalışılacak veritabanını değiştirmeye ve bir tablo oluşturup ona birkaç örnek veri satırı eklemeye karar verdim.

use azon
go

create table Category(id int,name nvarchar(50))
go

insert into Category values(1,'book');
insert into Category values(2,'movie');
insert into Category values(3,'music');
go
select * from Category
go

Örnekte azon isimli bir veritabanı oluşturuluyor. Sonrasında bu veritabanının kullanılacağı belirtiliyor. Use ifadesinin çalıştırılması sonrası komut satırından gerçekleştirilecek tüm işlemler azon veritabanı için geçerli olacaktır. Category tablosu tamamen deneysel amaçlı. id ve name alanlarından oluşuyor. İçine 3 satır veri ekleniyor ve son olarak tüm içeriğe bir Select sorgusu atılıyor. Sonuçlar West-World için aşağıdaki gibiydi. 

Doğruyu söylemek gerekirse West-World, SQL Server ile terminal üzerinden gayet güzel bir biçimde anlaşıyordu. Database oluşturulabiliyor, içerisine tablo konulup veriler eklenebiliyordu. Pekiiiii ya bu işin içerisine .Net Core ile yazılmış bir Web API servisini de katabilir miydim? Elebette bu mümkündü. Beni heyecanladıran kısım bunun Linux tabanlı bir sistemde olması.

Web API Servisinin Yazılması

İşe bir .Net Core Web API servisini oluşturmakla başlamak lazım. Visual Studio Code arabirimini açıp terminalden aşağıdaki komutları vererek ilerleyebiliriz.

dotnet new webapi -o Players
cd Players
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 2.0.2

Players isimli bir Web API uygulamamız var. Entity Framework paketinin SQL Server için yazılmış sürümünü kullanıyoruz. Servisin standart Values Controller tipini değiştirmeden önce Models isimli bir klasör oluşturup içerisine gerekli tipleri ekleyebiliriz. Bir oyuncunun temel özelliklerini barındıracak olan Player sınıfı,

using System.ComponentModel.DataAnnotations;

namespace Players.Models  
{
    public class Player
    {
        public int PlayerId { get; set; }
        [Required]
        public string FullName { get; set; }
        [Required]
        public string Team { get; set; }
        [Required]
        public int Level{get;set;}
    }
}

ve DbContext tipi.

using Microsoft.EntityFrameworkCore;

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

        public DbSet<Player> Players { get; set; }
    }
}

Tipik bir Entity Context sınıfı söz konusu. Örneğin basit olması açısından sadece Player sınıfına ait bir veri seti sunuyor ancak siz kendi denemelerinizi yaparken Master-Child ilişkide bir yapı kurgulayabilirseniz daha iyi olabilir. Bu işlemlerin ardından ValuesController sınıfını aşağıdaki gibi değiştirerek ilerleyebiliriz.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Players.Models;

namespace Players.Controllers
{
    [Route("fabrica/api/[controller]")]
    public class PlayersController 
        : Controller
    {
        private readonly PlayersDbContext _context;

        public PlayersController(PlayersDbContext context)
        {
            _context = context;
        }

        [HttpGet]
        public IActionResult Get()
        {
            var playerList = _context.Players.ToList();
            return Ok(new { Players = playerList });
        }

        [HttpPost]
        public IActionResult Create([FromBody]Player player)
        {
            if (!ModelState.IsValid)
                return BadRequest(ModelState);
            _context.Players.Add(player);
            _context.SaveChanges();
            return Ok(player);
        }

        [HttpPut("{playerId}")]
        public IActionResult Update(int playerId, [FromBody]Player player)
        {
            if (!ModelState.IsValid)
                return BadRequest(ModelState);
            var findResult = _context.Players.Find(playerId);
            if (findResult == null)
            {
                return NotFound();
            }
            findResult.FullName = player.FullName;
            findResult.Team = player.Team;
            findResult.Level=player.Level;
            _context.SaveChanges();
            return Ok(findResult);
        }

        [HttpDelete("{playerId}")]
        public IActionResult Delete(int playerId)
        {
            var findResult = _context.Players.Find(playerId);
            if (findResult == null)
            {
                return NotFound();
            }
            _context.Remove(findResult);
            _context.SaveChanges();
            return Ok(findResult);
        }
    }
}

Temel CRUD(Create Read Update Delete) operasyonlarını içeren bir Controller tipi olduğunu ifade edebiliriz. HTTP'nin POST, PUT, DELETE ve GET operasyonlarına cevap verecek metodlar içermekte. Bir oyuncu bilgisini veritabanına eklemek için Create metodu çalışıyor. Veri, HTTP talebinin Body kısmından alınmakta([FromBody] kullanımına dikkat) Benzer durum Update metodu için de söz konusu. HTTP adresinden gelen playerID bilgisi ve paketin Body'sinden çekilen JSON içeriğine göre bir güncelleme yapılmakta. Delete metodu sadece adresten gelen playerId bilgisini kullanarak bir silme operasyonunu icra ediyor. Get metodu tahmin edileceği üzere tüm oyuncu listesini döndürmekte. Elbette tüm listeyi döndürmek çok da tercih edeceğimiz bir yöntem değil ama burada amacımız West-World'de SQL Server'ı kullanan bir API servisini .Net Core ile yazmak ;)

Model, DbContext ve Controller tipleri hazır ama iş henüz bitmedi. Entity Framework hizmetinin orta katmana bildirilmesi gerekiyor. Bunun için Startup.cs sınıfındaki ConfigureServices fonksiyonunu değiştirmeliyiz. Aynen aşağıdaki gibi. 

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

Dikkat edileceği üzere AddDbContext fonksiyonunu kullanırken bir connection string tanımı mevcut. Bu West-World'e kurduğum SQL Server'a ulaşabilmek için gerekli. Diğer yandan generic tip olarak Models klasörüne eklediğimiz DbContext tipini belirtmekteyiz. Daha iyi olması açısından kullanıcı bilgilerini konfigurasyondan veya farklı bir ortamdan daha güvenli bir şekilde almayı deneyebilirsiniz. Konfigurasyonda okuyacaksanız kullanıcı adı ve şifre bilgilerinin kripto edilmiş hallerinin tutulmasında yarar var.

Testler

Artık testlere başlanabilir. İlk olarak,

dotnet run

komutu ile servisi yayına almak gerekiyor. Bundan sonrasında ise GET, POST, PUT, DELETE  komutlarını ayrı ayrı deneyimlemek lazım. Ben Postman'den yararlanarak ilgili testleri gerçekleştirdim(Bu arada Postman kullanmamız şart değil. Curl terminal komutunu kullanarak da aynı işlemleri gerçekleştirebilirsiniz ki araştırıp deneyimlemenizi öneririm)İlk olarak POST ile birkaç oyuncu bilgisi ekledim. 

Http metodumuz : POST
Adresimiz : http://localhost:5555/fabrica/api/players
Content-Type değeri application/json
Body'de gönderdiğimiz raw tipindeki içerikse örneğin
{"FullName":"Red Skins Mayk","Team":"Orlando Pelicans","Level":90}

Sonuç başarılıydı. 

Dikkati çekici noktaysa ilk isteğe cevap gelmesi için geçen yaklaşık 16 saniyelik kocaman sürey. Bunun veritabanının henüz ortada olmayışından kaynaklanan bir durum olduğu aşikar. Nitekim sonraki çağrılarda süre 200 milisaniyeler altına indi. Bir kaç oyuncu bilgisi daha ekledikten sonra Http GET operasyonu da denedim. 

Http Metodumuz : Get
Adresimiz : http://localhost:5555/fabrica/api/players

Sonuçların başarılı bir şekilde geldiğini görmek oldukça hoş. Hemen bir güncelleme işlemi denemenin tam sırası.

Http metodumuz : PUT
Adresimiz : http://localhost:5555/fabrica/api/players/1
Content-Type değeri application/json
Body'de gönderdiğimiz raw tipindeki içerikse örneğin
{"FullName":"Red Skin Mayk","Team":"Houston Motors","Level":55}

İşlem başarılıydı. HTTP 200 OK mesajı dönmüştü.

Gerçekten de tekrar Http Get talebi gönderdiğimde aşağıdaki sonuçla karşılaştım. 1 numaralı playerId içeriği istediğim gibi güncellenmişti.

Gerçi ben Red Skin Mayk'a takmıştım. Onu silmeye de karar verdim.

Http metodumuz : DELETE
Adresimiz : http://localhost:5555/fabrica/api/players/1

delete çağrısının sonucu

ve güncel oyuncu listesinin durumu.

Sql Sunucusunda Durum Ne?

Tüm bu işlemler kurulu olan SQL sunucusuna da yansımaktadır. Hemen aşağıdaki terminal komutlarını deneyerek gerçekten de orada da bir şeyler olduğunu kendi gözlerimizle görebiliriz. West-World'de durum aşağıdaki gibi gerçekleşti.

sqlcmd -S localhost -U SA -P 'P@ssw0rd'
select name from sys.databases
go
use PlayersDatabase
go
select FullName,Team from Players
go

Bu makalede konu West-World açısından heyecan vericiydi. Uzun yıllar birbirlerini görmeyen iki taraf buluşmuştu. Uygulamaya sizde bir şeyler katabilirsiniz. Söz gelimi SQL Server'ı Linux ortamınıza kurmak istemediğimizi düşünelim. Ne yapabiliriz? Acaba Docker bir çözüm olabilir mi? ;) Belki de Docker üzerinde konuşladıracağımız bir SQL Server hizmetini kullanabiliriz. API servisine de eklenecek bir çok şey olabilir. Bir Data Query servisi haline getirilebilir pekala. Hatta çalıştığınız yerde eğer SQL Server kullanıyorsanız ve deneysel amaçlı çalışmalar yapabileceğiniz bir ortamınız varsa SQL'in Linux üzerinde .Net Core ile yazılımış servislerle çalışması halindeki performans durumlarını da gözlemleyebilirsiniz. Her şey sizin elinizde. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Kaynaklar

Microsoft'un Resmi Dokümanı 

West-World'ün Uzaydan Gelen SQL Server ile Tanışması

$
0
0

Merhaba Arkadaşlar,

Renkler ve zevkler tartışılmaz. Hatta dünya öylesine renkli bir yerdir ki insanlar bazen neyi seçeceklerine karar veremeyebilir. Tabii ki işin içerisinde yazılım olunca bu renkler siyah ve beyaz gibi sadece iki seçeneğe de indirgenebilmişdir.

Hatta bu sadece yazılım için değil donanım için de söz konusu olmuştur. Hep bir kıyaslama vardır. PC'mi Mac'mi, RISC tabanlı mı CISC olanı mı, Intel'mi AMD'mi... Diğer yandan hepinizin bildiği üzere ben yaşlanmakta olan bir yazılımcıyım. Eski kuşak bir programcı olarak(bir başka deyişle 70li yılların ürünü bir birey olarak) benim neslin uzun yıllar içinde yer aldığı çatışmaların canlı şahitlerindenim. Hala süregelir ya bu sonu gelmez tartışmalar. Sizde içinde yer almışsınızdır mutlaka. Java'mı, .Net mi, SQL'mi Oracle'mı, OOP'mi Functional'mı, SOA'mı Microservices'mı vb diye sürer gider.

Bu ve benzeri tartışmalar daha da sürecek mi diye düşünürken şartlar uzun süre önce değişmeye başladı. Bugün bir Mac alıp üzerindeki Intel tabanlı işlemcide Windows koşturabiliyoruz. Hatta Microsoft açık kaynak dünyası ile çoktan el sıkıştı; ortaya .Net Core'u çıkardı. Yanılıyor muyum? Hatta Java Developers Day'a altın ortak bile oluverdi(Yaşıyorsa evet şu adrese girip Gold Sponsor kısmına bir bakın derim) 

Ne alıp veremedikleri vardı ki bu ayrı dünyalarda yaşadıklarını zanneden standart koyucuların, büyük oyucuların. Bu ayrı bir hikaye ama sonuçta bugün Linux üzerinde SQL Server kullanabilir ve hatta C# üzerinden onunla konuşabilir hale geldik. Barış güzel bir şey. İşte günün konusu. Linux üzerine SQL Server kurmak ve bir Web API servisi ile CRUD operasyonlarını deneyimlemek. 

SQL Server Kurulumu

Tabii ki ilk yapılması gereken Linux üzerine SQL server'ın kurulması. Her zaman ki gibi bu kurulum işleminde de terminalden yararlanacağız. Aşağıdaki komutları arka arkaya çalıştırarak işe başlayabiliriz. 

wget -qO- https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
sudo add-apt-repository "$(wget -qO- https://packages.microsoft.com/config/ubuntu/16.04/mssql-server-2017.list)"
sudo apt-get update
sudo apt-get install -y mssql-server

Bu adımlardan sonra SQL Server'ın Linux için konfigüre edilmesi gerekiyor. 

sudo /opt/mssql/bin/mssql-conf setup

Yukarıdaki komut sonrasında karşıma çıkan sorulara baktım ve Developer sürümü ile ilerleyeceğimi belirttim(Kuvvetle muhtemel ölene kadar developer olarak kalacağım :D) Bana sorulan SA(SQL'in meşhur System Admin kullanıcısı) için güzelde bir şifre belirledim.

İşlemlerin sorunsuz bir şekilde tamamlandığından emin olmanın yolu tahmin edileceği üzere SQL Server hizmetinin Linux ortamında çalışıp çalışmadığından emin olmak. Bunun için West-World'de aşağıdaki terminal komutunu vermem yeterliydi. Kısaca sistem kontrole mssql-server hizmetinin durumunu soruyoruz. Active durumunda olduğunu görmek yeterli.

systemctl status mssql-server

Sırada Command-Line Tool Kurulumu Var

Tabii ki SQL Server'ın West-World'e kurulması yeterli değil. Eğer Windows topraklarında olsaydım büyük ihtimalle Management Studio gibi bir şey de arayacaktım. Linux tarafında da bu tip bir araç kullanmak mümkün ama komut satırından da pekala bilinçli bir şekilde aynı işlemler halledilebilir. CLI aracının kurulumu için aşağıdaki komutların çalıştırılması yeterli.

wget -qO- https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
sudo add-apt-repository "$(wget -qO- https://packages.microsoft.com/config/ubuntu/16.04/prod.list)"
sudo apt-get update
sudo apt-get install -y mssql-tools unixodbc-dev
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc
source ~/.bashrc

Artık West-World üzerinde biraz SQLce(eskilerde sqlCe diye bir sürüm de vardı) konuşmaya başlayabilirim. Örneğin bir veritabanı ve tablolarını üretebilir bu tabloya veri ekleyebilirim. İşte başlamak için terminalden aşağıdaki komutu vermek yeterli.

sqlcmd -S localhost -U SA -P '...'

-S anahtarı sonrası bağlanacağımız sunucuyu, -U sonrası bağlanacak kullanıcıyı ve -P sonrası da bu kullanıcının şifresini belirtiyoruz. West-World güzel havadan olsa gerek oldukça ılımlı. Sorunsuz bir şekilde bağlantı isteklerini kabul edip komut satırını kullanımıma açtı. Ben de bu teklifi memnuniyetle karşıladım ve veritabanı oluşturmak için aşağıdaki komutu kullandım.

CREATE DATABASE azon
Go

var olan veritabanlarını görmek içinse aşağıdaki komutu. Amacım tabii ki de az önce oluşturduğum Azon isimli veritabanını sistem içerisinde görebilmekti.

SELECT Name from sys.Databases
go

Derken o an üzerinde çalışılacak veritabanını değiştirmeye ve bir tablo oluşturup ona birkaç örnek veri satırı eklemeye karar verdim.

use azon
go

create table Category(id int,name nvarchar(50))
go

insert into Category values(1,'book');
insert into Category values(2,'movie');
insert into Category values(3,'music');
go
select * from Category
go

Örnekte azon isimli bir veritabanı oluşturuluyor. Sonrasında bu veritabanının kullanılacağı belirtiliyor. Use ifadesinin çalıştırılması sonrası komut satırından gerçekleştirilecek tüm işlemler azon veritabanı için geçerli olacaktır. Category tablosu tamamen deneysel amaçlı. id ve name alanlarından oluşuyor. İçine 3 satır veri ekleniyor ve son olarak tüm içeriğe bir Select sorgusu atılıyor. Sonuçlar West-World için aşağıdaki gibiydi. 

Doğruyu söylemek gerekirse West-World, SQL Server ile terminal üzerinden gayet güzel bir biçimde anlaşıyordu. Database oluşturulabiliyor, içerisine tablo konulup veriler eklenebiliyordu. Pekiiiii ya bu işin içerisine .Net Core ile yazılmış bir Web API servisini de katabilir miydim? Elebette bu mümkündü. Beni heyecanladıran kısım bunun Linux tabanlı bir sistemde olması.

Web API Servisinin Yazılması

İşe bir .Net Core Web API servisini oluşturmakla başlamak lazım. Visual Studio Code arabirimini açıp terminalden aşağıdaki komutları vererek ilerleyebiliriz.

dotnet new webapi -o Players
cd Players
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 2.0.2

Players isimli bir Web API uygulamamız var. Entity Framework paketinin SQL Server için yazılmış sürümünü kullanıyoruz. Servisin standart Values Controller tipini değiştirmeden önce Models isimli bir klasör oluşturup içerisine gerekli tipleri ekleyebiliriz. Bir oyuncunun temel özelliklerini barındıracak olan Player sınıfı,

using System.ComponentModel.DataAnnotations;

namespace Players.Models  
{
    public class Player
    {
        public int PlayerId { get; set; }
        [Required]
        public string FullName { get; set; }
        [Required]
        public string Team { get; set; }
        [Required]
        public int Level{get;set;}
    }
}

ve DbContext tipi.

using Microsoft.EntityFrameworkCore;

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

        public DbSet<Player> Players { get; set; }
    }
}

Tipik bir Entity Context sınıfı söz konusu. Örneğin basit olması açısından sadece Player sınıfına ait bir veri seti sunuyor ancak siz kendi denemelerinizi yaparken Master-Child ilişkide bir yapı kurgulayabilirseniz daha iyi olabilir. Bu işlemlerin ardından ValuesController sınıfını aşağıdaki gibi değiştirerek ilerleyebiliriz.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Players.Models;

namespace Players.Controllers
{
    [Route("fabrica/api/[controller]")]
    public class PlayersController 
        : Controller
    {
        private readonly PlayersDbContext _context;

        public PlayersController(PlayersDbContext context)
        {
            _context = context;
        }

        [HttpGet]
        public IActionResult Get()
        {
            var playerList = _context.Players.ToList();
            return Ok(new { Players = playerList });
        }

        [HttpPost]
        public IActionResult Create([FromBody]Player player)
        {
            if (!ModelState.IsValid)
                return BadRequest(ModelState);
            _context.Players.Add(player);
            _context.SaveChanges();
            return Ok(player);
        }

        [HttpPut("{playerId}")]
        public IActionResult Update(int playerId, [FromBody]Player player)
        {
            if (!ModelState.IsValid)
                return BadRequest(ModelState);
            var findResult = _context.Players.Find(playerId);
            if (findResult == null)
            {
                return NotFound();
            }
            findResult.FullName = player.FullName;
            findResult.Team = player.Team;
            findResult.Level=player.Level;
            _context.SaveChanges();
            return Ok(findResult);
        }

        [HttpDelete("{playerId}")]
        public IActionResult Delete(int playerId)
        {
            var findResult = _context.Players.Find(playerId);
            if (findResult == null)
            {
                return NotFound();
            }
            _context.Remove(findResult);
            _context.SaveChanges();
            return Ok(findResult);
        }
    }
}

Temel CRUD(Create Read Update Delete) operasyonlarını içeren bir Controller tipi olduğunu ifade edebiliriz. HTTP'nin POST, PUT, DELETE ve GET operasyonlarına cevap verecek metodlar içermekte. Bir oyuncu bilgisini veritabanına eklemek için Create metodu çalışıyor. Veri, HTTP talebinin Body kısmından alınmakta([FromBody] kullanımına dikkat) Benzer durum Update metodu için de söz konusu. HTTP adresinden gelen playerID bilgisi ve paketin Body'sinden çekilen JSON içeriğine göre bir güncelleme yapılmakta. Delete metodu sadece adresten gelen playerId bilgisini kullanarak bir silme operasyonunu icra ediyor. Get metodu tahmin edileceği üzere tüm oyuncu listesini döndürmekte. Elbette tüm listeyi döndürmek çok da tercih edeceğimiz bir yöntem değil ama burada amacımız West-World'de SQL Server'ı kullanan bir API servisini .Net Core ile yazmak ;)

Model, DbContext ve Controller tipleri hazır ama iş henüz bitmedi. Entity Framework hizmetinin orta katmana bildirilmesi gerekiyor. Bunun için Startup.cs sınıfındaki ConfigureServices fonksiyonunu değiştirmeliyiz. Aynen aşağıdaki gibi. 

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

Dikkat edileceği üzere AddDbContext fonksiyonunu kullanırken bir connection string tanımı mevcut. Bu West-World'e kurduğum SQL Server'a ulaşabilmek için gerekli. Diğer yandan generic tip olarak Models klasörüne eklediğimiz DbContext tipini belirtmekteyiz. Daha iyi olması açısından kullanıcı bilgilerini konfigurasyondan veya farklı bir ortamdan daha güvenli bir şekilde almayı deneyebilirsiniz. Konfigurasyonda okuyacaksanız kullanıcı adı ve şifre bilgilerinin kripto edilmiş hallerinin tutulmasında yarar var.

Testler

Artık testlere başlanabilir. İlk olarak,

dotnet run

komutu ile servisi yayına almak gerekiyor. Bundan sonrasında ise GET, POST, PUT, DELETE  komutlarını ayrı ayrı deneyimlemek lazım. Ben Postman'den yararlanarak ilgili testleri gerçekleştirdim(Bu arada Postman kullanmamız şart değil. Curl terminal komutunu kullanarak da aynı işlemleri gerçekleştirebilirsiniz ki araştırıp deneyimlemenizi öneririm)İlk olarak POST ile birkaç oyuncu bilgisi ekledim. 

Http metodumuz : POST
Adresimiz : http://localhost:5555/fabrica/api/players
Content-Type değeri application/json
Body'de gönderdiğimiz raw tipindeki içerikse örneğin
{"FullName":"Red Skins Mayk","Team":"Orlando Pelicans","Level":90}

Sonuç başarılıydı. 

Dikkati çekici noktaysa ilk isteğe cevap gelmesi için geçen yaklaşık 16 saniyelik kocaman sürey. Bunun veritabanının henüz ortada olmayışından kaynaklanan bir durum olduğu aşikar. Nitekim sonraki çağrılarda süre 200 milisaniyeler altına indi. Bir kaç oyuncu bilgisi daha ekledikten sonra Http GET operasyonu da denedim. 

Http Metodumuz : Get
Adresimiz : http://localhost:5555/fabrica/api/players

Sonuçların başarılı bir şekilde geldiğini görmek oldukça hoş. Hemen bir güncelleme işlemi denemenin tam sırası.

Http metodumuz : PUT
Adresimiz : http://localhost:5555/fabrica/api/players/1
Content-Type değeri application/json
Body'de gönderdiğimiz raw tipindeki içerikse örneğin
{"FullName":"Red Skin Mayk","Team":"Houston Motors","Level":55}

İşlem başarılıydı. HTTP 200 OK mesajı dönmüştü.

Gerçekten de tekrar Http Get talebi gönderdiğimde aşağıdaki sonuçla karşılaştım. 1 numaralı playerId içeriği istediğim gibi güncellenmişti.

Gerçi ben Red Skin Mayk'a takmıştım. Onu silmeye de karar verdim.

Http metodumuz : DELETE
Adresimiz : http://localhost:5555/fabrica/api/players/1

delete çağrısının sonucu

ve güncel oyuncu listesinin durumu.

Sql Sunucusunda Durum Ne?

Tüm bu işlemler kurulu olan SQL sunucusuna da yansımaktadır. Hemen aşağıdaki terminal komutlarını deneyerek gerçekten de orada da bir şeyler olduğunu kendi gözlerimizle görebiliriz. West-World'de durum aşağıdaki gibi gerçekleşti.

sqlcmd -S localhost -U SA -P 'P@ssw0rd'
select name from sys.databases
go
use PlayersDatabase
go
select FullName,Team from Players
go

Bu makalede konu West-World açısından heyecan vericiydi. Uzun yıllar birbirlerini görmeyen iki taraf buluşmuştu. Uygulamaya sizde bir şeyler katabilirsiniz. Söz gelimi SQL Server'ı Linux ortamınıza kurmak istemediğimizi düşünelim. Ne yapabiliriz? Acaba Docker bir çözüm olabilir mi? ;) Belki de Docker üzerinde konuşladıracağımız bir SQL Server hizmetini kullanabiliriz. API servisine de eklenecek bir çok şey olabilir. Bir Data Query servisi haline getirilebilir pekala. Hatta çalıştığınız yerde eğer SQL Server kullanıyorsanız ve deneysel amaçlı çalışmalar yapabileceğiniz bir ortamınız varsa SQL'in Linux üzerinde .Net Core ile yazılımış servislerle çalışması halindeki performans durumlarını da gözlemleyebilirsiniz. Her şey sizin elinizde. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Kaynaklar

Microsoft'un Resmi Dokümanı 

Node.js ile Basit Cluster Kurguları

$
0
0

Merhaba Arkadaşlar,

Programcılıkla uğraşan bizim gibi organizmalar sükunetle kod yazmaya bayılır. Hatta her şeyin sorunsuz işlediği, test'lerin prüzsüz ilerlediği, taşımaların tereyağından kıl çeker gibi kolay olduğu bir yaşam alanı düşler. Ne yazık ki gerçek hayat çoğu zaman böyle değildir. Bilirsiniz işte...Sıkışık proje süreleri, anlamakta güçlük çektiğimiz iş süreçleri, değişen ve öğrenmemiz gereken yeni nesil teknolojiler, aniden ortaya çıkan Murphy kanunları vs derken bir bakmışız ki barut fıçısına dönmüşüz. Kim bilir kaç kere içimizden bir Hulk fırlamak üzere düşe gelmiştir. Bazen benim de bu tip gıcık olduğum anlar olmuyor değil. Zaten çalışılması zor, huysuz ve aksi bir insanken bunlara birde ters giden işler eklenince, iyice çekilmez oluyorum.

Ancak son yıllarda kendime güzel bir tedavi yöntemi bulduğumu söyleyebilirim. Öyle ki üzerimdeki tüm negatif enerjiyi alıp götürmeye yetiyor(En azından 2003ten beri işe yaradığını söyleyebilirim) Geçtiğimiz hafta içersinde de böyle hafiften gerginleşen sinirlerimi yatıştırmak için atladım Red Enterprise'ın akşamki ilk trenine, düştüm West-World yollarına. Gün batarken Node oteldeki odama çoktan yerleşmiş taze demlenmiş çayımı yudumluyordum bile. Kulaklarımda Freddie Hultana'dan Le Practicante'si tınlarken açtım özet notlarımı ve başladım yazmaya.

Ölçeklenebilirlik(Scalability) ve Node.js

Node.js ile yazdığımız uygulamaları genel olarak node [application_name.js] şeklinde çalıştırıyoruz/çalıştırıyordum. Aslında bu durumda söz konusu uygulama tekil bir iş parçacığı olarak(Single Thread)çalışmakta. Dolayısıyla birden fazla iş parçacığını çalıştırıp tüm işlemci/çekirdek gücünü almaktan yoksun kalıyoruz. Aslında özellikle web sunucuları/servisleri geliştirebileceğimiz etkili bir ortam söz konusu iken bu tip bir avantajdan faydalanamamak yazık olurdu. NodeJS ile birlikte gelen cluster isimli modül bu konuda bize önemli fonksiyonellikler sunuyor.

Buna göre bir iş parçacığını çatallayarak(fork) alt iş parçacıkları oluşturmamız mümkün. Bu iş parçacıklarını işlemci veya çekirdek sayısına göre oluşturarak aynı uygulamanın kendi bellek alanlarında çalışacak farklı örneklerini işletmemiz mümkün oluyor. Bunu daha çok bir web sunucusunu birinci seviyede ölçeklemek için kullanabiliriz. Yani web suncusuna gelen talepler için aynı adres:port'un farklı çalışma zamanı örneklerine yönlendirme yapılacak şekilde basit bir ölçekleme mekanizması kurgulayabiliriz(Sonlara doğru buna bir örnek vereceğiz)

Node.js'in tasarımı gereği dağıtık uygulamaların(Distributed Applications) farklı boğumlarda çalıştırılabilmesi üzerine kurulmuştur. Nitekim çoklu iş parçacıklarını kullanmak bir Node.js uygulamasını ölçeklemenin en etkili yoludur.

cluster modülünü kullanarak yazacağımız mekanizma oldukça basit. Tek bir uygulamaya gelen talepleri master veya child olma hallerine göre değerlendireceğiz. Uygulama ilk çalıştığında cluster modeline göre master process konumunda olacaktır. Bu koşula bakarak istediğimiz sayıda alt iş parçacığını(child process) oluşturabiliriz. Tabii istediğimiz sayıda derken bunu abartmamak, belli bir standarda göre yapmak(örneğin işlemci/çekirdek sayısı kadar) daha doğru bir yaklaşım olacaktır. Bu noktada aklıma Microsoft'un Task Parallel Library ile ilgili oluşturduğu doküman geldi. Tekrar konumuza dönelim. Uygulama başlatıldı, master iş parçacığında olduğumuz fark edildi ve ana iş, alt parçacıklara çatallanmaya başlandı. Her çatal aslında aynı uygulamanın yeni bir örneğinin de başlatılması anlamına gelir. Buna göre aynı uygulamaya bu kez bir alt iş parçacığı olarak gelinecektir. Bunu da cluster'ın master olmama halinde ele alabiliriz ki bu sayede aynı uygulama kodu içerisinde alt iş parçacıklarını da kontrol edebiliriz.

Pek tabii oluşan bu alt iş parçacıkları ve ana iş parçacığının aralarında haberleşmesi gerekebilir. Bu noktada her iş parçacığının kendi örneğine sahip olduğunu(hatta kendi V8 tabanlı örneğini çalıştırdığını) ve belleği ortaklaşa paylaşmadıklarını belirtmemiz gerekiyor. Ancak birbirlerine mesaj gönderebilirler. Bu mesajlaşma trafiği de şu adreste detaylarını bulabileceğiniz Inter Process Communication standardı ile sağlanmakta. Kısaca ana iş parçacığı alt iş parçacıklarına veya alt iş parçacıkları da ana iş parçacığına mesaj gönderebilir. Örneğe geçmeden önce son olarak ana iş parçacığının çeşitli olaylar ile alt iş parçacıklarını takip edebildiğini de belirtelim(fork, online, listening, exit)

Hello Clustering

Dilerseniz çok basit bir örnek ile konuyu anlamaya çalışalım. Alışılageldiği üzere kodları Ubuntu sisteminde Visual Studio Code ile geliştiriyorum.

cluster_sample_1.js

var cluster = require('cluster');

if (cluster.isMaster) {
    console.log('Master process ' + process.pid);
    for (var i = 0; i < 4; i++) {
        console.log('Worker #' + i + ' is starting.');
        cluster.fork();
    }

    cluster.on('fork', function (worker) {
        console.log('\tfork event (worker ' + worker.process.pid + ')');
    });

    cluster.on('online', function (worker) {
        console.log('\tonline event (worker ' + worker.process.pid + ')');
    })

    cluster.on('exit', function (worker) {
        console.log('\texit event (worker ' + worker.process.pid + ')');
    });

} else {
    console.log('Aloha. My name is worker #' + process.pid);
    cluster.worker.destroy();
}

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

Neler oldu bir bakalım? Kodu ilk çalıştırdığımızda isMaster kontrolüne girdik ve o anda ana iş parçacığı söz konusuydu. Dört tane alt iş parçacığı oluşturduk. Bunun için fork metodundan yararlanıyoruz. Sonrasında bazı olayları ele almak için fonksiyonellikler dahil ettik. Bir alt iş parçacığı oluştuğunda fork, yaşamaya başladığında online ve yok edildiğinde exit olayları çalışır. Başka olaylar da var. İlerleyen kodlarda göreceğiz. fork fonksiyonunun etkisi aynı kodun tekrar çalıştırılmasıdır. Bu durumda else bloğuna gireceğiz çünkü ilk alt iş parçacığı oluştuğu andan tamamı sonlanıncaya kadar isMaster false dönecektir. else bloğunda bu koda özel sadece destroy işlemini uyguluyoruz. Kısacası alt iş parçacıkları oluşuyor ve yok ediliyorlar. Ana ve alt iş parçacıklarını iyi izeyebilmek için Process ID değerlerini kullandık. Tüm olaylar dikkat edileceği üzere bir callback fonksiyonu içermekte. 

İş Parçacıkları Arası Mesajlaşma

Şimdi bir de master ve child iş parçacıklarının nasıl haberleşebileceğine bakalım. Aslında birbirlerine JSON formatında mesajlar gönderecekler. Örnek kod parçacığını aşağıdaki gibi geliştirebiliriz.

cluster_sample_2.js

var cluster = require('cluster');
var workers = [];
var names = ['con do', 'vuki', 'lora', 'deymin', 'mayk', 'cordi', 'klaus', 'commander', 'jenkins', 'semuel', 'fire starter'];
var colors = ['red', 'green', 'blue', 'gold', 'white', 'black', 'brown', 'yellow', 'gray', 'silver'];
if (cluster.isMaster) {
    console.log('I am the process #' + process.pid);
    for (var i = 0; i < 3; i++) {
        var worker = cluster.fork();
        workers.push(worker);
        worker.on('message', function (message) {
            console.log('\t\tChild says that:' + JSON.stringify(message));
        });
        workers.forEach(function (worker) {
            var index = Math.floor(Math.random() * names.length) + 1;
            worker.send({ name: names[index - 1] });
        }, this);
    }

} else {
    console.log('Aloha. I am the worker process #' + process.pid);
    process.on('message', function (message) {
        console.log('\The boss says that: ' + JSON.stringify(message));
    });
    var index = Math.floor(Math.random() * colors.length) + 1;
    process.send({ color: colors[index - 1] });
    cluster.worker.destroy();
}

Bu sefer ana ve alt iş parçacıkları arasında mesajlaşma yapmaya çalışıyoruz. Olayımızın adı message. Her zaman ki gibi söz konusu olayı ilgili nesnenin on fonksiyonunu kullanarak yakalıyoruz. Alt iş parçacığından üste veya tam tersi istikamete mesaj göndermek için mesaj göndermek istediğimiz nesne örneğinin send fonksiyonundan yararlanmaktayız. Örneği daha anlaşılır kılmak için names ve colors isimli dizilerden çektiğimiz rastgele değerleri kullanıyoruz. Ana iş parçacığı her alt iş parçacığına mesaj göndersin diye worker nesnelerini tuttuğumuz bir dizimiz de var. İşte çalışma zamanı çıktıları.

Web Server Örneği

Yazımızın başında da belirttiğimiz üzere bir web sunucusunun birinci seviyede ölçeklendirilmesi mümkün. Aslında aynı adres:port'a doğru gelen taleplerin birden fazla iş parçacığı tarafından ele alınmaya çalışıldığını ve bunun için arka planda çalışan basit bir load balancing mekanizması olduğunu ifade edebiliriz. Örnek kod parçamıza bakıp konuyu daha iyi anlamaya çalışalım.

cluster_sample_3.js 

var cluster = require('cluster');
var http = require('http');
var cpuCount = 2;
var names = ['con do', 'vuki', 'lora', 'deymin', 'meyk', 'cordi', 'klaus', 'commander', 'jenkins', 'semuel', 'fire starter'];

if (cluster.isMaster) {
    console.log('Master PID: ' + process.pid);
    for (var i = 0; i < cpuCount; i++) {
        cluster.fork();
    }

    cluster.on('fork', function (worker) {
        console.log('\tfork (worker ' + worker.process.pid + ')');
    });

    cluster.on('online', function (worker) {
        console.log('\tonline (worker ' + worker.process.pid + ')');
    })

    cluster.on('listening', function (worker, address) {
        console.log('\tlistening (worker ' + worker.id + ') pid ' + worker.process.pid + ', ' + address.address + ':' + address.port + ')');
    });

    cluster.on('exit', function (worker) {
        console.log('\texit (worker ' + worker.process.pid + ')');
    });

} else {
    console.log('Worker # has been' + process.pid + ' started.');
    http.createServer(function (req, res) {
        res.writeHead(200);
        var index = Math.floor(Math.random() * names.length) + 1;
        res.end('My name is "' + names[index - 1] + '" (pid ' + cluster.worker.process.pid + ')\n');
    }).listen(65001, "127.0.0.1");
}

Diğer örneklerde olduğu gibi program ilk çalıştığında iş parçacığının master olup olmamasına göre hareket ediyoruz. Master olmama hali daha dikkat çekici. Yani else bloğu. Burada çatallanan her iş parçacığı içerisinde yeni bir sunucu oluşturduğumuzu görebilirsiniz. Dikkat çekici nokta ise her birinin aynı ip:port bilgisini kullanıyor olması. Normal şartlarda çalışma zamanının buna kızması gerekir biliyorsunuz ki. Ne var ki o gizemli Load Balancer mekanizması bizim için gerekli yönlendirmeleri yapıyor.

Uygulamayı en az iki farklı tarayıcı ile denememizde yarar var. Nitekim şu adreste belirtildiği üzere Keep Alive sorunsalı sebebiyle aynı tarayıcıya her zaman için aynı iş parçacığının bakması söz konusu olabilir. Daha tutarlı bir çözüm olarak Load Balancer mekanizmasını yönetebiliyor olmak önemli sanırım. Gerçi burada şöyle bir düzenekte kurulabilir: Talepleri belli bir eşik değerine kadar çatallayıp du değere ulaşıldığında tüm alt iş parçalarını yok edebiliriz(Lakin state'leri nasıl saklarız orası da bir soru işareti. Daha derin düşün Burak) Bunu cluster nesnesinin exit olayında kontrol altına alıp yeni alt iş parçacıklarının tekrardan çatallanmasını sağlayabiliriz. Yine de daha etkili çözümler var tabii ki. Bu işin duayenlerinden olan NGinX' in şu adresteki yazısına bir bakın derim ;)

Şimdilik kendi sistemimde aşağıdaki ekran görüntüsünde yer alan sonuçları elde ettim. Dikkat edileceği üzere Chrome ve Firefox tarayıcıları farklı iş parçacıkları tarafından ele alınmakta(pid değerine bakın)

Bu yazımızda cluster modülünü kullanarak ana iş parçacığından farklı iş parçacıklarının nasıl dallandırılabileceğini incelemeye çalıştık. Aslında konunun özelinde Node.js'in child-Process adı verilen bir konsepti bulunuyor. Child Process kavramı göz önüne alındığında spawn, execFile, exec, fork gibi çeşitli operasyonlar var. cluster, fork işlemini basitleştiriyor diyebiliriz. Bu yapıları özümsemek içinde Node.js'in stream ve event-driven konularını da iyi bilmek gerekiyor. Ben halen bu konulara bakmaktayım. Bir şeyler pekişince yazmak istiyorum. Şimdilik benden bu kadar. Gün hafif hafif ağırmaya başladı. Yola düşsem iyi olacak. Enterprise hava yollarının ilk seferi ile tekrardan eve dönme vakti gelmiş bile. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Örneklere github'dan erişebilirsiniz.

http://www.buraksenyurt.com/post/Pug-Pug-PugPug, Pug, Pug

$
0
0

Merhaba Arkadaşlar,

Kısa bir süre önce çalışmakta olduğum şirkette epey eğlenceli bir mevzunun içerisinde kaldım. Daha önceden de bahsettiğim gibi kurumumuzun bize sunduğu güzel bir hizmet var...Pluralsight. Ne var ki benim gibi dikkatsiz kullanıcılar için enteresan şeyler olabiliyor. İzin verin hikayeyi size anlatayım; İlk hedefim oldukça hoşuma giden Node.js tarafında ilerlemekti.

Bu amaçla kendime çizdiğim kariyer yolunda(aslında Pluralsight Path demek daha doğru olur) bir takım Node.js eğitimlerini izlemeye başladım. Hatam bu eğitimlerin tarihlerine pek dikkat etmiyor oluşumdu. Öyle ki eğitimde sözü geçen bir npm modülü pekala eskimiş olabilirdi.

O hafta izlediğim eğitim serisi Node.js ile web programlama üzerineydi. Express ile işin içerisine girdikten sonra bir takım yardımcı çatılardan daha bahsedilmeye başlandı. Derken HTML'in sıkıcı olan o açısal ayraçlarından bizi kurtaran Jade isimli bir paketle karşılaştım. İnanılmaz hoşuma gitti. Nerden bilebilirdim aslında onun adının sonradan Pug olarak değiştirildiğinden ve hatta çalıştığım kurumun yeni nesil .Net projelerinde bu ürünü kullandığından:)

Bu arada ben Jade kod adını daha çok beğendiğimi ifade etmek isterim. Nitekim "Yeşim Taşı" olarak anlam kazanır. Oysa ki pug...Peah...Buldoğa benzeyen ufak bir köpek olduğu ifade ediliyor. Aslında Jade'in Pug olma hiyakesini şuradan okuyabilirsiniz.

O Cuma günü her zaman ki gibi sabah izlediğim derslerin bir uygulamasını gün içerisinde sıkıldığım anlarda yapmıştım(aman patron duymasın) Sonrasında sevgili Hilmi ağabeye durumu anlatmak istedim. Sık sık öğrendiklerimizi birbirimizle paylaşıyırız zaten. Heyecanlı bir şekilde anlattım da anlattım...Hatta yaptıklarımı gösterdim bile. Sonra çok güzel bir cümle sarfetti "yahu bunun bizim kullandığımız Pug'dan farkı ne? Neredeyse aynı..." Hemen google'ladık tabii...Ne gördük dersiniz? Pug meğerse Jade'in yeni adıymış. Önce birbirimize şaşkın şaşkın baktık ve bir süre sonra kahkayı patlattık :D

Tabii ki yaptığım kodlar çöpe gitmedi. Jade yerine hemen Pug paketini uygulamaya dahil edip aynı sonuçları elde etmeyi başardım. Peki neydi o becerebildiğim ve beni heyecanlandıran şey? Malum uzun yıllar back-end tarafında geliştirme yapmış birisi olarak front-end benim için epey geniş ve bir o kadar da korkutucu bir alan. Ancak görevim gereği o noktalara da dokunmam gerekiyor. Dilerseniz elde ettiğim sonucu göstererek başlayalım. Aşağıdaki ekran görüntüsüne bakalım.

Tarayıcı penceresinde açılmış basit bir HTML içeriği görüyorsunuz aslında değil mi? Hatta berbat ötesi bir tasarımı da var buna hiç şüphe yok :) Peki ya aslında bu sayfanın HTML tarafının aşağıdaki gibi yazıldığını söylesem.

html
    head
        meta(charset='utf-8')
        link(href='https://fonts.googleapis.com/css?family=Chewy',rel='stylesheet')
        title I love this game
    body(style='width:410px;font-family:Chewy,cursive')
        h2(id='myId',style='background-color:darkblue;color:white') İ s t a t i s t i k l e r
        p
            h3(style='background-color:red;color:white') En iyi takımlar
            p.
                NBA tarihinde tüm zamanların en iyi takımları   
            ul
                each team in teams
                    b
                        li=team
        p
            h3(style='background-color:green;color:white') En iyi oyuncular
            p.
                NBA tarihinin bugüne kadar gelmiş geçmiş en iyi oyuncuları
            mixin player-card(player)
                div(style='background-color:lightgray').player-card
                    a(href=player.url)
                        div.player-name=player.name
                    p
                        img.player-photo(src=player.photo)
                    p
                        i.heigth=player.height                        
                    p
                        i.bio=player.bio     
                    p                                        
            for player in players
                +player-card(player)

Şimdi işler biraz değişti değil mi? En azından bir farkındalık oluştu. Normalde yukarıdaki içeriğin görüntülenmesi için klasik ve bilinen yöntemlerle aşağıdakine benzer bir HTML dosyası hazırlanır.

<html><head><meta charset="utf-8" /><link href="https://fonts.googleapis.com/css?family=Chewy" rel="stylesheet" /><title>I love this game</title></head><body style="width:410px;font-family:Chewy,cursive"><h2 id="myId" style="background-color:darkblue;color:white">İ s t a t i s t i k l e r</h2><p><h3 style="background-color:red;color:white">En iyi takımlar</h3><p>NBA tarihinde tüm zamanların en iyi takımları </p><ul><b><li>bulls</li></b><b><li>celtics</li></b><b><li>lakers</li></b></ul></p><p><h3 style="background-color:green;color:white">En iyi oyuncular</h3><p>NBA tarihinin bugüne kadar gelmiş geçmiş en iyi oyuncuları</p><div class="player-card" style="background-color:lightgray"><a href="http://www.espn.com/nba/player/_/id/3975"><div class="player-name">sitivin köri</div></a><p><img class="player-photo" src="curry.gif" /></p><p><i class="heigth">190cm</i></p><p><i class="bio">benzersiz top tekniği, yüksek şut yüzdesi, sınır tanımaz üçlükleri...</i></p><p> </p></div><div class="player-card" style="background-color:lightgray"><a href="http://www.espn.com/nba/player/_/id/1966"><div class="player-name">löbron ceyms</div></a><p><img class="player-photo" src="lebron.gif" /></p><p><i class="heigth">206cm</i></p><p><i class="bio">çok güçlü, ani hızlanma, meydan okuma, istatistikleri alt üst etme...</i></p><p> </p></div><div class="player-card" style="background-color:lightgray"><a href="http://www.espn.com/nba/player/_/id/2384"><div class="player-name">divayt hauvırd</div></a><p><img class="player-photo" src="howard.gif" /></p><p><i class="heigth">206cm</i></p><p><i class="bio">o boyla o fudamental hareketleri yapabiliyor olmak, oyun zekası...</i></p><p> </p></div></p></body></html>

İşte Pug'ın ortaya koyduğu fark bu. "İki resim arasındaki 9 farkı bulun" bulmacasını hatırlar mısınız bilmem ama bu kötü espriyi yapmanın tam yeri ve zamanı sanırım :) Gelin kolları sıvayalım ve bu işi nasıl yaptığımızı kısaca özetleyelim. Uygulamanın bir kısmını şirket kaynaklarını kullaranak Windows tabanlı bir sistem üzerinde yapmış olsam da asıl tecrübe etmek istediğim yer elbette ki West-World topraklarıydı. Ubuntu'nun başına geçtim ve sonradan aşağıdaki ekran görüntüsünde yer alan yapıya dönüşecek klasör ağacını oluşturmaya başladım.

public/images klasörü içerisinde basketbol oyuncularına ait fotoğraflar yer alıyor(itinayla taranmışlardır) src/views altında pug uzantılı dosyamız bulunmakta. Bu yazının ilk kısmında paylaşmış olduğumuz içeriğe sahip. Aslında dikkatli bir şekilde okuduğunuzda girinti sistemine dayanan ve bu sayede HTML'in < ve > gibi açısal ayraçlarını geride bırakan bir yazım stili söz konusu olduğunu rahatlıkla anlayabilirsiniz. Klasörde yer alan server.js ve package.json içeriklerini biraz sonra dolduracağız. Nitekim yapmamız gereken bazı ön hazırlıklar var. İlk olarak terminal'den aşağıdaki komutları kullanarak gerekli yüklemeleri yapalım.

npm init
npm install express --save
npm install pug --save

npm init kullanımı ile package.json dosyasının oluşmasını sağlıyoruz. Bu komutu çalıştırdığımızda bazı sorularla da karşılaşırız. Uygulamanın adı, versiyonu, yazarı, test komutu vs...Diğer iki komut express ve pug paketlerinin yüklenmesi için kullanılıyorlar. express paketini web hizmetleri için kullanacağız. Static içeriklerin görüntülenmesi veya belirlenen adrese gelen taleplerin yönetilmesi dışında servis geliştirilmesinde de kullanılabilir. Pug ise az önce sıklıkla değindiğimiz yeni stil HTML şablonunun çalışma zamanında anlaşılabilmesi için gerekli. Bu işlemler sonrasında en azından West-World için ilgili package.json içeriğinin aşağıdaki gibi oluştuğunu ifade edebilirim.

{
  "name": "en.bi.ey",
  "version": "1.0.0",
  "description": "NBA istatistikleri",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "keywords": [
    "nba",
    "spor",
    "istatistik",
    "nodejs"
  ],
  "author": "burak selim senyurt",
  "license": "ISC",
  "dependencies": {
    "express": "^4.16.3",
    "pug": "^2.0.3"
  }
}

Package.json içerisinde müdahale ettiğimiz bir kısım var. scripts elementine dikkat ederseniz start özelliği için node server.js şeklinde bir tanımlama yaptık. Buna göre uygulamamızı "node server.js" yerine "npm start" terminal komutu ile de çalıştırabiliriz ki genel jargon bu yöndedir.

Gelelim server.js dosyasının içeriğine. Burada da öğrendiğim bir çok şey olduğunu ifade edebilirim. Python, Ruby, Go gibi pek çok programlama ortamındakine benzer bir işleyiş söz konusu tabii ama Node.js açısından düşündüğümde benim için yeni yeni şeyler...

var express = require('express');
var app = express();
var port = 65003;

app.use(express.static('public/images'))
app.set('views', './src/views');
app.set('view engine', 'pug');

app.get('/', function (req, res) {
    res.render('index'
        , {
            teams: ['bulls', 'celtics', 'lakers'],
            players: [
                {
                    name: 'sitivin köri',
                    bio: 'benzersiz top tekniği, yüksek şut yüzdesi, sınır tanımaz üçlükleri...',
                    height: "190cm",
                    url: 'http://www.espn.com/nba/player/_/id/3975',
                    photo: 'curry.gif'
                },
                {
                    name: 'löbron ceyms',
                    bio: 'çok güçlü, ani hızlanma, meydan okuma, istatistikleri alt üst etme...',
                    height: "206cm",
                    url: 'http://www.espn.com/nba/player/_/id/1966',
                    photo: 'lebron.gif'
                },
                {
                    name: 'divayt hauvırd',
                    bio: 'o boyla o fudamental hareketleri ​_yapabiliyor olmak, oyun zekası...',
                    height: "206cm",
                    url: 'http://www.espn.com/nba/player/_/id/2384',
                    photo: 'howard.gif'
                }
            ]
        }
    );
});

app.listen(port, function (err) {
    console.log('running server on port ' + port);
});

Neler olduğuna kısaca değinelim. Web hizmeti için express modülüne ihtiyacımız var. Bu nedenle bir require bildirimi ile başlıyoruz. epxress modülünü rahat kullanabilmek için app isimli bir değişken tanımlanıyor ve bir express nesnesi örnekleniyor. app.use ve app.set isimli fonksiyon çağırımları önemli. use metodunda static dosyalara hizmet verilmesi için gerekli bildirimleri yapmaktayız. NBA oyuncularının fotoğrafları public/images klasörü altında yer alıyor. Bunları img elementinin src niteliğinde bildirdiğimizde sunucu tarafının ilgili içerikleri bulabilmesi lazım. O nedenle yaptığımız bir bildirim olduğunu ifade edebiliriz(Detaylı bilgi için şu adrese bakabilirsiniz)İki tane set metot çağrımı söz konusu. İlkinde önyüz içeriklerine nereden bakılacağını, ikincisinde Pug'ın görüntüleme motoru olarak kullanılacağını belirtmekteyiz. Bundan sonra tek yaptığımız root adrese gelecek olan talebi yönetmek.

app.get('/' metod çağırımındaki callback fonksiyonuna dikkat edelim. Burada index sayfasının ele alınacağı belirtilmekte. Öncesinde View Engine olarak da pug belirtildiğinden index.pug HTML formatına çevrilerek istemciye gönderilecektir. Aslında response değişkeni üzerinden çağırılan render fonksiyonunda yaptığımız tek şey JSON tipinden bir içerik oluşturmak. Lakin buradaki teams ve players isimli özellikler index.pug içerisinde değer kazanmaktalar. Son satırda yer alan listen metodu tahmin edeceğiniz üzere belirtilen port'tan ilgili web hizmetini yayına almak için kullanılıyor.

Biraz da index.pug dosyası içerisine bakalım. Aslında anlaşılması oldukça kolay bir mekanizma söz konusu. Kapatma tag'leri yok, < ve > işaretleri yok. Bunların yerine hiyerarşiyi tanımlayabilmek için girintili bir söz dizimi kullanılıyor. Belki bizler için yeni olabilecek mixin ve for each kullanımları söz konusu. Sizden ricam server.js dosyasında yer alan teams ve players isimli dizilerin pug dosyası içerisinde nasıl ele alındığı incelemeniz. Buna ek olarak bir CSS niteliğini ilgili elemente nasıl bağlıyoruz bakmanızı öneririm. html, head, body, b, p, div gibi elementlerin nasıl kullanıldığını kavradığınızda sizin için de yazmak oldukça kolaylaşacaktır.

Pek tabii pug'ın etkin kullanımı için bu adresteki içeriklere göz atmamızda yarar var. mixin olarak oluşturduğumuz div içerisinde oyuncuların bir takım bilgilerini gösteriyoruz. Basit bir şablon oluşturduk aslında. Bu şablonun verisini ise son satırdaki for döngüsü ile doldurmaktayız. Benzer bir döngü takımların bilgilerini birer liste elementi olarak yazdırırken de kullanılmakta(each team in teams kısmı) Yani pug dosyasında HTML haricinde javascript gibi kod parçalarını da kullanabiliriz. Tek değişen yazım formatı dikkat ettiyseniz.

Benim için epey yeni ve farklı bir deneyimdi. Front-end tarafına uzak olmama rağmen pug'ın sunduğu imkanlardan olumlu şekilde etkilendiğimi ifade edebilirim. HTML'e göre okunurluğu çok daha kolay olan bir altyapı sağladığı aşikar. Aslında siz bu örneği çok daha iyi bir noktaya taşıyabilirsiniz. Örneğin oyuncu verilerini gerçek zamanlı bir REST servisinden veya veritabanından çekebilir, önyüz tarafı için Bootstrap gibi daha şık çatılardan yararlanabilirsiniz. Son olarak örneği çalıştırmak için terminalden npm start komutunu vermemiz ve herhangi bir tarayıcıdan localhost:65003 adresine gitmemizin yeterli olacağını belirteyim. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Pug, Pug, Pug

$
0
0

Merhaba Arkadaşlar,

Kısa bir süre önce çalışmakta olduğum şirkette epey eğlenceli bir mevzunun içerisinde kaldım. Daha önceden de bahsettiğim gibi kurumumuzun bize sunduğu güzel bir hizmet var...Pluralsight. Ne var ki benim gibi dikkatsiz kullanıcılar için enteresan şeyler olabiliyor. İzin verin hikayeyi size anlatayım; İlk hedefim oldukça hoşuma giden Node.js tarafında ilerlemekti.

Bu amaçla kendime çizdiğim kariyer yolunda(aslında Pluralsight Path demek daha doğru olur) bir takım Node.js eğitimlerini izlemeye başladım. Hatam bu eğitimlerin tarihlerine pek dikkat etmiyor oluşumdu. Öyle ki eğitimde sözü geçen bir npm modülü pekala eskimiş olabilirdi.

O hafta izlediğim eğitim serisi Node.js ile web programlama üzerineydi. Express ile işin içerisine girdikten sonra bir takım yardımcı çatılardan daha bahsedilmeye başlandı. Derken HTML'in sıkıcı olan o açısal ayraçlarından bizi kurtaran Jade isimli bir paketle karşılaştım. İnanılmaz hoşuma gitti. Nerden bilebilirdim aslında onun adının sonradan Pug olarak değiştirildiğinden ve hatta çalıştığım kurumun yeni nesil .Net projelerinde bu ürünü kullandığından:)

Bu arada ben Jade kod adını daha çok beğendiğimi ifade etmek isterim. Nitekim "Yeşim Taşı" olarak anlam kazanır. Oysa ki pug...Peah...Buldoğa benzeyen ufak bir köpek olduğu ifade ediliyor. Aslında Jade'in Pug olma hiyakesini şuradan okuyabilirsiniz.

O Cuma günü her zaman ki gibi sabah izlediğim derslerin bir uygulamasını gün içerisinde sıkıldığım anlarda yapmıştım(aman patron duymasın) Sonrasında sevgili Hilmi ağabeye durumu anlatmak istedim. Sık sık öğrendiklerimizi birbirimizle paylaşıyırız zaten. Heyecanlı bir şekilde anlattım da anlattım...Hatta yaptıklarımı gösterdim bile. Sonra çok güzel bir cümle sarfetti "yahu bunun bizim kullandığımız Pug'dan farkı ne? Neredeyse aynı..." Hemen google'ladık tabii...Ne gördük dersiniz? Pug meğerse Jade'in yeni adıymış. Önce birbirimize şaşkın şaşkın baktık ve bir süre sonra kahkayı patlattık :D

Tabii ki yaptığım kodlar çöpe gitmedi. Jade yerine hemen Pug paketini uygulamaya dahil edip aynı sonuçları elde etmeyi başardım. Peki neydi o becerebildiğim ve beni heyecanlandıran şey? Malum uzun yıllar back-end tarafında geliştirme yapmış birisi olarak front-end benim için epey geniş ve bir o kadar da korkutucu bir alan. Ancak görevim gereği o noktalara da dokunmam gerekiyor. Dilerseniz elde ettiğim sonucu göstererek başlayalım. Aşağıdaki ekran görüntüsüne bakalım.

Tarayıcı penceresinde açılmış basit bir HTML içeriği görüyorsunuz aslında değil mi? Hatta berbat ötesi bir tasarımı da var buna hiç şüphe yok :) Peki ya aslında bu sayfanın HTML tarafının aşağıdaki gibi yazıldığını söylesem.

html
    head
        meta(charset='utf-8')
        link(href='https://fonts.googleapis.com/css?family=Chewy',rel='stylesheet')
        title I love this game
    body(style='width:410px;font-family:Chewy,cursive')
        h2(id='myId',style='background-color:darkblue;color:white') İ s t a t i s t i k l e r
        p
            h3(style='background-color:red;color:white') En iyi takımlar
            p.
                NBA tarihinde tüm zamanların en iyi takımları   
            ul
                each team in teams
                    b
                        li=team
        p
            h3(style='background-color:green;color:white') En iyi oyuncular
            p.
                NBA tarihinin bugüne kadar gelmiş geçmiş en iyi oyuncuları
            mixin player-card(player)
                div(style='background-color:lightgray').player-card
                    a(href=player.url)
                        div.player-name=player.name
                    p
                        img.player-photo(src=player.photo)
                    p
                        i.heigth=player.height                        
                    p
                        i.bio=player.bio     
                    p                                        
            for player in players
                +player-card(player)

Şimdi işler biraz değişti değil mi? En azından bir farkındalık oluştu. Normalde yukarıdaki içeriğin görüntülenmesi için klasik ve bilinen yöntemlerle aşağıdakine benzer bir HTML dosyası hazırlanır.

<html><head><meta charset="utf-8" /><link href="https://fonts.googleapis.com/css?family=Chewy" rel="stylesheet" /><title>I love this game</title></head><body style="width:410px;font-family:Chewy,cursive"><h2 id="myId" style="background-color:darkblue;color:white">İ s t a t i s t i k l e r</h2><p><h3 style="background-color:red;color:white">En iyi takımlar</h3><p>NBA tarihinde tüm zamanların en iyi takımları </p><ul><b><li>bulls</li></b><b><li>celtics</li></b><b><li>lakers</li></b></ul></p><p><h3 style="background-color:green;color:white">En iyi oyuncular</h3><p>NBA tarihinin bugüne kadar gelmiş geçmiş en iyi oyuncuları</p><div class="player-card" style="background-color:lightgray"><a href="http://www.espn.com/nba/player/_/id/3975"><div class="player-name">sitivin köri</div></a><p><img class="player-photo" src="curry.gif" /></p><p><i class="heigth">190cm</i></p><p><i class="bio">benzersiz top tekniği, yüksek şut yüzdesi, sınır tanımaz üçlükleri...</i></p><p> </p></div><div class="player-card" style="background-color:lightgray"><a href="http://www.espn.com/nba/player/_/id/1966"><div class="player-name">löbron ceyms</div></a><p><img class="player-photo" src="lebron.gif" /></p><p><i class="heigth">206cm</i></p><p><i class="bio">çok güçlü, ani hızlanma, meydan okuma, istatistikleri alt üst etme...</i></p><p> </p></div><div class="player-card" style="background-color:lightgray"><a href="http://www.espn.com/nba/player/_/id/2384"><div class="player-name">divayt hauvırd</div></a><p><img class="player-photo" src="howard.gif" /></p><p><i class="heigth">206cm</i></p><p><i class="bio">o boyla o fudamental hareketleri yapabiliyor olmak, oyun zekası...</i></p><p> </p></div></p></body></html>

İşte Pug'ın ortaya koyduğu fark bu. "İki resim arasındaki 9 farkı bulun" bulmacasını hatırlar mısınız bilmem ama bu kötü espriyi yapmanın tam yeri ve zamanı sanırım :) Gelin kolları sıvayalım ve bu işi nasıl yaptığımızı kısaca özetleyelim. Uygulamanın bir kısmını şirket kaynaklarını kullaranak Windows tabanlı bir sistem üzerinde yapmış olsam da asıl tecrübe etmek istediğim yer elbette ki West-World topraklarıydı. Ubuntu'nun başına geçtim ve sonradan aşağıdaki ekran görüntüsünde yer alan yapıya dönüşecek klasör ağacını oluşturmaya başladım.

public/images klasörü içerisinde basketbol oyuncularına ait fotoğraflar yer alıyor(itinayla taranmışlardır) src/views altında pug uzantılı dosyamız bulunmakta. Bu yazının ilk kısmında paylaşmış olduğumuz içeriğe sahip. Aslında dikkatli bir şekilde okuduğunuzda girinti sistemine dayanan ve bu sayede HTML'in < ve > gibi açısal ayraçlarını geride bırakan bir yazım stili söz konusu olduğunu rahatlıkla anlayabilirsiniz. Klasörde yer alan server.js ve package.json içeriklerini biraz sonra dolduracağız. Nitekim yapmamız gereken bazı ön hazırlıklar var. İlk olarak terminal'den aşağıdaki komutları kullanarak gerekli yüklemeleri yapalım.

npm init
npm install express --save
npm install pug --save

npm init kullanımı ile package.json dosyasının oluşmasını sağlıyoruz. Bu komutu çalıştırdığımızda bazı sorularla da karşılaşırız. Uygulamanın adı, versiyonu, yazarı, test komutu vs...Diğer iki komut express ve pug paketlerinin yüklenmesi için kullanılıyorlar. express paketini web hizmetleri için kullanacağız. Static içeriklerin görüntülenmesi veya belirlenen adrese gelen taleplerin yönetilmesi dışında servis geliştirilmesinde de kullanılabilir. Pug ise az önce sıklıkla değindiğimiz yeni stil HTML şablonunun çalışma zamanında anlaşılabilmesi için gerekli. Bu işlemler sonrasında en azından West-World için ilgili package.json içeriğinin aşağıdaki gibi oluştuğunu ifade edebilirim.

{
  "name": "en.bi.ey",
  "version": "1.0.0",
  "description": "NBA istatistikleri",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "keywords": [
    "nba",
    "spor",
    "istatistik",
    "nodejs"
  ],
  "author": "burak selim senyurt",
  "license": "ISC",
  "dependencies": {
    "express": "^4.16.3",
    "pug": "^2.0.3"
  }
}

Package.json içerisinde müdahale ettiğimiz bir kısım var. scripts elementine dikkat ederseniz start özelliği için node server.js şeklinde bir tanımlama yaptık. Buna göre uygulamamızı "node server.js" yerine "npm start" terminal komutu ile de çalıştırabiliriz ki genel jargon bu yöndedir.

Gelelim server.js dosyasının içeriğine. Burada da öğrendiğim bir çok şey olduğunu ifade edebilirim. Python, Ruby, Go gibi pek çok programlama ortamındakine benzer bir işleyiş söz konusu tabii ama Node.js açısından düşündüğümde benim için yeni yeni şeyler...

var express = require('express');
var app = express();
var port = 65003;

app.use(express.static('public/images'))
app.set('views', './src/views');
app.set('view engine', 'pug');

app.get('/', function (req, res) {
    res.render('index'
        , {
            teams: ['bulls', 'celtics', 'lakers'],
            players: [
                {
                    name: 'sitivin köri',
                    bio: 'benzersiz top tekniği, yüksek şut yüzdesi, sınır tanımaz üçlükleri...',
                    height: "190cm",
                    url: 'http://www.espn.com/nba/player/_/id/3975',
                    photo: 'curry.gif'
                },
                {
                    name: 'löbron ceyms',
                    bio: 'çok güçlü, ani hızlanma, meydan okuma, istatistikleri alt üst etme...',
                    height: "206cm",
                    url: 'http://www.espn.com/nba/player/_/id/1966',
                    photo: 'lebron.gif'
                },
                {
                    name: 'divayt hauvırd',
                    bio: 'o boyla o fudamental hareketleri ​_yapabiliyor olmak, oyun zekası...',
                    height: "206cm",
                    url: 'http://www.espn.com/nba/player/_/id/2384',
                    photo: 'howard.gif'
                }
            ]
        }
    );
});

app.listen(port, function (err) {
    console.log('running server on port ' + port);
});

Neler olduğuna kısaca değinelim. Web hizmeti için express modülüne ihtiyacımız var. Bu nedenle bir require bildirimi ile başlıyoruz. epxress modülünü rahat kullanabilmek için app isimli bir değişken tanımlanıyor ve bir express nesnesi örnekleniyor. app.use ve app.set isimli fonksiyon çağırımları önemli. use metodunda static dosyalara hizmet verilmesi için gerekli bildirimleri yapmaktayız. NBA oyuncularının fotoğrafları public/images klasörü altında yer alıyor. Bunları img elementinin src niteliğinde bildirdiğimizde sunucu tarafının ilgili içerikleri bulabilmesi lazım. O nedenle yaptığımız bir bildirim olduğunu ifade edebiliriz(Detaylı bilgi için şu adrese bakabilirsiniz)İki tane set metot çağrımı söz konusu. İlkinde önyüz içeriklerine nereden bakılacağını, ikincisinde Pug'ın görüntüleme motoru olarak kullanılacağını belirtmekteyiz. Bundan sonra tek yaptığımız root adrese gelecek olan talebi yönetmek.

app.get('/' metod çağırımındaki callback fonksiyonuna dikkat edelim. Burada index sayfasının ele alınacağı belirtilmekte. Öncesinde View Engine olarak da pug belirtildiğinden index.pug HTML formatına çevrilerek istemciye gönderilecektir. Aslında response değişkeni üzerinden çağırılan render fonksiyonunda yaptığımız tek şey JSON tipinden bir içerik oluşturmak. Lakin buradaki teams ve players isimli özellikler index.pug içerisinde değer kazanmaktalar. Son satırda yer alan listen metodu tahmin edeceğiniz üzere belirtilen port'tan ilgili web hizmetini yayına almak için kullanılıyor.

Biraz da index.pug dosyası içerisine bakalım. Aslında anlaşılması oldukça kolay bir mekanizma söz konusu. Kapatma tag'leri yok, < ve > işaretleri yok. Bunların yerine hiyerarşiyi tanımlayabilmek için girintili bir söz dizimi kullanılıyor. Belki bizler için yeni olabilecek mixin ve for each kullanımları söz konusu. Sizden ricam server.js dosyasında yer alan teams ve players isimli dizilerin pug dosyası içerisinde nasıl ele alındığı incelemeniz. Buna ek olarak bir CSS niteliğini ilgili elemente nasıl bağlıyoruz bakmanızı öneririm. html, head, body, b, p, div gibi elementlerin nasıl kullanıldığını kavradığınızda sizin için de yazmak oldukça kolaylaşacaktır.

Pek tabii pug'ın etkin kullanımı için bu adresteki içeriklere göz atmamızda yarar var. mixin olarak oluşturduğumuz div içerisinde oyuncuların bir takım bilgilerini gösteriyoruz. Basit bir şablon oluşturduk aslında. Bu şablonun verisini ise son satırdaki for döngüsü ile doldurmaktayız. Benzer bir döngü takımların bilgilerini birer liste elementi olarak yazdırırken de kullanılmakta(each team in teams kısmı) Yani pug dosyasında HTML haricinde javascript gibi kod parçalarını da kullanabiliriz. Tek değişen yazım formatı dikkat ettiyseniz.

Benim için epey yeni ve farklı bir deneyimdi. Front-end tarafına uzak olmama rağmen pug'ın sunduğu imkanlardan olumlu şekilde etkilendiğimi ifade edebilirim. HTML'e göre okunurluğu çok daha kolay olan bir altyapı sağladığı aşikar. Aslında siz bu örneği çok daha iyi bir noktaya taşıyabilirsiniz. Örneğin oyuncu verilerini gerçek zamanlı bir REST servisinden veya veritabanından çekebilir, önyüz tarafı için Bootstrap gibi daha şık çatılardan yararlanabilirsiniz. Son olarak örneği çalıştırmak için terminalden npm start komutunu vermemiz ve herhangi bir tarayıcıdan localhost:65003 adresine gitmemizin yeterli olacağını belirteyim. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

http://www.buraksenyurt.com/post/Stream-ve-Pipe-MevzusuStream ve Pipe Mevzusu

$
0
0

Merhaba Arkadaşlar,

West-World bu hafta neredeyse savaş alanı gibiydi. Node.js tarafında öğrenmeye çalştığım yeni konu sebebiyle makineyi bir çok kez restart etmek zorunda kaldım. Üstelik düğmeden :|

Sebep çok büyük boyutlu bir dosya içeriğini basit bir web sunucusu üzerinden sunmaya çalışmaktı. Aslında kimse bu tip bir şey yapmaz. Hadi yapsa da koca dosyayı tek seferde istemciye göndermez. Kaldı ki istemci de bu web hizmetine herhangi bir tarayıcıdan talep göndermez.

Neyse ki sonunda doğru yolu buldum ve bu tip bir araştırma senaryosunda terminalden curl komutunu kullanarak ilerlemenin daha mantıklı olduğunu öğrendim. Tabii tüm bunlar için geçerli bir sebebim vardı. Akımların sıklıkla bahsedilen pipe fonksiyonunu denemek ve bunun performansa olan olumlu etkilerini görebilmek.

Node.js tarafında anlaşılması en zor konulardan birisinin akımlar(stream olarak telafüz edelim) olduğu söyleniyor. Özellikle event ve multi-process gibi kavramlarla yakın temas içerisinde. Okuduğum kaynaklar ve izlediğim Pluralsight eğitimlerine göre performans konusunda dikkat edilmesi gereken ve önemli özellikler barındıran bir mevzu. Özellikle büyük veri ile çalışan bir web sunucusu söz konusu ise stream nesnelerinin pipe mekanizması ile birlikte kullanılması tercih edilmeli. Gelin ne demek istediğimi benden daha iyi özetleyecek basit bir örnek ile konuya giriş yapalım.

Örnek Dosyanın Oluşturulması

Yapmak istediğim aynen kaynaklarda tariflendiği üzere dosya hizmeti veren bir web sunucusu oluşturmaktı. Performans farklılıklarını canlı görebilmek için en az bir dosyaya ve bunun farklı boyutlardaki hallerini ele alan senaryolara ihtiyacım vardı. Büyük boyutlu bir dosya bulmakla neden uğraşayım ki? Pekala içi anlamsız verilerle dolu bir dosyayı kendim oluşturabildim. İşe aşağıdaki kodlarla başladım.

var fs = require('fs');

console.log("Big file is creating...");
var bigEF = fs.createWriteStream('bigEF.data');
for (var i = 0; i < 3e6; i++) {
    bigEF.write('{"fname": "Devon","lname": "Karma"},{"fname": "Lorenz","lname": "Douglas"},{"fname": "Ora","lname": "Wade"},{"fname": "Kelly","lname": "Ragusa"},{"fname": "Teresa","lname": "Gergely"},{"fname": "Wendy","lname": "Kerkemeyer"},{"fname": "Georgia","lname": "Malo"},{"fname": "Tonja","lname": "Lichtenwalner"},{"fname": "Dorota","lname": "Breiter"},{"fname": "Priscilla","lname": "Bartovics"}');
}
bigEF.end();

fs(Temel IO işlemleri için kullanıyoruz diyebilirim) modülünün kullanıldığı ve içerisinde JSON cemiyetinden rastgele insanların olduğu büyük boyutlu bir dosya üretiliyor. createWriteStream ile bigEF.data isimli örnek dosya için yazılabilir bir Stream nesnesi örnekliyoruz. Buraya uygulayacağımız write çağrısı tahmin edeceğiniz üzere dosya içerisine ilgili metinsel içeriklerin yazılmasını sağlamakta. Tüm işlerin onaycısı sondaki end çağrısı. Kodu çalıştırdığımda West-World üzerinde 1.2 Gb'lık alan işgal edecek tamamen atmasyon verilerden oluşan bir dosya üretilmiş oldu. İlk senaryo için yeterli büyüklükte.

Problem Çıkartalım

İlk hedef bu dosyayı bir web sunucusu üzerinden sunmak. Yani istemcilerin göndereceği bir HTTP talebine göre kendilerine bu dosya içeriğini göndereceğimiz anlamısız bir senaryomuz var. Aslında kodun yapacağı iş oldukça basit. Sunucuya gelen talep sonrası ilgili dosyayı okumak ve istemciye paslamak. Aşağıdaki kod parçası bu işi görebilir.

var fs = require('fs');
var http = require('http');

var server = http.createServer().on('request', function (req, res) {
    fs.readFile('bigEF.data', function (err, data) {
        if (err)
            throw err;
        res.end(data);
    });
});
server.listen(65002);
console.log('Server is online');

Daha önceden aşina olduğumuz üzere http modülüne ihtiyacımız var. Sunucu nesnesi örneklendikten sonra request olayını dinleyecek şekilde bir metod zinciri bulunuyor. Callback fonksiyonu istemciden gelen talep sonrası çalışacaktır. İçerisinde öncelikle dosyanın okunmasını sağlıyoruz. readFile'ın Callback fonkisyonunda ise okunan dosya içeriğini response değişkenine yazıyoruz. Daha doğrusu response değişkeninin açtığı stream'e aktarıyoruz. Sunucu West-World'ün 65002 nolu Kuzey Batı kapısından hizmet verecek şekilde tasarlanmış durumda.

Uygulamayı çalıştırıp curl ile(özellikle curl ile denedim çünkü tarayıcı ile gerçekleştirdiğim acı deneyimler sonrası makineyi bir kaç kez düğmesinden kapatıp açmak zorunda kaldım) 65002 nolu porta talep gönderdiğimde aşağıdaki sonuçlarla karşılaştım.

Aynen kaynaklarda bahsedildiği gibi olmuştu. Sunucu açık ve istemci curl komutu ile adrese bir talep gönderiyor. Kısa bir süre için sorun yok. Derken bir anda bellek tüketimi artıp çıktığı noktada seyretmeye devam ediyor. Hatta West-World'ün klima sistemi de aynı anda coşmuştu diyebilirim. Uygulama çalışmasını tamamlandığında ise her şey normale döndü. Bellek tüketimi kısa süre içinde baştaki seviyelere indi.

Burada gözle görülür bir performans sıkıntısı olduğu ortada. Sadece tek bir dosya için gönderilmiş bir talep var ancak n sayıda talebin gelmesi ve küçük boyutlarda olsalar bile onlarca, yüzlerce dosyanın sunulacağı bir sistem için çok daha büyük sorunlar oluşması pekala mümkün.

Pipe Kullanımı

İşte bu noktada pipe fonksiyonu ile karşılaşılıyor. Çok temel olarak kaynak ve hedef arasında tek veya çift yönlü bir boru hattının oluşturulmasına yarayan ama enteresan avantajlar sunan bir fonksiyondan bahsediyoruz aslında. Bu fonksiyon, açılan kanal üzerinden verilerin akışını sağlıyor ancak bi farkla; kendisi stream nesne örneklerine uygulanabiliyor ve verinin tamamının belleğe alınması yerine belli boyutlarda parçalanarak kullanılmasına olanak sağlıyor. Örneğin 1.2 Gb'lık devasa bir dosyanın tamamen belleğe alınıp işlenmesi yerine küçük parçalar haline(chunk) bölünerek ele alınmasını sağlıyor (Bunu nasıl yapabileceğini bir düşünün derim. Yani siz böyle bir fonksiyon yazmaya çalışsanız nasıl yazardınız?) pipe için şöyle bir ifade doğru olacaktır.

[kaynak].pipe([hedef])

West-World'de ikinci deneyi gerçekleştirmenin zamanı gelmişti. Öğrendiklerimi uygulayarak aşağıdaki kod parçasını hazırladım.

var fs = require('fs');
var http = require('http');

var server = http.createServer().on('request', function (req, res) {
    var source = fs.createReadStream('bigEF.data');
    source.pipe(res);
});
server.listen(65002);
console.log('Server is online');

Bir önceki örnekten farklı olarak doğrudan readFile fonksiyonunu kullanmak yerine createReadStream için çağrı yapılmakta. Bunun sonucu olarak veri okunabilir bir akım örneklenecek(ReadableStream). source olarak isimlendirilen nesne örneği üzerinden pipe metodunun nasıl kullanıldığına dikkat edelim. Parametre olarak response değişkenini alıyor. response, bu senaryo gereği üzerine veri yazılabilir bir akım(WritableStream) Buna göre dosyadan okudukça, çıktı olarak ağ üzerindeki kanala veri yazılıyor diye düşünebiliriz.

Testi tekrar yaptığımda West-World'te ortam gayet sakin görünmekteydi. Önce sunucu uygulamasını çalıştırdım, ardından curl ile talebi gönderdim.

Veriler yine okunuyordu ancak sunucunun bellek tüketiminde gözle görülür önemli bir artış olmamıştı. Hatta neredeyse hiç olmamıştı.

Pipe Yerine Event Kullanımı

Bir önceki örnekte yer alan pipe fonksiyonu yerine olay bazlı kurgulama ile de aynı senaryo çalıştırılabilir. Hatta bu durumda olay fonksiyonlarındaki parametrelerin gücünden de yararlanılabilir. Kodları aşağıdaki gibi düzenleyerek testlere devam ettim. Ancak öncesinde üzerinde çalışacağım dosya boyutunu epeyce küçülttüm. Nitekim buffer kullanacağım için bu veri kümelerini(chunk) izleyebilmek istiyordum (Burada böyle yazıyorum çünkü önce bir kaç deneme yaptım. Baktım terminalden parçaları göremiyorum, üzerinde çalışacağım dosya boyutunu küçülttüm)

var fs = require('fs');
var http = require('http');

// pipe yerine stream eventlerini kullanmak
// daha küçük boyutlu bir dosya seçelim ki takibimiz kolay olsun

var server = http.createServer().on('request', function (req, res) {
    var source = fs.createReadStream('bigEF.data');
    // aşağıdaki data ve end olayları da bir nevi pipe'ın karşılığıdır.
    source.on('data', function (chunk) {
        res.write(chunk);
        var date = new Date().toISOString();
        console.log('\n' + date + '\n');
        console.log('\t' + chunk);
    });
    source.on('end', function () {
        res.end();
        console.log('end');
    });
});

server.listen(65002);
console.log('Server is online');

Dikkat edileceği üzere source nesnesi yine okunabilir stream örneği olarak başrolde. Bu sefer ilgili nesne için data ve en isimli olayları bildiriyoruz. Her iki olay için de callback fonksiyonları içinde gerekli işlerin yapıldığını belirtebiliriz. data olayı gerçekleştiğinde istemci talebine cevaben verinin küçük bir parçasını gönderiyoruz. Dosyaya ait bütün parçaların gönderimi tamamlandığında end olayı tetiklenmekte. Burada da istemciye göndereceğimiz verilerin tamamlandığını belirtiyoruz.

Çalışma zamanı çıktılarına baktığımda aşağıdaki gibi tampon bölgeye alınan veri kümelerinin değerlendirildiğini gördüm. Hatta üstteki bilginin kesildiği yerden alttakinin devam ettiğini de fark etmiş olmalısınız.

Dikkat edileceği üzere bellek kullanımında yine önemli bir sıkıntı görülmüyor.

Limitleri Zorlayalım

Peki ya dosya boyutu baya baya büyük olsaydı. Kaynaklarda bahsedilen 2Gb sınırını merak ediyordum aslında. Özellikle bu değer için readFile'ın cevap vermediği ifade ediliyordu. Bu nedenle saçma veriler içeren dosya boyutunu 2Gb'ın üstüne çıkarttım.

West-World bu kez 2.3Gb'lık bir saha işgali ile karşı karşıyaydı. pipe mekanizmasını kullandığım kodu bir kenara bıraktım ve ilk olarak standart okuma yöntemini kullanmaya karar verdim. Sonuçları almam hiç uzun sürmedi. 

Görüldüğü üzere dosya boyutu olası Buffer boyutunun üzerindeydi. Bu sebepten işlemler zaten yapılamadı. Ancak pipe fonksiyonelliğinin kullanıldığı kod parçası söz konusu dosyayı sorunsuz bir şekilde işlemeyi başarmıştı.

Diğer Kullanışlı Bilgiler

Bitirmeden önce bir kaç teknik bilgi daha vermeye çalışayım(Anladığım kadarıya tabii) Node.js ile birlikte gelen bir çok standart stream enstrümanı söz konusu. Bunları iki ana kategoriye ayırmak mümkün. Okuma amaçlı kullanılanlar(readable streams) ve yazma amaçlı(writable streams) kullanılanlar. Sonuç itibariyle bir kaynaktan veri okuma veya bir hedefe veri yazma işleri bu kapsamlara giriyor. Bazı node.js türleri ise her iki rolü birden üstlenebiliyor. TCP soketleri, sıkıştırma kütüphanesi(zlib) ve şifreleme nesneleri her iki rolü üstlenen aktörlerden.

Bunlara ek olarak tek yönlü olan stream nesneleri de bulunuyor. Örneğin bir istemcinin sunucuya gönderdiği HTTP talebi sunucu açısından readable stream olarak değerlendirilirken, istemci açısından writable stream şeklinde anlam kazanıyor. Tam tersi durumda söz konusu elbette. Okunabilir veya yazılabilir akımlar yalnız değiller. Bunlara ilaveten çift yönlü(duplex) ve dönüşebilir(transform) akım türleri de var.

Özellikle duplex formatta olanlar çok ilginç. Öyle ki bunları ele aldığımız senaryolarda arka arkaya birden fazla pipe çağrısının yer aldığı metod zincirleri oluşturulması mümkün. Nitekim pipe çağrısının sonucu yine okunabilir ve dolayısıyla başka bir kaynağa girdi olarak aktarılabilir bir stream olabiliyor. Mesela Veli okunabilir, Ayşe ve Hakan hem okunabilir hem yazılabilir, son olarak da Levent sadece yazılabilir ise şu şekilde bir ietişim hattı oluşturmak mümkün(müş).

veli.pipe(ayşe).pipe(hakan).pipe(levent) 

İnanın bu noktada benim de kafam epey karışmış durumda. Şimdilik yapacağım şey biraz dinlenmek ve konunun diğer detaylarına bir şekilde inmeye çalışmak. Lütfen siz de araştırın ve Node.js dünyasındaki stream konusunu en ince detayına kadar öğrenmeye bakın. Söz gelimi dosya harici büyük veri kümeleri üzerinde işlem yapmak istediğiniz soket haberleşmesi odaklı senaryolar da bu vakaları ele almayı deneyebilirsiniz. Benden şimdilik bu kadar. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Kaynaklar

Basic of Node.js Streams
Node.js Offical Documentation
The Definitive Guide of Object Streams in Node.js
Free Code Camp
W3School
Events and Streams in Node.js


Stream ve Pipe Mevzusu

$
0
0

Merhaba Arkadaşlar,

West-World bu hafta neredeyse savaş alanı gibiydi. Node.js tarafında öğrenmeye çalştığım yeni konu sebebiyle makineyi bir çok kez restart etmek zorunda kaldım. Üstelik düğmeden :|

Sebep çok büyük boyutlu bir dosya içeriğini basit bir web sunucusu üzerinden sunmaya çalışmaktı. Aslında kimse bu tip bir şey yapmaz. Hadi yapsa da koca dosyayı tek seferde istemciye göndermez. Kaldı ki istemci de bu web hizmetine herhangi bir tarayıcıdan talep göndermez.

Neyse ki sonunda doğru yolu buldum ve bu tip bir araştırma senaryosunda terminalden curl komutunu kullanarak ilerlemenin daha mantıklı olduğunu öğrendim. Tabii tüm bunlar için geçerli bir sebebim vardı. Akımların sıklıkla bahsedilen pipe fonksiyonunu denemek ve bunun performansa olan olumlu etkilerini görebilmek.

Node.js tarafında anlaşılması en zor konulardan birisinin akımlar(stream olarak telafüz edelim) olduğu söyleniyor. Özellikle event ve multi-process gibi kavramlarla yakın temas içerisinde. Okuduğum kaynaklar ve izlediğim Pluralsight eğitimlerine göre performans konusunda dikkat edilmesi gereken ve önemli özellikler barındıran bir mevzu. Özellikle büyük veri ile çalışan bir web sunucusu söz konusu ise stream nesnelerinin pipe mekanizması ile birlikte kullanılması tercih edilmeli. Gelin ne demek istediğimi benden daha iyi özetleyecek basit bir örnek ile konuya giriş yapalım.

Örnek Dosyanın Oluşturulması

Yapmak istediğim aynen kaynaklarda tariflendiği üzere dosya hizmeti veren bir web sunucusu oluşturmaktı. Performans farklılıklarını canlı görebilmek için en az bir dosyaya ve bunun farklı boyutlardaki hallerini ele alan senaryolara ihtiyacım vardı. Büyük boyutlu bir dosya bulmakla neden uğraşayım ki? Pekala içi anlamsız verilerle dolu bir dosyayı kendim oluşturabildim. İşe aşağıdaki kodlarla başladım.

var fs = require('fs');

console.log("Big file is creating...");
var bigEF = fs.createWriteStream('bigEF.data');
for (var i = 0; i < 3e6; i++) {
    bigEF.write('{"fname": "Devon","lname": "Karma"},{"fname": "Lorenz","lname": "Douglas"},{"fname": "Ora","lname": "Wade"},{"fname": "Kelly","lname": "Ragusa"},{"fname": "Teresa","lname": "Gergely"},{"fname": "Wendy","lname": "Kerkemeyer"},{"fname": "Georgia","lname": "Malo"},{"fname": "Tonja","lname": "Lichtenwalner"},{"fname": "Dorota","lname": "Breiter"},{"fname": "Priscilla","lname": "Bartovics"}');
}
bigEF.end();

fs(Temel IO işlemleri için kullanıyoruz diyebilirim) modülünün kullanıldığı ve içerisinde JSON cemiyetinden rastgele insanların olduğu büyük boyutlu bir dosya üretiliyor. createWriteStream ile bigEF.data isimli örnek dosya için yazılabilir bir Stream nesnesi örnekliyoruz. Buraya uygulayacağımız write çağrısı tahmin edeceğiniz üzere dosya içerisine ilgili metinsel içeriklerin yazılmasını sağlamakta. Tüm işlerin onaycısı sondaki end çağrısı. Kodu çalıştırdığımda West-World üzerinde 1.2 Gb'lık alan işgal edecek tamamen atmasyon verilerden oluşan bir dosya üretilmiş oldu. İlk senaryo için yeterli büyüklükte.

Problem Çıkartalım

İlk hedef bu dosyayı bir web sunucusu üzerinden sunmak. Yani istemcilerin göndereceği bir HTTP talebine göre kendilerine bu dosya içeriğini göndereceğimiz anlamısız bir senaryomuz var. Aslında kodun yapacağı iş oldukça basit. Sunucuya gelen talep sonrası ilgili dosyayı okumak ve istemciye paslamak. Aşağıdaki kod parçası bu işi görebilir.

var fs = require('fs');
var http = require('http');

var server = http.createServer().on('request', function (req, res) {
    fs.readFile('bigEF.data', function (err, data) {
        if (err)
            throw err;
        res.end(data);
    });
});
server.listen(65002);
console.log('Server is online');

Daha önceden aşina olduğumuz üzere http modülüne ihtiyacımız var. Sunucu nesnesi örneklendikten sonra request olayını dinleyecek şekilde bir metod zinciri bulunuyor. Callback fonksiyonu istemciden gelen talep sonrası çalışacaktır. İçerisinde öncelikle dosyanın okunmasını sağlıyoruz. readFile'ın Callback fonkisyonunda ise okunan dosya içeriğini response değişkenine yazıyoruz. Daha doğrusu response değişkeninin açtığı stream'e aktarıyoruz. Sunucu West-World'ün 65002 nolu Kuzey Batı kapısından hizmet verecek şekilde tasarlanmış durumda.

Uygulamayı çalıştırıp curl ile(özellikle curl ile denedim çünkü tarayıcı ile gerçekleştirdiğim acı deneyimler sonrası makineyi bir kaç kez düğmesinden kapatıp açmak zorunda kaldım) 65002 nolu porta talep gönderdiğimde aşağıdaki sonuçlarla karşılaştım.

Aynen kaynaklarda bahsedildiği gibi olmuştu. Sunucu açık ve istemci curl komutu ile adrese bir talep gönderiyor. Kısa bir süre için sorun yok. Derken bir anda bellek tüketimi artıp çıktığı noktada seyretmeye devam ediyor. Hatta West-World'ün klima sistemi de aynı anda coşmuştu diyebilirim. Uygulama çalışmasını tamamlandığında ise her şey normale döndü. Bellek tüketimi kısa süre içinde baştaki seviyelere indi.

Burada gözle görülür bir performans sıkıntısı olduğu ortada. Sadece tek bir dosya için gönderilmiş bir talep var ancak n sayıda talebin gelmesi ve küçük boyutlarda olsalar bile onlarca, yüzlerce dosyanın sunulacağı bir sistem için çok daha büyük sorunlar oluşması pekala mümkün.

Pipe Kullanımı

İşte bu noktada pipe fonksiyonu ile karşılaşılıyor. Çok temel olarak kaynak ve hedef arasında tek veya çift yönlü bir boru hattının oluşturulmasına yarayan ama enteresan avantajlar sunan bir fonksiyondan bahsediyoruz aslında. Bu fonksiyon, açılan kanal üzerinden verilerin akışını sağlıyor ancak bi farkla; kendisi stream nesne örneklerine uygulanabiliyor ve verinin tamamının belleğe alınması yerine belli boyutlarda parçalanarak kullanılmasına olanak sağlıyor. Örneğin 1.2 Gb'lık devasa bir dosyanın tamamen belleğe alınıp işlenmesi yerine küçük parçalar haline(chunk) bölünerek ele alınmasını sağlıyor (Bunu nasıl yapabileceğini bir düşünün derim. Yani siz böyle bir fonksiyon yazmaya çalışsanız nasıl yazardınız?) pipe için şöyle bir ifade doğru olacaktır.

[kaynak].pipe([hedef])

West-World'de ikinci deneyi gerçekleştirmenin zamanı gelmişti. Öğrendiklerimi uygulayarak aşağıdaki kod parçasını hazırladım.

var fs = require('fs');
var http = require('http');

var server = http.createServer().on('request', function (req, res) {
    var source = fs.createReadStream('bigEF.data');
    source.pipe(res);
});
server.listen(65002);
console.log('Server is online');

Bir önceki örnekten farklı olarak doğrudan readFile fonksiyonunu kullanmak yerine createReadStream için çağrı yapılmakta. Bunun sonucu olarak veri okunabilir bir akım örneklenecek(ReadableStream). source olarak isimlendirilen nesne örneği üzerinden pipe metodunun nasıl kullanıldığına dikkat edelim. Parametre olarak response değişkenini alıyor. response, bu senaryo gereği üzerine veri yazılabilir bir akım(WritableStream) Buna göre dosyadan okudukça, çıktı olarak ağ üzerindeki kanala veri yazılıyor diye düşünebiliriz.

Testi tekrar yaptığımda West-World'te ortam gayet sakin görünmekteydi. Önce sunucu uygulamasını çalıştırdım, ardından curl ile talebi gönderdim.

Veriler yine okunuyordu ancak sunucunun bellek tüketiminde gözle görülür önemli bir artış olmamıştı. Hatta neredeyse hiç olmamıştı.

Pipe Yerine Event Kullanımı

Bir önceki örnekte yer alan pipe fonksiyonu yerine olay bazlı kurgulama ile de aynı senaryo çalıştırılabilir. Hatta bu durumda olay fonksiyonlarındaki parametrelerin gücünden de yararlanılabilir. Kodları aşağıdaki gibi düzenleyerek testlere devam ettim. Ancak öncesinde üzerinde çalışacağım dosya boyutunu epeyce küçülttüm. Nitekim buffer kullanacağım için bu veri kümelerini(chunk) izleyebilmek istiyordum (Burada böyle yazıyorum çünkü önce bir kaç deneme yaptım. Baktım terminalden parçaları göremiyorum, üzerinde çalışacağım dosya boyutunu küçülttüm)

var fs = require('fs');
var http = require('http');

// pipe yerine stream eventlerini kullanmak
// daha küçük boyutlu bir dosya seçelim ki takibimiz kolay olsun

var server = http.createServer().on('request', function (req, res) {
    var source = fs.createReadStream('bigEF.data');
    // aşağıdaki data ve end olayları da bir nevi pipe'ın karşılığıdır.
    source.on('data', function (chunk) {
        res.write(chunk);
        var date = new Date().toISOString();
        console.log('\n' + date + '\n');
        console.log('\t' + chunk);
    });
    source.on('end', function () {
        res.end();
        console.log('end');
    });
});

server.listen(65002);
console.log('Server is online');

Dikkat edileceği üzere source nesnesi yine okunabilir stream örneği olarak başrolde. Bu sefer ilgili nesne için data ve en isimli olayları bildiriyoruz. Her iki olay için de callback fonksiyonları içinde gerekli işlerin yapıldığını belirtebiliriz. data olayı gerçekleştiğinde istemci talebine cevaben verinin küçük bir parçasını gönderiyoruz. Dosyaya ait bütün parçaların gönderimi tamamlandığında end olayı tetiklenmekte. Burada da istemciye göndereceğimiz verilerin tamamlandığını belirtiyoruz.

Çalışma zamanı çıktılarına baktığımda aşağıdaki gibi tampon bölgeye alınan veri kümelerinin değerlendirildiğini gördüm. Hatta üstteki bilginin kesildiği yerden alttakinin devam ettiğini de fark etmiş olmalısınız.

Dikkat edileceği üzere bellek kullanımında yine önemli bir sıkıntı görülmüyor.

Limitleri Zorlayalım

Peki ya dosya boyutu baya baya büyük olsaydı. Kaynaklarda bahsedilen 2Gb sınırını merak ediyordum aslında. Özellikle bu değer için readFile'ın cevap vermediği ifade ediliyordu. Bu nedenle saçma veriler içeren dosya boyutunu 2Gb'ın üstüne çıkarttım.

West-World bu kez 2.3Gb'lık bir saha işgali ile karşı karşıyaydı. pipe mekanizmasını kullandığım kodu bir kenara bıraktım ve ilk olarak standart okuma yöntemini kullanmaya karar verdim. Sonuçları almam hiç uzun sürmedi. 

Görüldüğü üzere dosya boyutu olası Buffer boyutunun üzerindeydi. Bu sebepten işlemler zaten yapılamadı. Ancak pipe fonksiyonelliğinin kullanıldığı kod parçası söz konusu dosyayı sorunsuz bir şekilde işlemeyi başarmıştı.

Diğer Kullanışlı Bilgiler

Bitirmeden önce bir kaç teknik bilgi daha vermeye çalışayım(Anladığım kadarıya tabii) Node.js ile birlikte gelen bir çok standart stream enstrümanı söz konusu. Bunları iki ana kategoriye ayırmak mümkün. Okuma amaçlı kullanılanlar(readable streams) ve yazma amaçlı(writable streams) kullanılanlar. Sonuç itibariyle bir kaynaktan veri okuma veya bir hedefe veri yazma işleri bu kapsamlara giriyor. Bazı node.js türleri ise her iki rolü birden üstlenebiliyor. TCP soketleri, sıkıştırma kütüphanesi(zlib) ve şifreleme nesneleri her iki rolü üstlenen aktörlerden.

Bunlara ek olarak tek yönlü olan stream nesneleri de bulunuyor. Örneğin bir istemcinin sunucuya gönderdiği HTTP talebi sunucu açısından readable stream olarak değerlendirilirken, istemci açısından writable stream şeklinde anlam kazanıyor. Tam tersi durumda söz konusu elbette. Okunabilir veya yazılabilir akımlar yalnız değiller. Bunlara ilaveten çift yönlü(duplex) ve dönüşebilir(transform) akım türleri de var.

Özellikle duplex formatta olanlar çok ilginç. Öyle ki bunları ele aldığımız senaryolarda arka arkaya birden fazla pipe çağrısının yer aldığı metod zincirleri oluşturulması mümkün. Nitekim pipe çağrısının sonucu yine okunabilir ve dolayısıyla başka bir kaynağa girdi olarak aktarılabilir bir stream olabiliyor. Mesela Veli okunabilir, Ayşe ve Hakan hem okunabilir hem yazılabilir, son olarak da Levent sadece yazılabilir ise şu şekilde bir ietişim hattı oluşturmak mümkün(müş).

veli.pipe(ayşe).pipe(hakan).pipe(levent) 

İnanın bu noktada benim de kafam epey karışmış durumda. Şimdilik yapacağım şey biraz dinlenmek ve konunun diğer detaylarına bir şekilde inmeye çalışmak. Lütfen siz de araştırın ve Node.js dünyasındaki stream konusunu en ince detayına kadar öğrenmeye bakın. Söz gelimi dosya harici büyük veri kümeleri üzerinde işlem yapmak istediğiniz soket haberleşmesi odaklı senaryolar da bu vakaları ele almayı deneyebilirsiniz. Benden şimdilik bu kadar. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Kaynaklar

Basic of Node.js Streams
Node.js Offical Documentation
The Definitive Guide of Object Streams in Node.js
Free Code Camp
W3School
Events and Streams in Node.js

http://www.buraksenyurt.com/post/Google-Cloud-PubSub-Service-MacerasıGoogle Cloud Pub/Sub Service Macerası

$
0
0

Merhaba Arkadaşlar,

Yeni yuvam ile evimin arası 40 km. Uzaklık nedeniyle mesailerimiz erken başlıyor. Sabah 05:50de çalan alarmla güne başlıyorum. Üst baş, kişisel bakım, seyahat boyu bana eşlik edecek filtre kahveyi hazırlama vs derken 06:35 sıralarında sevgili servis şoförümüz İhsan ağabey ile buluşup yola devam ediyorum. Yaklaşık 40-45 dakikalık bir seyahatten sonra iş yerine ulaşıyorum. Yol boyunca "o saatte kim ayakta olur?" sorusunu cevaplarcasına her sabah onlarca kez ezen insanla karşılaşıyorum. Mevsime göre evlerin sarı beyaz oda ışıkları, seyir halindeki arabalar, çalışanları işe götüren servisler, otobüsler, minibüsler, duraklarda bekleyen öğrenciler... O vakitlerde empati yapmak farklı bir deneyim.

Ama bazı sabahlarda Feedly sayfama düşen yazıları okuyorum. Şirkete ulaştığımda mesainin başladığı 07:45e kadar da neredeyse yarım saatlik serbest zamanım oluyor. Genelde beğendiğim ve Feedly listemde favorilere eklediğim bir yazının devamını o zaman diliminde getiriyorum. Bazı yazıları da tekrar tekrar okuyorum. İşte tam da böyle bir sabahtı Google'ın iş ortaklarından olan Incentro firmasının(Internet sayfaları çok hoş) direktörü Kees van Bemmel'ın kaleminde çıkan yazıyı okuduğumda. Makale, Cloud Platform temelli olarak geliştirilen bir çözüm hakkındaydı. 

Şirkete varır varmaz ilk işim yazının üstünden bir kere daha geçmek olmuştu. Sadece başlığında Publisher/Subscriber, Cloud Function, Machine Learning, Serverless kelimeleri geçiyordu. Bunlar ilgi çekici olması için fazlasıyla yeterliydi. Google'un bloğuna konu olan vakada, video, resim ve ses gibi içerikleri yönetmeye çalışan bir firmanın Google Cloud Platform ile uyguladığı Serverless çözüm anlatılıyordu. Sorun bu içeriklerin takı bazında kayıt altına alınması ve aramalarda doğru konumlandırılamamasıyla alakalıydı.

Şöyle düşünebiliriz; müşteri olarak elimizde tonlarca video, resim ve ses içeriği bulunuyor. Bunları tag sistemi ile teker teker kategorize etmeye çalışırken ne kadar doğru sonuçlar üretebiliyoruz? Kaçını yanlış takılarla, hatta takıları olmadan sisteme dahil ediyoruz. İşte söz konusu çözümde hem takı belirleme hem de arama işleri için gerekli içeriklerin Elasticsearch üzerinde indekslenmesinde Google Cloud Platform'un çeşitli hizmetlerinden nasıl yararlanıldığı anlatılıyordu. Biliyorum "ne bu? ne bu?" diyorsunuz. Buradaki yazıyı okumanızı şiddetle tavsiye ederim. 

Şunu hayal edin...Diyelim ki aksiyon videoları/fotoğrafları çekenlerin olduğu bir internet arşivinin sahibisiniz. Müşterileriniz videolarını yükledikçe içeriklerindeki bir takım imgelere göre otomatik olarak takılarla işaretlenmelerini(mesela rüzgar sörfünü otomatik olarak algılayıp windsurfing takısı ile işaretlenmesi) ve hatta konuşmalarındaki metinsel ifadelere göre de("alaçatı'nın rüzgarları sörf yapmak için idealmiş ahbap...") hangi sporlarla uğraştıklarını ve belki de konuşanın hangi ünlü olabileceğini kayıt altına almak istiyorsunuz. Hatta üyelerinizin çekip yükledikleri fotoğraflarda sizin tanımladığınız imgelerin otomatik olarak algılanıp takı bazında değerlendirilmesini istiyorsunuz vs

Ben yazıyı okuduktan sonra masamın başına geçtiğimde yaptığım ilk iş, mimari resmin bir benzerini çizmeye çalışmak oldu. Her zaman okuduğumu bakarak da olsa(az bakarak yapılanı kabul, hiç bakmadan tek seferde yapılanı makbuldur)çizmeye çalışırdım. Kendi notlarımı ekleyerek öğrenmeyi pekiştirmeye gayret ederdim. Sonuçta kurşun kalemle de olsa aşağıdaki gibi bir şeylere ulaştım.

Kabaca olayı anlamış gibiydim. Eğer işlenmesini istediğim bir değer varsa(asset diyelim), bunu Google Cloud Storage'a atmam yeterliydi. Sonrasında Google'ın Handler fonksiyonları devreye girip bu değeri çeşidine göre işleyişin yürütüldüğü hattaki uygun enstrümana(yazının konusu olan Pub/Sub hizmetine) yönlendirecekti. Bu yönlendirme sırasında resim, video ve ses ile ilgili işleme fonksiyonları(Google Cloud Functions) devreye girecekti. Sonrası Elasticsearch'e atılan bilgilerden ibaretti. Benim ilgimi çeken Machine Learning, Speech to Text gibi akıllı hizmetlerin sunulduğu Google Cloud Functions alanıydı. Lakin daha önceden bir şekilde incelemiş olmama rağmen aradaki bir katmanı öğrenmeden ilerleyemeyeceğimi anlamıştım. Google Pub/Sub hizmeti. 

Esasında bir resmin içerisindeki nesnelere göre Tensorflow'un bile araya girebildiği anlamlaştırma ya da bir video içerisindeki sesin Speech API ile metne dönüştürülüp konunun ne olduğunun çıkartılması ve tüm bunların EleasticSearch üzerine yazılması gibi fonksiyonellikler bir şekilde tetikleniyordu. Genellikle bir HTTP talebi bunun için yeterli ancak Publisher/Subscriber modeli de bu tetikleyicilerden birisiydi. İşte benim öncelikli olarak Google Cloud Platform üzerindeki Publisher/Subscriber hizmetini anlamam gerekiyordu.

Temel olarak bu hizmeti şu şekilde ifade edebiliriz; Uygulamalar arasında güvenilir şekilde hızlı ve asenkron olarak mesaj değiş tokuşuna izin veren bir Google hizmeti olarak tanımlasak yanlış olmaz. Sistem klasik Publisher/Subscriber modelini baz alır. Publisher rolünü üstlenen taraf belli bir konu(topic) için mesaj yayımlar. Subscriber rolünü üstlenen taraf eğer ilgili konuya(topic) abone olmuşsa yayıncının mesajını istediği zaman çekebilir. Google'ın bu hizmetindeki mesajlar alıcıya ulaşana kadar belli bir süre boyunca(ben araştırırken 7 gündü) korunmaktadır.

Bu teorik bilgiyi pekiştirmek ve özellikle bunu West-World gibi Ubuntu tabanlı bir dünyada, .Net Core,Go,Ruby, Python, Node.js gibi dilleri kullanarak deneyimlemek benim için önemliydi. Ana amacım Google Cloud Platform'da Pub/Sub servisini belli bir proje için etkinleştirmek ve sonrasında .Net Core tarafında belli bir topic için mesaj yayınlayıp bu mesajı okuyabilmek. Zaten Google Cloud Platform açısından amaç da bu. Uygulamalar arasında güvenilir bir hat üzerinden mesaj akışına izin veren yüksek performanslı tamamen asenkron çalışan bir boru hattı(pipeline)Öyleyse gelin adım adım ilerleyerek konuyu anlamaya çalışalım.

gCloud ile İlk Deneyim

Google bu konu ile ilişkili oldukça zengin ve basit öğreti dökümanları sunmakta(Diğer bulut bilişim sistemlerinde olduğu gibi) Bende ilgili dokümanları takip ettim ve ilk olarak gCloud aracını kullanarak var olan bir Google projemde Publisher/Subscriber modelini deneyimlemeye çalıştım. my-starwars-game-project isimli projemi seçtikten sonra Google Console-> API sekmesinden Enable APIS and Services linkine tıklayarak ilerledim. Big Data kısmında yer alan Google Cloud Pub/Sub API hizmetini seçip 

Enable yazan düğmeye bastım. Böylece Google Cloud Platform üzerinde yer alan bir projem için Pub/Sub API hizmetini etkinleştirmiş oldum.

GCP üzerinde, doğal dil işleme hizmetinden(Google Cloud Natural Language API) çeviri servisine(Google Cloud Translation API), tahminlemeden(Prediction API) makine öğrenimine(Google Machine Learning Engine) kadar farklı farklı kategorilerde bir çok API olduğunu ve proje bazlı kullanılabildiğini ifade edebilirim. Tabii bunları deneyimlemeden önce mutlaka ücret politikasını incelemekte yarar var.

Bundan sonra iş West-World terminalindeydi. gCloud komutunu kullanarak(daha önceden West-World'e kurmuştum) bir topic oluşturmayı, bu topic'e abone olup mesaj göndermeyi ve diğer bir abone ile de bu mesajı okumayı denedim. İşte West-World'ün bu denemeler sonrası görünümü.

Öncelikle şunu belirtmem lazım; işe gcloud init komutu ile başlamakta yarar olabilir. Nitekim projeniz için makinedeki ayarların tekrardan yapılması gerekebilir. Ekran görüntüsünden de görüleceği üzere pubsub uygulamasına ait komutları kullanarak string bir mesajı codeTopic isimli bir konu başlığı altında yayınlıyor ve tekrardan okuyoruz. Kullanılan komutlara kısaca bakacak olursak şunları söyleyebiliriz;

codeTopic isimli bir konu başlığı oluşturmak için şu komutu kullanıyoruz,

gcloud pubsub topics create codeTopic

Ben oluşturulan topikleri nasıl görebileceğimizi de merak ettiğimden şöyle bir komut buldum.

gcloud pubsub topics list

Tabii bu konu başlığına mesaj atmak veya okumak için öncelikle abone olunması gerekiyor. westworldSubscription isimli bir aboneliği aşağıdaki komutla oluşturmak mümkün.

gcloud pubsub subscriptions create --topic codeTopic westworldSubscription

Eğer abonelerin bir listesini görmek istersek de şu komut işimize yarayacaktır.

gcloud pubsub subscriptions list

Bir topic ve bir abone var. Bu durumda uzaya doğru bir mesaj fırlatılabilir. Söz gelimi codeTopic başlığı altında "convert this message to C#" şeklinde bir mesaj gönderebiliriz.

gcloud pubsub topics publish codeTopic --message "convert this message to c#"

Peki gönderdiğimiz bu mesajı nasıl çekeceğiz? Bunun aslında iki yolu var. Birisi Push diğeri ise Pull olarak geçiyor. Push modelinde google cloud platform tarafının abone olan tarafa mesaj göndermesi gibi bir durum söz konusu. Pull modelinde ise abonenin kendisi gidip mesajı alıyor. Aşağıdaki komut pull modeline göre çalışmakta (Şu adreste Push ve Pull metodlarının çalışması ve hangi durumda hangisinin tercih edilmesi gerektiğine dair bir takım bilgiler bulunmakta)

gcloud pubsub subscriptions pull --auto-ack westworldSubscription

Ben mesajı gönderip almayı başardıktan sonra ilgili konu başlığı ve aboneliği nasıl sileceğimi de öğrendim. Şu komutlarla codeTopic ve buna abone olan westworldSubscription'ı silebiliyoruz. Elbette bu tip oluşturma ve silme işlemlerini Google Cloud Platform arabirimi üzerinden de yapabiliriz.

gcloud pubsub topics delete codeTopic
gcloud pubsub subscriptions delete westworldSubscription

.Net Core Tarafı

Gelelim kod tarafına. Komut satırından çalışırken West-World üzerinden gCloud aracılığıyla topic oluşturabileceğimi, bu topic için bir abonelik kullanabileceğimi ve mesaj gönderip okuyabileceğimi öğrenmiştim. Pek tabii bunu bir program kodu ile nasıl yapabileceğimi de keşfetmem gerekiyordu. İlk olarak West-World'ün en sevilen sakinlerinden olan Visual Studio Code'un kapısını çaldım. Ondan bana basit bir Console projesi açmasını istedim. 

dotnet new console -o gcppubsubhello

Sonrasındaysa işimi kolaylaştıracak olan Google.Cloud.PubSub kütüphanesinin yazıyı hazırladığım tarihte önerilen sürümünü projeye ekledim.

dotnet add package Google.Cloud.PubSub.V1 --version 1.0.0-beta16

Artık paketi kullanarak Pub/Sub API ile konuşmaya başlayabilirdim. İlk olarak bir topic oluşturmayı ve bu topic için mesajlar yayınlamayı denedim.

using System;
using System.Collections.Generic;
using Google.Cloud.PubSub.V1;

namespace gcppubsubhello
{
    class Program
    {
        static void Main(string[] args)
        {
            var projectId = "subtle-seer-193315";
            var topicId = "codeTopic";

            PublisherServiceApiClient psClient = PublisherServiceApiClient.Create();

            TopicName topicName = new TopicName(projectId, topicId);
            psClient.CreateTopic(topicName);
            IEnumerable<Topic> topics = psClient.ListTopics(new ProjectName(projectId));
            foreach (Topic t in topics)
                Console.WriteLine($"{t.Name}");

            PublisherClient publisher = PublisherClient.Create(topicName, new[] { psClient });
            var result = publisher.PublishAsync("Convert.ToQBit function");
            Console.WriteLine(result.Result);

            result = publisher.PublishAsync("GetFactorial function");
            Console.WriteLine(result.Result);           
        }
    }
}

İşin başında PublisherServiceApiClient türünden bir nesne oluşturmak gerekiyor. Bunu Create metodu ile sağlıyoruz. Sonrasında TopicName türünden bir örnek oluşturuluyor. İlk parametre GCPdeki projenin ID değeri, diğeri ise topic için verilecek string bir bilgi(Topic ID) CreateTopic fonksiyonu kullanılarak ilgili Topic'in Google tarafında oluşturulması sağlanıyor. Ki örneği ilk çalıştırdığımda bunu görebildim.

ListTopics metodu ile var olan tüm topic bilgilerini elde edebiliriz. Bende bunu denemek istedim. Mesaj yayınlamak içinse bir PublisherClient örneğine ihtiyaç var. Bunu oluştururken ilk parametre ile topic nesnesini, ikinci parametre ile de PublisherServiceApiClient örneğini veriyoruz. Böylece hangi Google projesinin hangi konusuna abone olacağımızı bildirmiş oluyoruz. Sonrası oldukça kolay. PublishAsync fonksiyonunu kullanarak bir konu başlığına mesaj bırakılıyor. Ben örnek olarak iki tane string içerik gönderdim. Sonuç olarak elde edilen bilgiler ise bu mesajlar için üretilen AcknowledgeID değerleridir. Topic altına bırakılan her mesajın(sonradan aynı içeriğe sahip mesajlar tekrar geldiğinde farklı olacak şekilde) birer ackID değeri bulunur. Kodu arka arkaya çalıştırdığımda aşağıdaki sonuçları elde ettim.

İlk çalıştırma normal sonuçlansa da ikinci çalıştırmada bir exception almıştım. Aslında hata oldukça basitti.

Unhandled Exception: Grpc.Core.RpcException: Status(StatusCode=AlreadyExists, Detail="Resource already exists in the project (resource=codeTopic).")

Zaten codeTopic isimli bir Topic vardı. Tekrar yaratmaya çalışınca bir çalışma zamanı istisnası oluştu. Bu gibi durumları engellemek için topic nesnesinin var olup olmadığını kontrol etmekte ve yoksa oluşturmaya çalışmakta yarar var. Silme işlemi için DeleteTopic fonksiyonu kullanılabilir. Oluşturulma adımıysa try...catch...when yapısı ile daha güvenli hale getirilebilir. Ben bu kadarlık ipucu vereyim. Gerisini siz deneyin ;)

Topic oluşturulması ve mesaj yayınlandığını görmek benim için yeterliydi. Sıradaki adım codeTopic konusuna atılan mesajları okumaktı. İlk olarak bir abone oluşturmak gerekiyor.

var projectId = "subtle-seer-193315";
var topicId = "codeTopic";

TopicName topicName = new TopicName(projectId, topicId);
SubscriberServiceApiClient subsClient = SubscriberServiceApiClient.Create();
SubscriptionName subsName = new SubscriptionName(projectId, "einstein");
subsClient.CreateSubscription(subsName, topicName, pushConfig: null, ackDeadlineSeconds: 120);

Abone üretme işi bu kez SubscriberServiceApiClient nesnesinde. Create metodu ile bu nesne örneklendikten sonra CreateSubscription fonksiyonu ile de aboneyi oluşturmaktayız. Abonemiz einstein isimli bir ID değerine sahip, Push değil de Pull modelini kullanan bir abone. Kodu ilk çalıştırdığımda abonenin my-starwars-game-project için başarılı bir şekilde oluşturulduğunu gördüm.

Pek tabii kodu ikince kez denediğimde zaten var olan bir aboneyi tekrar oluşturmaya çalıştığım için exception almam gayet normaldi.

Grpc.Core.RpcException: Status(StatusCode=AlreadyExists, Detail="Resource already exists in the project (resource=einstein).")

Çözüm olarak abonenin zaten var olup olmadığı kontrol edilebilir. Aynen Topic oluşturma vakasında olduğu gibi(Bunu benim için denersiniz değil mi? :) )

Bir abonem olduğuna göre onu kullanarak codeTopic üzerine bırakılan mesajları okumayı deneyebilirdim. İşte kodlar.

using System;
using System.Linq;
using System.Text;
using System.Threading;
using Google.Cloud.PubSub.V1;

namespace gcppubsubhello
{
    class Program
    {
        static void Main(string[] args)
        {
            var projectId = "subtle-seer-193315";

            SubscriberServiceApiClient subsClient = SubscriberServiceApiClient.Create();
            SubscriptionName subsName = new SubscriptionName(projectId, "einstein");
            SubscriberClient einstein = SubscriberClient.Create(subsName, new[] { subsClient });
            bool acknowledge = false;
            einstein.StartAsync(
                async (PubsubMessage pubSubMessage, CancellationToken cancel) =>
                {
                    string msg = Encoding.UTF8.GetString(pubSubMessage.Data.ToArray());
                    await Console.Out.WriteLineAsync($"{pubSubMessage.MessageId}: {msg}");
                    return acknowledge ? SubscriberClient.Reply.Ack : SubscriberClient.Reply.Nack;
                });
            Thread.Sleep(5000);
            einstein.StopAsync(CancellationToken.None).Wait();
        }
    }
}

Bütün iş einstein isimli nesnede bitiyor. StartAync metodu içerisinde, abonenin daha önceki kod parçasında oluşturulurken abone olduğu Topic üstüne atılan mesajlar alınıyor. Ne kadar mesaj varsa gelecektir. Eğer mesaj başarılı bir şekilde alınabilmişse bu Reply.Ack ile ifade edilir(Message handled successfully) Aksi durumda Reply.Nack olur(Message not handled successfully) 

Görüldüğü gibi .Net Core tarafında uygun kütüphaneleri kullanarak Pub/Sub API ile konuşmak oldukça basit. Elbette yapılabilecek bir çok şey var. Söz gelimi bu örnekte .Net Core uygulaması Google hizmetini kullanırken hiçbir credential bilgisi kullanmadık. Nitekim West-World'e çok önceden

export GOOGLE_APPLICATION_CREDENTIALS="my-starwars-game-project-d977a50a19f5.json"

şeklinde bir terminal komutu ile gerekli credential bilgilerini işlemiştim. Eğer yazdığımız ürünü bir sunucuya atacak ve oradan Google Pub/Sub hizmetini kullandırmak isteyeceksek bu tip Credential bilgilerini de kod tarafında yüklememiz gerekebilir. Bunun nasıl yapılabileceği ile ilgili olarak Google'ın şu adresindeki yazıya bakabilirsiniz.

Benim için sıradaki aşama bir Google fonksiyonunu Pub/Sub API üzerinden tetikletmek. Yani yazının başında bahsettiğim vakadaki çalışmanın minik bir hattını canlandırmaya çalışmak. Bakalım yolda karşıma öğrenmem gereken daha neler neler çıkacak. Siz buradaki kullanım şekillerini geliştirerek ilerlemeye devam edebilirsiniz. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Google Cloud Pub/Sub Service Macerası

$
0
0

Merhaba Arkadaşlar,

Yeni yuvam ile evimin arası 40 km. Uzaklık nedeniyle mesailerimiz erken başlıyor. Sabah 05:50de çalan alarmla güne başlıyorum. Üst baş, kişisel bakım, seyahat boyu bana eşlik edecek filtre kahveyi hazırlama vs derken 06:35 sıralarında sevgili servis şoförümüz İhsan ağabey ile buluşup yola devam ediyorum. Yaklaşık 40-45 dakikalık bir seyahatten sonra iş yerine ulaşıyorum. Yol boyunca "o saatte kim ayakta olur?" sorusunu cevaplarcasına her sabah onlarca kez ezen insanla karşılaşıyorum. Mevsime göre evlerin sarı beyaz oda ışıkları, seyir halindeki arabalar, çalışanları işe götüren servisler, otobüsler, minibüsler, duraklarda bekleyen öğrenciler... O vakitlerde empati yapmak farklı bir deneyim.

Ama bazı sabahlarda Feedly sayfama düşen yazıları okuyorum. Şirkete ulaştığımda mesainin başladığı 07:45e kadar da neredeyse yarım saatlik serbest zamanım oluyor. Genelde beğendiğim ve Feedly listemde favorilere eklediğim bir yazının devamını o zaman diliminde getiriyorum. Bazı yazıları da tekrar tekrar okuyorum. İşte tam da böyle bir sabahtı Google'ın iş ortaklarından olan Incentro firmasının(Internet sayfaları çok hoş) direktörü Kees van Bemmel'ın kaleminde çıkan yazıyı okuduğumda. Makale, Cloud Platform temelli olarak geliştirilen bir çözüm hakkındaydı. 

Şirkete varır varmaz ilk işim yazının üstünden bir kere daha geçmek olmuştu. Sadece başlığında Publisher/Subscriber, Cloud Function, Machine Learning, Serverless kelimeleri geçiyordu. Bunlar ilgi çekici olması için fazlasıyla yeterliydi. Google'un bloğuna konu olan vakada, video, resim ve ses gibi içerikleri yönetmeye çalışan bir firmanın Google Cloud Platform ile uyguladığı Serverless çözüm anlatılıyordu. Sorun bu içeriklerin takı bazında kayıt altına alınması ve aramalarda doğru konumlandırılamamasıyla alakalıydı.

Şöyle düşünebiliriz; müşteri olarak elimizde tonlarca video, resim ve ses içeriği bulunuyor. Bunları tag sistemi ile teker teker kategorize etmeye çalışırken ne kadar doğru sonuçlar üretebiliyoruz? Kaçını yanlış takılarla, hatta takıları olmadan sisteme dahil ediyoruz. İşte söz konusu çözümde hem takı belirleme hem de arama işleri için gerekli içeriklerin Elasticsearch üzerinde indekslenmesinde Google Cloud Platform'un çeşitli hizmetlerinden nasıl yararlanıldığı anlatılıyordu. Biliyorum "ne bu? ne bu?" diyorsunuz. Buradaki yazıyı okumanızı şiddetle tavsiye ederim. 

Şunu hayal edin...Diyelim ki aksiyon videoları/fotoğrafları çekenlerin olduğu bir internet arşivinin sahibisiniz. Müşterileriniz videolarını yükledikçe içeriklerindeki bir takım imgelere göre otomatik olarak takılarla işaretlenmelerini(mesela rüzgar sörfünü otomatik olarak algılayıp windsurfing takısı ile işaretlenmesi) ve hatta konuşmalarındaki metinsel ifadelere göre de("alaçatı'nın rüzgarları sörf yapmak için idealmiş ahbap...") hangi sporlarla uğraştıklarını ve belki de konuşanın hangi ünlü olabileceğini kayıt altına almak istiyorsunuz. Hatta üyelerinizin çekip yükledikleri fotoğraflarda sizin tanımladığınız imgelerin otomatik olarak algılanıp takı bazında değerlendirilmesini istiyorsunuz vs

Ben yazıyı okuduktan sonra masamın başına geçtiğimde yaptığım ilk iş, mimari resmin bir benzerini çizmeye çalışmak oldu. Her zaman okuduğumu bakarak da olsa(az bakarak yapılanı kabul, hiç bakmadan tek seferde yapılanı makbuldur)çizmeye çalışırdım. Kendi notlarımı ekleyerek öğrenmeyi pekiştirmeye gayret ederdim. Sonuçta kurşun kalemle de olsa aşağıdaki gibi bir şeylere ulaştım.

Kabaca olayı anlamış gibiydim. Eğer işlenmesini istediğim bir değer varsa(asset diyelim), bunu Google Cloud Storage'a atmam yeterliydi. Sonrasında Google'ın Handler fonksiyonları devreye girip bu değeri çeşidine göre işleyişin yürütüldüğü hattaki uygun enstrümana(yazının konusu olan Pub/Sub hizmetine) yönlendirecekti. Bu yönlendirme sırasında resim, video ve ses ile ilgili işleme fonksiyonları(Google Cloud Functions) devreye girecekti. Sonrası Elasticsearch'e atılan bilgilerden ibaretti. Benim ilgimi çeken Machine Learning, Speech to Text gibi akıllı hizmetlerin sunulduğu Google Cloud Functions alanıydı. Lakin daha önceden bir şekilde incelemiş olmama rağmen aradaki bir katmanı öğrenmeden ilerleyemeyeceğimi anlamıştım. Google Pub/Sub hizmeti. 

Esasında bir resmin içerisindeki nesnelere göre Tensorflow'un bile araya girebildiği anlamlaştırma ya da bir video içerisindeki sesin Speech API ile metne dönüştürülüp konunun ne olduğunun çıkartılması ve tüm bunların EleasticSearch üzerine yazılması gibi fonksiyonellikler bir şekilde tetikleniyordu. Genellikle bir HTTP talebi bunun için yeterli ancak Publisher/Subscriber modeli de bu tetikleyicilerden birisiydi. İşte benim öncelikli olarak Google Cloud Platform üzerindeki Publisher/Subscriber hizmetini anlamam gerekiyordu.

Temel olarak bu hizmeti şu şekilde ifade edebiliriz; Uygulamalar arasında güvenilir şekilde hızlı ve asenkron olarak mesaj değiş tokuşuna izin veren bir Google hizmeti olarak tanımlasak yanlış olmaz. Sistem klasik Publisher/Subscriber modelini baz alır. Publisher rolünü üstlenen taraf belli bir konu(topic) için mesaj yayımlar. Subscriber rolünü üstlenen taraf eğer ilgili konuya(topic) abone olmuşsa yayıncının mesajını istediği zaman çekebilir. Google'ın bu hizmetindeki mesajlar alıcıya ulaşana kadar belli bir süre boyunca(ben araştırırken 7 gündü) korunmaktadır.

Bu teorik bilgiyi pekiştirmek ve özellikle bunu West-World gibi Ubuntu tabanlı bir dünyada, .Net Core,Go,Ruby, Python, Node.js gibi dilleri kullanarak deneyimlemek benim için önemliydi. Ana amacım Google Cloud Platform'da Pub/Sub servisini belli bir proje için etkinleştirmek ve sonrasında .Net Core tarafında belli bir topic için mesaj yayınlayıp bu mesajı okuyabilmek. Zaten Google Cloud Platform açısından amaç da bu. Uygulamalar arasında güvenilir bir hat üzerinden mesaj akışına izin veren yüksek performanslı tamamen asenkron çalışan bir boru hattı(pipeline)Öyleyse gelin adım adım ilerleyerek konuyu anlamaya çalışalım.

gCloud ile İlk Deneyim

Google bu konu ile ilişkili oldukça zengin ve basit öğreti dökümanları sunmakta(Diğer bulut bilişim sistemlerinde olduğu gibi) Bende ilgili dokümanları takip ettim ve ilk olarak gCloud aracını kullanarak var olan bir Google projemde Publisher/Subscriber modelini deneyimlemeye çalıştım. my-starwars-game-project isimli projemi seçtikten sonra Google Console-> API sekmesinden Enable APIS and Services linkine tıklayarak ilerledim. Big Data kısmında yer alan Google Cloud Pub/Sub API hizmetini seçip 

Enable yazan düğmeye bastım. Böylece Google Cloud Platform üzerinde yer alan bir projem için Pub/Sub API hizmetini etkinleştirmiş oldum.

GCP üzerinde, doğal dil işleme hizmetinden(Google Cloud Natural Language API) çeviri servisine(Google Cloud Translation API), tahminlemeden(Prediction API) makine öğrenimine(Google Machine Learning Engine) kadar farklı farklı kategorilerde bir çok API olduğunu ve proje bazlı kullanılabildiğini ifade edebilirim. Tabii bunları deneyimlemeden önce mutlaka ücret politikasını incelemekte yarar var.

Bundan sonra iş West-World terminalindeydi. gCloud komutunu kullanarak(daha önceden West-World'e kurmuştum) bir topic oluşturmayı, bu topic'e abone olup mesaj göndermeyi ve diğer bir abone ile de bu mesajı okumayı denedim. İşte West-World'ün bu denemeler sonrası görünümü.

Öncelikle şunu belirtmem lazım; işe gcloud init komutu ile başlamakta yarar olabilir. Nitekim projeniz için makinedeki ayarların tekrardan yapılması gerekebilir. Ekran görüntüsünden de görüleceği üzere pubsub uygulamasına ait komutları kullanarak string bir mesajı codeTopic isimli bir konu başlığı altında yayınlıyor ve tekrardan okuyoruz. Kullanılan komutlara kısaca bakacak olursak şunları söyleyebiliriz;

codeTopic isimli bir konu başlığı oluşturmak için şu komutu kullanıyoruz,

gcloud pubsub topics create codeTopic

Ben oluşturulan topikleri nasıl görebileceğimizi de merak ettiğimden şöyle bir komut buldum.

gcloud pubsub topics list

Tabii bu konu başlığına mesaj atmak veya okumak için öncelikle abone olunması gerekiyor. westworldSubscription isimli bir aboneliği aşağıdaki komutla oluşturmak mümkün.

gcloud pubsub subscriptions create --topic codeTopic westworldSubscription

Eğer abonelerin bir listesini görmek istersek de şu komut işimize yarayacaktır.

gcloud pubsub subscriptions list

Bir topic ve bir abone var. Bu durumda uzaya doğru bir mesaj fırlatılabilir. Söz gelimi codeTopic başlığı altında "convert this message to C#" şeklinde bir mesaj gönderebiliriz.

gcloud pubsub topics publish codeTopic --message "convert this message to c#"

Peki gönderdiğimiz bu mesajı nasıl çekeceğiz? Bunun aslında iki yolu var. Birisi Push diğeri ise Pull olarak geçiyor. Push modelinde google cloud platform tarafının abone olan tarafa mesaj göndermesi gibi bir durum söz konusu. Pull modelinde ise abonenin kendisi gidip mesajı alıyor. Aşağıdaki komut pull modeline göre çalışmakta (Şu adreste Push ve Pull metodlarının çalışması ve hangi durumda hangisinin tercih edilmesi gerektiğine dair bir takım bilgiler bulunmakta)

gcloud pubsub subscriptions pull --auto-ack westworldSubscription

Ben mesajı gönderip almayı başardıktan sonra ilgili konu başlığı ve aboneliği nasıl sileceğimi de öğrendim. Şu komutlarla codeTopic ve buna abone olan westworldSubscription'ı silebiliyoruz. Elbette bu tip oluşturma ve silme işlemlerini Google Cloud Platform arabirimi üzerinden de yapabiliriz.

gcloud pubsub topics delete codeTopic
gcloud pubsub subscriptions delete westworldSubscription

.Net Core Tarafı

Gelelim kod tarafına. Komut satırından çalışırken West-World üzerinden gCloud aracılığıyla topic oluşturabileceğimi, bu topic için bir abonelik kullanabileceğimi ve mesaj gönderip okuyabileceğimi öğrenmiştim. Pek tabii bunu bir program kodu ile nasıl yapabileceğimi de keşfetmem gerekiyordu. İlk olarak West-World'ün en sevilen sakinlerinden olan Visual Studio Code'un kapısını çaldım. Ondan bana basit bir Console projesi açmasını istedim. 

dotnet new console -o gcppubsubhello

Sonrasındaysa işimi kolaylaştıracak olan Google.Cloud.PubSub kütüphanesinin yazıyı hazırladığım tarihte önerilen sürümünü projeye ekledim.

dotnet add package Google.Cloud.PubSub.V1 --version 1.0.0-beta16

Artık paketi kullanarak Pub/Sub API ile konuşmaya başlayabilirdim. İlk olarak bir topic oluşturmayı ve bu topic için mesajlar yayınlamayı denedim.

using System;
using System.Collections.Generic;
using Google.Cloud.PubSub.V1;

namespace gcppubsubhello
{
    class Program
    {
        static void Main(string[] args)
        {
            var projectId = "subtle-seer-193315";
            var topicId = "codeTopic";

            PublisherServiceApiClient psClient = PublisherServiceApiClient.Create();

            TopicName topicName = new TopicName(projectId, topicId);
            psClient.CreateTopic(topicName);
            IEnumerable<Topic> topics = psClient.ListTopics(new ProjectName(projectId));
            foreach (Topic t in topics)
                Console.WriteLine($"{t.Name}");

            PublisherClient publisher = PublisherClient.Create(topicName, new[] { psClient });
            var result = publisher.PublishAsync("Convert.ToQBit function");
            Console.WriteLine(result.Result);

            result = publisher.PublishAsync("GetFactorial function");
            Console.WriteLine(result.Result);           
        }
    }
}

İşin başında PublisherServiceApiClient türünden bir nesne oluşturmak gerekiyor. Bunu Create metodu ile sağlıyoruz. Sonrasında TopicName türünden bir örnek oluşturuluyor. İlk parametre GCPdeki projenin ID değeri, diğeri ise topic için verilecek string bir bilgi(Topic ID) CreateTopic fonksiyonu kullanılarak ilgili Topic'in Google tarafında oluşturulması sağlanıyor. Ki örneği ilk çalıştırdığımda bunu görebildim.

ListTopics metodu ile var olan tüm topic bilgilerini elde edebiliriz. Bende bunu denemek istedim. Mesaj yayınlamak içinse bir PublisherClient örneğine ihtiyaç var. Bunu oluştururken ilk parametre ile topic nesnesini, ikinci parametre ile de PublisherServiceApiClient örneğini veriyoruz. Böylece hangi Google projesinin hangi konusuna abone olacağımızı bildirmiş oluyoruz. Sonrası oldukça kolay. PublishAsync fonksiyonunu kullanarak bir konu başlığına mesaj bırakılıyor. Ben örnek olarak iki tane string içerik gönderdim. Sonuç olarak elde edilen bilgiler ise bu mesajlar için üretilen AcknowledgeID değerleridir. Topic altına bırakılan her mesajın(sonradan aynı içeriğe sahip mesajlar tekrar geldiğinde farklı olacak şekilde) birer ackID değeri bulunur. Kodu arka arkaya çalıştırdığımda aşağıdaki sonuçları elde ettim.

İlk çalıştırma normal sonuçlansa da ikinci çalıştırmada bir exception almıştım. Aslında hata oldukça basitti.

Unhandled Exception: Grpc.Core.RpcException: Status(StatusCode=AlreadyExists, Detail="Resource already exists in the project (resource=codeTopic).")

Zaten codeTopic isimli bir Topic vardı. Tekrar yaratmaya çalışınca bir çalışma zamanı istisnası oluştu. Bu gibi durumları engellemek için topic nesnesinin var olup olmadığını kontrol etmekte ve yoksa oluşturmaya çalışmakta yarar var. Silme işlemi için DeleteTopic fonksiyonu kullanılabilir. Oluşturulma adımıysa try...catch...when yapısı ile daha güvenli hale getirilebilir. Ben bu kadarlık ipucu vereyim. Gerisini siz deneyin ;)

Topic oluşturulması ve mesaj yayınlandığını görmek benim için yeterliydi. Sıradaki adım codeTopic konusuna atılan mesajları okumaktı. İlk olarak bir abone oluşturmak gerekiyor.

var projectId = "subtle-seer-193315";
var topicId = "codeTopic";

TopicName topicName = new TopicName(projectId, topicId);
SubscriberServiceApiClient subsClient = SubscriberServiceApiClient.Create();
SubscriptionName subsName = new SubscriptionName(projectId, "einstein");
subsClient.CreateSubscription(subsName, topicName, pushConfig: null, ackDeadlineSeconds: 120);

Abone üretme işi bu kez SubscriberServiceApiClient nesnesinde. Create metodu ile bu nesne örneklendikten sonra CreateSubscription fonksiyonu ile de aboneyi oluşturmaktayız. Abonemiz einstein isimli bir ID değerine sahip, Push değil de Pull modelini kullanan bir abone. Kodu ilk çalıştırdığımda abonenin my-starwars-game-project için başarılı bir şekilde oluşturulduğunu gördüm.

Pek tabii kodu ikince kez denediğimde zaten var olan bir aboneyi tekrar oluşturmaya çalıştığım için exception almam gayet normaldi.

Grpc.Core.RpcException: Status(StatusCode=AlreadyExists, Detail="Resource already exists in the project (resource=einstein).")

Çözüm olarak abonenin zaten var olup olmadığı kontrol edilebilir. Aynen Topic oluşturma vakasında olduğu gibi(Bunu benim için denersiniz değil mi? :) )

Bir abonem olduğuna göre onu kullanarak codeTopic üzerine bırakılan mesajları okumayı deneyebilirdim. İşte kodlar.

using System;
using System.Linq;
using System.Text;
using System.Threading;
using Google.Cloud.PubSub.V1;

namespace gcppubsubhello
{
    class Program
    {
        static void Main(string[] args)
        {
            var projectId = "subtle-seer-193315";

            SubscriberServiceApiClient subsClient = SubscriberServiceApiClient.Create();
            SubscriptionName subsName = new SubscriptionName(projectId, "einstein");
            SubscriberClient einstein = SubscriberClient.Create(subsName, new[] { subsClient });
            bool acknowledge = false;
            einstein.StartAsync(
                async (PubsubMessage pubSubMessage, CancellationToken cancel) =>
                {
                    string msg = Encoding.UTF8.GetString(pubSubMessage.Data.ToArray());
                    await Console.Out.WriteLineAsync($"{pubSubMessage.MessageId}: {msg}");
                    return acknowledge ? SubscriberClient.Reply.Ack : SubscriberClient.Reply.Nack;
                });
            Thread.Sleep(5000);
            einstein.StopAsync(CancellationToken.None).Wait();
        }
    }
}

Bütün iş einstein isimli nesnede bitiyor. StartAync metodu içerisinde, abonenin daha önceki kod parçasında oluşturulurken abone olduğu Topic üstüne atılan mesajlar alınıyor. Ne kadar mesaj varsa gelecektir. Eğer mesaj başarılı bir şekilde alınabilmişse bu Reply.Ack ile ifade edilir(Message handled successfully) Aksi durumda Reply.Nack olur(Message not handled successfully) 

Görüldüğü gibi .Net Core tarafında uygun kütüphaneleri kullanarak Pub/Sub API ile konuşmak oldukça basit. Elbette yapılabilecek bir çok şey var. Söz gelimi bu örnekte .Net Core uygulaması Google hizmetini kullanırken hiçbir credential bilgisi kullanmadık. Nitekim West-World'e çok önceden

export GOOGLE_APPLICATION_CREDENTIALS="my-starwars-game-project-d977a50a19f5.json"

şeklinde bir terminal komutu ile gerekli credential bilgilerini işlemiştim. Eğer yazdığımız ürünü bir sunucuya atacak ve oradan Google Pub/Sub hizmetini kullandırmak isteyeceksek bu tip Credential bilgilerini de kod tarafında yüklememiz gerekebilir. Bunun nasıl yapılabileceği ile ilgili olarak Google'ın şu adresindeki yazıya bakabilirsiniz.

Benim için sıradaki aşama bir Google fonksiyonunu Pub/Sub API üzerinden tetikletmek. Yani yazının başında bahsettiğim vakadaki çalışmanın minik bir hattını canlandırmaya çalışmak. Bakalım yolda karşıma öğrenmem gereken daha neler neler çıkacak. Siz buradaki kullanım şekillerini geliştirerek ilerlemeye devam edebilirsiniz. Böylece geldik bir makalemizin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

http://www.buraksenyurt.com/post/Blockchain-Eliptik-Egri-Sifreleme-AlgoritmasıBlockchain Eliptik Eğri Şifreleme Algoritması

$
0
0

Merhaba Arkadaşlar,

Matematik tek evresenl dil olarak varoluşumuzdan bu yana yaşamın içerisinde. Onun diğer bilimlere olan pozitif etkisi tartışılamaz. Bugün ulaştığımız noktada teknoloji sınırlarını zorlarken yüz yıllar öncesinden ispat edilmiş pek çok teoremin uygulanabilirliklerine de rastlıyoruz. Doğruyu söylemek gerekrise 1999 yılında ilk işe başladığımdan beri matematik'ten epey uzakta sadece kod yazmaktayım. Belki de bugün .Net Core'un Linux üzerinde koşturulup bir Cloud platformuna taşınması da önemli bir mevzu. Lakin o hayranlık duyduğumuz fikirlerin arkasında, çok fazla ilişmediğimiz(belki de bakmaya korktuğumuz) güçlü bir matematik var. Bende büyük bir cesaretle o fikirlerden birisinin arkasında olan matematiği bir nebze olsun anlamak istedim. Matematik kesin kuralları olan bir dil olduğu içi, yazdığım şeyleri doğru telafüz etmem gerekiyor. Eğer basit bir şekilde anlatabilirsem, konuyu da anlamış sayılırm(Son gün notu: Basitleştiremedi)

Geçtiğimiz iki hafta boyunca neredeyse her gün yarım saatimi ayırdığım ve anlamak için çaba sarf ettiğim bir konu oldu. Blockchain'in kullandığı Elliptic Curve Cryptography(ECC) algoritmasının nasıl çalıştığını öğrenmek. Araştırmalarıma başladığımda olayın içerisinde matematiğin bilgisayar şifreleme teknikleri üzerine kullanılan bir çok teorisine denk geldim. 1993 yılında girdiğim Matematik Mühendisliği bölümünde okurken gördüğüm pek çok konu burada da yer alıyordu. Ama zaman içerisinde hepsini unutmuşum. İkinci ve üçüncü dereceden denklemler, eliptik eğriler, asal sayılar, gruplar, sonlu alanlar(Finite Fields), Fermat'nın küçük teoremi(Little Theorem), modüler aritmetik, ayrık logaritma problemi(discrete logarithm problem), double and add algoritması, euclid vs... Aslında her şey Blockchain sisteminde yer alan eliptik eğri denklemine ait grafik gösterimin, gerçek sayılar ile ifade edileninden çok daha farklı olduğunu öğrenmemle başladı diyebilirim.

Örneğin Bitcoin, secp256k1 isimli ve aşağıdaki eşitlikle ifade edilen eliptik eğri denklemini kullanmakta. secp256k1' deki sec, Standards for Efficent Cryptography anlamına gelirken 256 değeri asal sayının kaç bit olduğunu ifade etmektedir. Sonlara doğru bu kavramları anlayacağım/anlatabileceğim diye umut ediyorum.

y2=x3+7

Gerçek sayılar için olan gösterimi şöyle;

Toplam eleman sayısı asal olacak şekilde oluşturulmuş bir sonlu alan dizilimi için olan gösterimi ise şu şekilde;

Merak uyandırdı değil mi? Öyleyse ilk konumuz ile başlayalım.

Eliptik Eğriler

Eliptik eğri denklemlerine geçmeden önce bir kaç basit denklemi de hatırlamamız lazım. Doğrusal, ikinci dereceden ve üçüncü dereceden denklemleri ve x,y düzlemindeki gösterimlerini görünce sizler de hatırlayacaksınız?

y=ax+b , doğru denklemi

y=ax2+bx+c , parabol denklemi

y=ax3+bx2+cx+d , 3ncü dereceden denklem

ve tabii konumuz olan eliptik eğri denklemi.

y2=x3+ax+b

Yukarıdaki şekilde denkleme ait bir kaç farklı örnek görmektesiniz. Her ne kadar ben çok iyi çizemesemde, özellikle x ekseni özelinde simetriklik olduğunu söyleyebiliriz. Bazı hallerde iki ayrı eğri ve bazı hallerde de bu iki eğrinin birleştiği grafikler söz konusu. y2 den kaynaklı bir durum olduğu aşikar. Eliptik eğrilerin enteresan bir özelliği de vardır. Eğri üzerinde olduğu bilinen iki koordinat söz konusu olduğunda, bu koordinatların x değerleri birbirinden farklı olmak şartıyla, her iki koordinatın toplamından yine eğri üzerine denk düşen 3ncü bir koordinatı bulmamız mümkündür. Bulunuş şekli matematik severler için hayranlık uyandırıcıdır. Aşağıdaki şekille konuyu anlatmaya çalışayım.

Şekilde Mnoktasını bulunuşu ifade edilmektedir. Olay şöyle başlar. Eğri üzerinden bilinen iki nokta referans alınır. x değerleri birbirlerinden farklı olan iki nokta. Bu noktaların üstünden geçen bir doğru çizilir. Çizilen doğru eğriyi 3ncü bir noktada daha kesecektir(örneğimizdeki P noktası) Bu noktanın iz düşümü de simetrik taraftaki bir noktaya denk gelmektedir(örneğimizdeki M3)İşte teoriye göre M1 ve M2 noktalarının toplamı M3 noktasını elde etmemizi sağlamaktadır. Tabii toplam dediğimiz olay biraz daha farklı. Noktayı bulmak için aşağıdaki gibi sıralanmış bir formül takımından yararlanılır.

3ncü noktanın bulunmasında ilk iki noktanın sıralı olması şart değildir. Sadece eğri üzerinde olduğu bilinen iki noktanın üzerinden geçen doğrunun kestiği üçüncü noktanın x düzlemindeki iz düşümü önemlidir.

Eğri denklemimiz : y= x+ ax + b

M1 = (x1,y1) , M2 = (x2,y2), M1 + M2 = (x3,y3)

x1 ve x2 eşit olmadığı sürece

Eğim s = (y2 - y1) / (x2 - x1)

x3 = s2 - x2 - x1

y3 = s(x1 - x3) - y1

Kafamızı çok fazla bulandırmadan 3ncü nokta bulmanın nasıl yapıldığını basit bir örnekle inceleyelim.

y2=x3+5x+7
P1 = (2,5) , P2 = (3,7) => P3 = ?

52= 25 = 23+(5*2)+7 (P1 noktası eğri üzerinde)
72= 49 = 33+(5*3)+7 (P2 noktası eğri üzerinde)

s = (7 - 5) / (3 - 2) = 2 (Eğimi bulduk)
x3 = 22 - 2 - 3 = -1
y3 = 2(2 - (-1) ) - 5 = -1

P3 = (-1,1) 
12= 1 = -13+(5*(-1))+7 (P3 noktası eğri üzerinde)

Şimdi örnekte neler oldu anlamaya çalışalım. Denklem ortada. İki tane noktamız var. Öncelikle bu noktaların eğri üzerinde olup olmadıklarının sağlamasını yapıyoruzSonrasında eğim değerini(s) bulmamız gerekiyor. Eğim bulunduktan sonra bu değerden yararlanarak x3 bilinmeyenini ve x3'ü de işin içerisine katarak y3 değerini hesaplıyor  ve 3ncü noktanın koordinatlarını bulmuş oluyoruz. Son aşamada yine x3,y3 noktasının eliptik eğri üzerinde olup olmadığının sağlamasını gerçekleştiriyoruz. Bunu program kodu ile de deneyimleyebiliriz. Özellikle Python gibi diller bu tip matematiksel işlemler için kolaylıklar sunmakta. 

p1=(2,5)
p2=(3,7)

def isOnCurve(p):
    """    
    p1 egri ustunde mi bakalim
    """
    (x,y)=p1
    return y**2 == x**3+(5*x)+7

def findSlope(p1,p2):
    """
    p1 ve p2den yararlanarak egimi buluyoruz
    """
    (x1,y1)=p1
    (x2,y2)=p2
    s=(y2-y1)/(x2-x1)
    return s

def findThirdPoint(p1,p2,s):
    """
    p1 ve p2den yararlanip 3ncu noktanin bulunmasi
    """
    (x1,y1)=p1
    (x2,y2)=p2
    x3=s**2-x2-x1
    y3=s*(x1-x3)-y1
    return (x3,y3)

print p1,"is on curve?",isOnCurve(p1)
print p2,"is on curve?",isOnCurve(p2)
print findSlope(p1,p2)
print findThirdPoint(p1,p2,findSlope(p1,p2))

Python tarafına aşina olmayanlar için bile okunması oldukça kolay bir kod parçası görmektesiniz. x,y koordinatlarını işaret eden noktaları tuple tipi ile işaret etmekteyiz. Bu bir noktanın x ve y değerlerini taşırken veya elde ederken işlerimizi kolaylaştırmakta. isOnCurve fonksiyonu parametre olarak verilen noktanın eğri üzerinde olup olmadığını kontrol ediyor. findSlope metodu ile tahmin edeceğiniz üzere eğim değerini buluyoruz. findThirdPoint fonksiyonu p1 ve p2 parametrelerinden yararlanılarak p3ün yani 3ncü noktanın bulunmasında kullanılmakta. Kodu Visual Studio Code üzerinde geliştirebilirsiniz. Şahsen ben, öyle yaptım.

Eliptik Eğrilerin Gruplar ile İlişkisi

Eliptik eğriler ile matematik grupları arasında yakın ilişki vardır. Özellikle asallık söz konusu ise. Bunları bir eliptik eğri için düşündüğümüzde şunları söyleyebiliriz. 

  • G'yi noktaların olduğu bir grup olarak düşünürsek iki noktanın toplamı (P1 + P2 = P3) yine G'nin içinde yer alacaktır(Kapalılık özelliği)
  • P1 + P2 + P3 = 0 aynı hat üzerinde 3 nokta söz konusu olduğunda toplam sonucu 0 olarak çıkar(Tabii noktaların hiçbirisi 0 olmayacak)
  • Grubun mutlaka şu eşitliği sağlayan bir birim elemanı vardır ki eliptik eğriler için 0 olduğunu söyleyebiliriz. P1 + 0 = 0 + P1 = P1
  • Her bir noktanın x ekseninde bir simetrisi vardır. 
  • Eğer değişebilirlik ( P1 + P2 = P2 + P1 ) söz konusu ise bu grup Abelian(Değişmeli diyebiliriz) olarak isimlendirilir(Abelian olmanın avantajları nelerdir halen araştırıyorum sevgili okur)

Grup olma özellikleri biraz sonra kriptografinin zorluğunu ortaya koyarken değer kazancak. Bu nedenle eliptik eğri kriptografisine geçmeden önce sonlu alanlara, asal sayılar nezninde de uğramamız gerekiyor.

Sonlu Alanlar

Artık eliptik eğrilerin nasıl bir denklem ile ifade edildiğini biliyoruz. Yazının başında Blockchain tarafından kullanılan denklemin grafiğini hatırlarsanız gerçek sayılar yerine toplam eleman sayısı bir asal sayı ile ifade edilen eliptik eğrinin söz konusu olduğunu belirtmiştik. Peki ne olaki bu sonlu alanlar(Finite Fields) Aşağıdaki gibi ifade edilen bir sayı dizisi olduğunu düşünelim(Aslında bizler için 0dan başlayan 13 elemanlı bir tamsayı dizisi)

F13 = {0, 1, 2, 3, … 12}

Bu dizilimin en önemli yanı 13 elemandan oluşması. 13  asal bir sayı. Dizinin bir diğer önemli özelliği de modüler aritmetik denklik kuramına göre içerideki iki sayının toplamının yine içerideki bir elemanı veriyor olması. Üstelik bu sadece toplama değil, çıkarma, çarpma ve bölme işlemleri için de geçerli bir durum. Sadece bölme işleminde kafaların biraz karışabildiği bir senaryo var ki burada da işin içerisine Fermat'nın Küçük Teorim(Fermat's Little Theorem) girmekte.

Toplama, çıkartma ve çarpma işlemlerine örnekler;

4 + 5 = 9 % 13 = 9 (Dizi içerisinde)
8 + 11 = 19 %13 = 6 (Dizi içerisinde)
8 - 12 = (-4) % 13 = 4 (Dizi içerisinde)
9 - 4 = 5 % 13 = 5 (Dizi içerisinde)

Gelelim bölme işlemine...

2 / 3 = 2 * 3-= 2 * 311 = 354.294 % 13 = 5 (Dizi içerisinde)
3 / 12 = 3 * 12-= 3 * 1211 = 2.229.025.112.064 % 13 = 10 (Dizi içerisinde)

İşlemler biraz tuhaf geldi değil mi? Özellike -1 üs değerinin eşitliğin devamında 13-2 şeklinde ifade edilmesi. Burada az önce bahsettiğimiz küçük teorimin büyük bir önemi var. Fermat'a göre p bir asal sayı, a bir tamsayı ve a ile p aralarında asal(p, a'nın bir çarpanı olamaz) iken

211 - 2 = 2046 % 11 = 0 

gibi bir işlem'den bahsedilebilir. Modüler aritmetik notasyonuna göre ifade şudur.

a≡ a (mod p)

Buradan hareketle teoremin ispatı sırasında kullanılan Euler teoremine göre de

ap-1 ≡ 1 (mod p)

dir. Henüz ispatını araştıramamış olsam da bu denkliklerden yola çıkılarak şu ifadenin de doğru olduğu söylenmekte.

ap-2 ≡ a-1 ≡ 1/a (mod p)

Böylece bir bölme işleminin modüler aritmetik enstürmanlarına göre yine dizi içerisindeki bir elemanı işaret ettiğini görmüş oluyoruz.

Eliptik Eğrideki Ayrık Logaritma Problemi

Gelelim yukarıda anlattıklarımızı kullanarak neler yapabileceğimize bakmaya. Bir eliptik eğri üzerinde bir başlangıç noktası seçtiğimizi düşünelim. P olarak isimlendirelim(Sonradan Generator Point adına kavuşacak) Buna göre P'nin 1 katını, 2katını, 3katını ekleyerek devam edelim. Artık elimizde bir nokta grubu var ve onu şöyle ifade edebiliriz.

{0, P, 2P, 3P, 4P, 5P,... (n-1)P}

Çarpan olarak ele alınan n'nin gizli bir anahtar olduğunu düşündüğümüzde her ne kadar sP=Q değerini bulmak kolay olsa da P ve Q'yi bilip s'yi bulmaya çalıştığımız durumda bu o kadar da kolay olmayacaktır. Çünkü 0 ile n-1 arasındaki tüm olası değerleri göz önüne alıp eşitliğin sağlanıp sağlanmadığını anlamamız gerekir. Bunun sebebi ayrık logaritma problemi ile açıklanmaktadır. 

Discrete Logarithm Problem

Aşağıdaki işlemi düşünelim.

 329 mod 17 ≡ 12

Burada 12 değerine ulaşmak kolay. Fakat soru şu;

3mod 17 ≡ 12

Burada x değerini nasıl bulabiliriz? Aslında 3ün olası üslerini taramak söz konusu eşitlikteki uygun x değerini bulmak için yeterli. Küçük bir asal sayı için bu çok büyük sorun teşkil etmeyecektir. Sorun 17 sayısı yerine çok çok çok büyük bir asal sayı geldiğinde ortaya çıkmaktadır. Teorikte mümkün ama pratiğe dökülmesi için asal değere göre dünyadaki işlemci gücünün tamamına sahip olsak bile çok uzun yıllar sürebilecek bir problem söz konusu(Uzmanların dilinden) 

Tekrar P noktalarından oluşan grubumuza dönelim. Buradaki çarpan hesaplamaları için Double and Add algoritmasından yararlanılabilir.

Double and Add algorithm

Double and Add algoritmasında noktanın çarpanının ikilik sayı sistemindeki ifadesinden yararlanılır. Şöyle başlayalım. 19 asal sayısının ikilik sistemdeki karşılğı 

10011

şeklindedir.

Bunu üssel gösterimle ifade etmek istersek şu eşitliği de yazabiliriz.

19 = 10011 = 1.24 + 0.23 + 0.2+ 1.2+ 1.2

Buna göre bir noktanın 19 ile çarpımını da şu şekilde ifade etmemiz mümkün hale gelir.

19P = 24P + 21P + 20P

Oluşan eşitliğe göre Double and Add algoritması şöyle işletilir.

P noktasını al.

Bunu 2ye katla(double). Bu sayede 2P değerini elde ederiz.

2P yi P ile topla(add) Böylece 21P + 20P değerini yakalarız.

...

Bu şekilde ikiye katlama ve toplama işlemlerinin tekrar edilmesi yoluyla sonuca ulaşabiliriz. Siz örneğin 151 sayısı için bu denkliği sağlamaya çalışarak konuyu pekiştirebilirsiniz. İpucu olarak başlangıçı veriyorum;

151 = 10010111 = 1.27 + 0.26 + 0.2+ 1.2+ 0.2+ 1.2+ 1.2+ 1.2= 2+ 2+ 2+ 2+20

 

Bir nokta grubu için tam sayı ile çarpma işlemini ele aldığımıza göre P grubu için şöyle bir örnek yapalım.

Denklemimiz y= x+ 2x + 3
Sonlu alandaki toplam sayı adedi 17 (asaldır dikkat edin)
Başlangıç noktamız P(3,6)
Buna göre P'yi kendisi ile toplaya toplaya aşağıdaki dizilimi elde edebiliriz. 

0P = 0
1P = (3,6)
2P = (12,2)
3P = (15,5)
4p = (14,2)
5P = (8,2)
6P = (8,15)
7P = (14,15)
8P = (15,12)
9P = (12,15)
10P = (3,11)
11P = (∞,∞)
12P = (3,6)
13P = (12,2)
14P =(15,5)
...

Bir şey dikkatinizi çekti mi? Toplamda denklemi sağlayan 22 adet (x,y) noktası söz konusu iken biz 11 elemanlı bir alt grup elde ettik ve bu grubun tekrar eden bir döngü içerisinde olduğunu görmekteyiz. Buradaki hesaplamalar için aşağıdaki örnek kod parçasını da kullanabiliriz. Fonksiyonları ve kullanım şekillerini anlamaya çalışın. İçeride bir de uzatılmış Euclid algoritması olarak isimlendirilmiş bir kısım var.

import collections

EllipticCurve = collections.namedtuple('EliptikEgri', 'name p a b g')

params = EllipticCurve(
    'y^2=x^3+ax+b', #denklem
    p=17, #toplam nokta sayisi
    a=2, #denklem a degeri
    b=3, #denklem b degeri
    g=(3,6) #generator noktasi
)

def ReverseOfMod(n, p):
    """
    n mod p isleminin tersini dondurur.
    egim hesaplamasi isleminde p1 = p2 ve p1 != p2 durumlari icin gerekli
    """
    if n == 0:
        raise ZeroDivisionError('division by zero')

    if n < 0:
        # n ** -1 = p - (-n) ** -1  (mod p)
        return p - ReverseOfMod(-n, p)

    # Uzatilmis Euclid Algoritmasi uygulanir (Extended Euclidean Algorithm)
    s, old_s = 0, 1
    t, old_t = 1, 0
    r, old_r = p, n

    while r != 0:
        d = old_r // r
        old_r, r = r, old_r - d * r
        old_s, s = s, old_s - d * s
        old_t, t = t, old_t - d * t

    gcd, x, y = old_r, old_s, old_t #gcd-greates common divisor - ebob
    return x % p

def FindNegativePoint(p):
    """
    negatif noktayi bulur
    """

    if p is None:
        return None

    x, y = p
    result = (x, -y % params.p)

    return result


def Add(p1, p2):
    """
    grup yasasindaki kriterlere gore p1+p1 islemini gerceklestirir
    """

    if p1 is None:
        # 0 + p2 = p2 durumu
        return p2
    if p2 is None:
        # p1 + 0 = p1 durumu
        return p1

    x1, y1 = p1
    x2, y2 = p2

    if x1 == x2 and y1 != y2:
        return None

    if x1 == x2:
        # p1==p2 durumu
        m = (3 * x1 * x1 + params.a) * ReverseOfMod(2 * y1, params.p)
    else:
        # p1!=p2 durumu
        m = (y1 - y2) * ReverseOfMod(x1 - x2, params.p)

    x3 = m * m - x1 - x2
    y3 = y1 + m * (x3 - x1)
    result = (x3 % params.p,-y3 % params.p)

    return result


def Multiply(n, p):
    """
    n * P islemini gerceklestirir
    """
    if n < 0:
        return Multiply(-n, FindNegativePoint(p))

    result = None
    nextP = p

    while n:
        if n & 1:
            result = Add(result, nextP)

        nextP = Add(nextP, nextP)

        n >>= 1
    return result


for i in range(0,17):
    print i,Multiply(i,(3,6))

Nokta sahası sonlu uzunlukta ve çok doğal olarak alt grup da öyle. Ancak denklem ve asal sayı değeri dikkatli seçilirse çok büyük bir grubun elde edilmesi söz konusu olabilir. Öyle ki geri çevirlemeye çalışıldığında bu inanılmaz derecede zor olur.

Bitcoin Cephesi(secp256k1)

Onlar Blockchain'in bu kriptografi kuramını göz önüne alarak aşağıdaki parametreleri içeren bir eğri tanımlamışlar. 

Denklem : y2=x3+7
Sonlu alan asal sayı değeri (p) = 2256 -232 - 2-2-2-2- 2- 1 
Giriş noktası G=( 79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 )
Bir gruptaki asal nokta sayısı n = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141

Dikkat edileceği üzere nokta ve asal sayı değerleri oldukça büyük. Bu da ayrık logaritma probleminin getireceği sorunun çözümünü oldukça zorlaştırır nitelikte. Her şeyden önce ortada 256bitlik bir asal sayı var. Bunun bir sonucu da en iyimser tahminle ortada 2256 olası gizli anahtarın olması ki herhangibirini tespit edebilmek için var olanlarından mümkün olduğunca çoğunu bilmek gerekiyor. Bunu anlatmak çok zor ama trilyonlarca yıl alabilecek bir zaman ortaya çıktığı söyleniyor(Teorik olarak)

Peki yazılımcı olarak biz bu değerleri kullanarak ne yapabiliriz? Aslında seçeceğimiz bir private key değeri ile public key üretebilir sonra bu iki anahtar bilgisinden yararlanarak dijital bir imza oluşturarak belgelerimizi kriptolayabiliriz. Bu amaçla kullanılabilecek pek çok kütüphane var. Hatta şu adreste güzel bir kod örneği de bulunmakta. İnceleyip denemenizi öneririm.

Sonuç

Eliptik Eğri denklemi Blockchain ve ondan türeyen pek çok yapı tarafından asimetrik şifre üretilip transaction'ların imzalanması maksadıyla kullanılmakta. Asitmerik şifrelemede public ve private olmak üzere iki anahtar söz konusu. Public Key herkes tarafından görülebilir bir bilgi ama private key tahmin edeceğiniz üzere kişiye özel. Private key değeri kullanılarak public key değerinin elde edilmesi mümkün. Bu değeri elde ederken yukarıdaki eliptik eğri denkleminden yararlanılmakta. Ancak public key değerini kullanarak private key bilgisine oluşturmak en azından önümüzdeki birkaç milyon yüz yıl(belki de fazlası) için mümkün değil. Blockchain bir transaction'ı imzalarken private key ile oluşturulmuş bir hash değeri kullanıyor. Hash bilgisinin geriye döndürülerek private key içeriğinin bulunması zaten mümkün değil lakin public key değerine sahip olan birisi kendi ürettiği private key'leri kullanarak oluşturacağı hash'leri karşılaştırmaya çalışabilir. Lakin burada onu bekleyen şey Eliptik Eğri Dijital Kriptografi Algoritması(Elliptic Curve Digital Signature Algorithm) oluyor; ki bu konu şu an için beni aşmakta. Kaynaklar arasında daha fazla kaybolmadan hatırladığım eski matematik denklemlerimi bir kenara bırakıyor ve hepinize mutlu günler diliyerek istirahata çekiliyorum.

Kaynaklar

Blockchain 101 - Foundational Math
Blockchain 101 - Elliptic Curve Cryptography
Modulo Denklik
MathWorl - Elliptic Curve
Learn Cryptography - CryptoCurrency(51 Attack)
Johannes Bauer - ECC
Andrea Corbellini - Elliptic Cure Cryptography - A Gentle Introduction
Andrea Corbellini - Elliptic Cure Cryptography - Finite Fields and Discrete Logarithms
Implementation of Elliptic Curve Digital Signature Algorithm
Elliptic Curve Scalar Multiplaction Calculator
BitcounWiki

Blockchain Eliptik Eğri Şifreleme Algoritması

$
0
0

Merhaba Arkadaşlar,

Matematik tek evresenl dil olarak varoluşumuzdan bu yana yaşamın içerisinde. Onun diğer bilimlere olan pozitif etkisi tartışılamaz. Bugün ulaştığımız noktada teknoloji sınırlarını zorlarken yüz yıllar öncesinden ispat edilmiş pek çok teoremin uygulanabilirliklerine de rastlıyoruz. Doğruyu söylemek gerekrise 1999 yılında ilk işe başladığımdan beri matematik'ten epey uzakta sadece kod yazmaktayım. Belki de bugün .Net Core'un Linux üzerinde koşturulup bir Cloud platformuna taşınması da önemli bir mevzu. Lakin o hayranlık duyduğumuz fikirlerin arkasında, çok fazla ilişmediğimiz(belki de bakmaya korktuğumuz) güçlü bir matematik var. Bende büyük bir cesaretle o fikirlerden birisinin arkasında olan matematiği bir nebze olsun anlamak istedim. Matematik kesin kuralları olan bir dil olduğu içi, yazdığım şeyleri doğru telafüz etmem gerekiyor. Eğer basit bir şekilde anlatabilirsem, konuyu da anlamış sayılırm(Son gün notu: Basitleştiremedi)

Geçtiğimiz iki hafta boyunca neredeyse her gün yarım saatimi ayırdığım ve anlamak için çaba sarf ettiğim bir konu oldu. Blockchain'in kullandığı Elliptic Curve Cryptography(ECC) algoritmasının nasıl çalıştığını öğrenmek. Araştırmalarıma başladığımda olayın içerisinde matematiğin bilgisayar şifreleme teknikleri üzerine kullanılan bir çok teorisine denk geldim. 1993 yılında girdiğim Matematik Mühendisliği bölümünde okurken gördüğüm pek çok konu burada da yer alıyordu. Ama zaman içerisinde hepsini unutmuşum. İkinci ve üçüncü dereceden denklemler, eliptik eğriler, asal sayılar, gruplar, sonlu alanlar(Finite Fields), Fermat'nın küçük teoremi(Little Theorem), modüler aritmetik, ayrık logaritma problemi(discrete logarithm problem), double and add algoritması, euclid vs... Aslında her şey Blockchain sisteminde yer alan eliptik eğri denklemine ait grafik gösterimin, gerçek sayılar ile ifade edileninden çok daha farklı olduğunu öğrenmemle başladı diyebilirim.

Örneğin Bitcoin, secp256k1 isimli ve aşağıdaki eşitlikle ifade edilen eliptik eğri denklemini kullanmakta. secp256k1' deki sec, Standards for Efficent Cryptography anlamına gelirken 256 değeri asal sayının kaç bit olduğunu ifade etmektedir. Sonlara doğru bu kavramları anlayacağım/anlatabileceğim diye umut ediyorum.

y2=x3+7

Gerçek sayılar için olan gösterimi şöyle;

Toplam eleman sayısı asal olacak şekilde oluşturulmuş bir sonlu alan dizilimi için olan gösterimi ise şu şekilde;

Merak uyandırdı değil mi? Öyleyse ilk konumuz ile başlayalım.

Eliptik Eğriler

Eliptik eğri denklemlerine geçmeden önce bir kaç basit denklemi de hatırlamamız lazım. Doğrusal, ikinci dereceden ve üçüncü dereceden denklemleri ve x,y düzlemindeki gösterimlerini görünce sizler de hatırlayacaksınız?

y=ax+b , doğru denklemi

y=ax2+bx+c , parabol denklemi

y=ax3+bx2+cx+d , 3ncü dereceden denklem

ve tabii konumuz olan eliptik eğri denklemi.

y2=x3+ax+b

Yukarıdaki şekilde denkleme ait bir kaç farklı örnek görmektesiniz. Her ne kadar ben çok iyi çizemesemde, özellikle x ekseni özelinde simetriklik olduğunu söyleyebiliriz. Bazı hallerde iki ayrı eğri ve bazı hallerde de bu iki eğrinin birleştiği grafikler söz konusu. y2 den kaynaklı bir durum olduğu aşikar. Eliptik eğrilerin enteresan bir özelliği de vardır. Eğri üzerinde olduğu bilinen iki koordinat söz konusu olduğunda, bu koordinatların x değerleri birbirinden farklı olmak şartıyla, her iki koordinatın toplamından yine eğri üzerine denk düşen 3ncü bir koordinatı bulmamız mümkündür. Bulunuş şekli matematik severler için hayranlık uyandırıcıdır. Aşağıdaki şekille konuyu anlatmaya çalışayım.

Şekilde Mnoktasını bulunuşu ifade edilmektedir. Olay şöyle başlar. Eğri üzerinden bilinen iki nokta referans alınır. x değerleri birbirlerinden farklı olan iki nokta. Bu noktaların üstünden geçen bir doğru çizilir. Çizilen doğru eğriyi 3ncü bir noktada daha kesecektir(örneğimizdeki P noktası) Bu noktanın iz düşümü de simetrik taraftaki bir noktaya denk gelmektedir(örneğimizdeki M3)İşte teoriye göre M1 ve M2 noktalarının toplamı M3 noktasını elde etmemizi sağlamaktadır. Tabii toplam dediğimiz olay biraz daha farklı. Noktayı bulmak için aşağıdaki gibi sıralanmış bir formül takımından yararlanılır.

3ncü noktanın bulunmasında ilk iki noktanın sıralı olması şart değildir. Sadece eğri üzerinde olduğu bilinen iki noktanın üzerinden geçen doğrunun kestiği üçüncü noktanın x düzlemindeki iz düşümü önemlidir.

Eğri denklemimiz : y= x+ ax + b

M1 = (x1,y1) , M2 = (x2,y2), M1 + M2 = (x3,y3)

x1 ve x2 eşit olmadığı sürece

Eğim s = (y2 - y1) / (x2 - x1)

x3 = s2 - x2 - x1

y3 = s(x1 - x3) - y1

Kafamızı çok fazla bulandırmadan 3ncü nokta bulmanın nasıl yapıldığını basit bir örnekle inceleyelim.

y2=x3+5x+7
P1 = (2,5) , P2 = (3,7) => P3 = ?

52= 25 = 23+(5*2)+7 (P1 noktası eğri üzerinde)
72= 49 = 33+(5*3)+7 (P2 noktası eğri üzerinde)

s = (7 - 5) / (3 - 2) = 2 (Eğimi bulduk)
x3 = 22 - 2 - 3 = -1
y3 = 2(2 - (-1) ) - 5 = -1

P3 = (-1,1) 
12= 1 = -13+(5*(-1))+7 (P3 noktası eğri üzerinde)

Şimdi örnekte neler oldu anlamaya çalışalım. Denklem ortada. İki tane noktamız var. Öncelikle bu noktaların eğri üzerinde olup olmadıklarının sağlamasını yapıyoruzSonrasında eğim değerini(s) bulmamız gerekiyor. Eğim bulunduktan sonra bu değerden yararlanarak x3 bilinmeyenini ve x3'ü de işin içerisine katarak y3 değerini hesaplıyor  ve 3ncü noktanın koordinatlarını bulmuş oluyoruz. Son aşamada yine x3,y3 noktasının eliptik eğri üzerinde olup olmadığının sağlamasını gerçekleştiriyoruz. Bunu program kodu ile de deneyimleyebiliriz. Özellikle Python gibi diller bu tip matematiksel işlemler için kolaylıklar sunmakta. 

p1=(2,5)
p2=(3,7)

def isOnCurve(p):
    """    
    p1 egri ustunde mi bakalim
    """
    (x,y)=p1
    return y**2 == x**3+(5*x)+7

def findSlope(p1,p2):
    """
    p1 ve p2den yararlanarak egimi buluyoruz
    """
    (x1,y1)=p1
    (x2,y2)=p2
    s=(y2-y1)/(x2-x1)
    return s

def findThirdPoint(p1,p2,s):
    """
    p1 ve p2den yararlanip 3ncu noktanin bulunmasi
    """
    (x1,y1)=p1
    (x2,y2)=p2
    x3=s**2-x2-x1
    y3=s*(x1-x3)-y1
    return (x3,y3)

print p1,"is on curve?",isOnCurve(p1)
print p2,"is on curve?",isOnCurve(p2)
print findSlope(p1,p2)
print findThirdPoint(p1,p2,findSlope(p1,p2))

Python tarafına aşina olmayanlar için bile okunması oldukça kolay bir kod parçası görmektesiniz. x,y koordinatlarını işaret eden noktaları tuple tipi ile işaret etmekteyiz. Bu bir noktanın x ve y değerlerini taşırken veya elde ederken işlerimizi kolaylaştırmakta. isOnCurve fonksiyonu parametre olarak verilen noktanın eğri üzerinde olup olmadığını kontrol ediyor. findSlope metodu ile tahmin edeceğiniz üzere eğim değerini buluyoruz. findThirdPoint fonksiyonu p1 ve p2 parametrelerinden yararlanılarak p3ün yani 3ncü noktanın bulunmasında kullanılmakta. Kodu Visual Studio Code üzerinde geliştirebilirsiniz. Şahsen ben, öyle yaptım.

Eliptik Eğrilerin Gruplar ile İlişkisi

Eliptik eğriler ile matematik grupları arasında yakın ilişki vardır. Özellikle asallık söz konusu ise. Bunları bir eliptik eğri için düşündüğümüzde şunları söyleyebiliriz. 

  • G'yi noktaların olduğu bir grup olarak düşünürsek iki noktanın toplamı (P1 + P2 = P3) yine G'nin içinde yer alacaktır(Kapalılık özelliği)
  • P1 + P2 + P3 = 0 aynı hat üzerinde 3 nokta söz konusu olduğunda toplam sonucu 0 olarak çıkar(Tabii noktaların hiçbirisi 0 olmayacak)
  • Grubun mutlaka şu eşitliği sağlayan bir birim elemanı vardır ki eliptik eğriler için 0 olduğunu söyleyebiliriz. P1 + 0 = 0 + P1 = P1
  • Her bir noktanın x ekseninde bir simetrisi vardır. 
  • Eğer değişebilirlik ( P1 + P2 = P2 + P1 ) söz konusu ise bu grup Abelian(Değişmeli diyebiliriz) olarak isimlendirilir(Abelian olmanın avantajları nelerdir halen araştırıyorum sevgili okur)

Grup olma özellikleri biraz sonra kriptografinin zorluğunu ortaya koyarken değer kazancak. Bu nedenle eliptik eğri kriptografisine geçmeden önce sonlu alanlara, asal sayılar nezninde de uğramamız gerekiyor.

Sonlu Alanlar

Artık eliptik eğrilerin nasıl bir denklem ile ifade edildiğini biliyoruz. Yazının başında Blockchain tarafından kullanılan denklemin grafiğini hatırlarsanız gerçek sayılar yerine toplam eleman sayısı bir asal sayı ile ifade edilen eliptik eğrinin söz konusu olduğunu belirtmiştik. Peki ne olaki bu sonlu alanlar(Finite Fields) Aşağıdaki gibi ifade edilen bir sayı dizisi olduğunu düşünelim(Aslında bizler için 0dan başlayan 13 elemanlı bir tamsayı dizisi)

F13 = {0, 1, 2, 3, … 12}

Bu dizilimin en önemli yanı 13 elemandan oluşması. 13  asal bir sayı. Dizinin bir diğer önemli özelliği de modüler aritmetik denklik kuramına göre içerideki iki sayının toplamının yine içerideki bir elemanı veriyor olması. Üstelik bu sadece toplama değil, çıkarma, çarpma ve bölme işlemleri için de geçerli bir durum. Sadece bölme işleminde kafaların biraz karışabildiği bir senaryo var ki burada da işin içerisine Fermat'nın Küçük Teorim(Fermat's Little Theorem) girmekte.

Toplama, çıkartma ve çarpma işlemlerine örnekler;

4 + 5 = 9 % 13 = 9 (Dizi içerisinde)
8 + 11 = 19 %13 = 6 (Dizi içerisinde)
8 - 12 = (-4) % 13 = 4 (Dizi içerisinde)
9 - 4 = 5 % 13 = 5 (Dizi içerisinde)

Gelelim bölme işlemine...

2 / 3 = 2 * 3-= 2 * 311 = 354.294 % 13 = 5 (Dizi içerisinde)
3 / 12 = 3 * 12-= 3 * 1211 = 2.229.025.112.064 % 13 = 10 (Dizi içerisinde)

İşlemler biraz tuhaf geldi değil mi? Özellike -1 üs değerinin eşitliğin devamında 13-2 şeklinde ifade edilmesi. Burada az önce bahsettiğimiz küçük teorimin büyük bir önemi var. Fermat'a göre p bir asal sayı, a bir tamsayı ve a ile p aralarında asal(p, a'nın bir çarpanı olamaz) iken

211 - 2 = 2046 % 11 = 0 

gibi bir işlem'den bahsedilebilir. Modüler aritmetik notasyonuna göre ifade şudur.

a≡ a (mod p)

Buradan hareketle teoremin ispatı sırasında kullanılan Euler teoremine göre de

ap-1 ≡ 1 (mod p)

dir. Henüz ispatını araştıramamış olsam da bu denkliklerden yola çıkılarak şu ifadenin de doğru olduğu söylenmekte.

ap-2 ≡ a-1 ≡ 1/a (mod p)

Böylece bir bölme işleminin modüler aritmetik enstürmanlarına göre yine dizi içerisindeki bir elemanı işaret ettiğini görmüş oluyoruz.

Eliptik Eğrideki Ayrık Logaritma Problemi

Gelelim yukarıda anlattıklarımızı kullanarak neler yapabileceğimize bakmaya. Bir eliptik eğri üzerinde bir başlangıç noktası seçtiğimizi düşünelim. P olarak isimlendirelim(Sonradan Generator Point adına kavuşacak) Buna göre P'nin 1 katını, 2katını, 3katını ekleyerek devam edelim. Artık elimizde bir nokta grubu var ve onu şöyle ifade edebiliriz.

{0, P, 2P, 3P, 4P, 5P,... (n-1)P}

Çarpan olarak ele alınan n'nin gizli bir anahtar olduğunu düşündüğümüzde her ne kadar sP=Q değerini bulmak kolay olsa da P ve Q'yi bilip s'yi bulmaya çalıştığımız durumda bu o kadar da kolay olmayacaktır. Çünkü 0 ile n-1 arasındaki tüm olası değerleri göz önüne alıp eşitliğin sağlanıp sağlanmadığını anlamamız gerekir. Bunun sebebi ayrık logaritma problemi ile açıklanmaktadır. 

Discrete Logarithm Problem

Aşağıdaki işlemi düşünelim.

 329 mod 17 ≡ 12

Burada 12 değerine ulaşmak kolay. Fakat soru şu;

3mod 17 ≡ 12

Burada x değerini nasıl bulabiliriz? Aslında 3ün olası üslerini taramak söz konusu eşitlikteki uygun x değerini bulmak için yeterli. Küçük bir asal sayı için bu çok büyük sorun teşkil etmeyecektir. Sorun 17 sayısı yerine çok çok çok büyük bir asal sayı geldiğinde ortaya çıkmaktadır. Teorikte mümkün ama pratiğe dökülmesi için asal değere göre dünyadaki işlemci gücünün tamamına sahip olsak bile çok uzun yıllar sürebilecek bir problem söz konusu(Uzmanların dilinden) 

Tekrar P noktalarından oluşan grubumuza dönelim. Buradaki çarpan hesaplamaları için Double and Add algoritmasından yararlanılabilir.

Double and Add algorithm

Double and Add algoritmasında noktanın çarpanının ikilik sayı sistemindeki ifadesinden yararlanılır. Şöyle başlayalım. 19 asal sayısının ikilik sistemdeki karşılğı 

10011

şeklindedir.

Bunu üssel gösterimle ifade etmek istersek şu eşitliği de yazabiliriz.

19 = 10011 = 1.24 + 0.23 + 0.2+ 1.2+ 1.2

Buna göre bir noktanın 19 ile çarpımını da şu şekilde ifade etmemiz mümkün hale gelir.

19P = 24P + 21P + 20P

Oluşan eşitliğe göre Double and Add algoritması şöyle işletilir.

P noktasını al.

Bunu 2ye katla(double). Bu sayede 2P değerini elde ederiz.

2P yi P ile topla(add) Böylece 21P + 20P değerini yakalarız.

...

Bu şekilde ikiye katlama ve toplama işlemlerinin tekrar edilmesi yoluyla sonuca ulaşabiliriz. Siz örneğin 151 sayısı için bu denkliği sağlamaya çalışarak konuyu pekiştirebilirsiniz. İpucu olarak başlangıçı veriyorum;

151 = 10010111 = 1.27 + 0.26 + 0.2+ 1.2+ 0.2+ 1.2+ 1.2+ 1.2= 2+ 2+ 2+ 2+20

 

Bir nokta grubu için tam sayı ile çarpma işlemini ele aldığımıza göre P grubu için şöyle bir örnek yapalım.

Denklemimiz y= x+ 2x + 3
Sonlu alandaki toplam sayı adedi 17 (asaldır dikkat edin)
Başlangıç noktamız P(3,6)
Buna göre P'yi kendisi ile toplaya toplaya aşağıdaki dizilimi elde edebiliriz. 

0P = 0
1P = (3,6)
2P = (12,2)
3P = (15,5)
4p = (14,2)
5P = (8,2)
6P = (8,15)
7P = (14,15)
8P = (15,12)
9P = (12,15)
10P = (3,11)
11P = (∞,∞)
12P = (3,6)
13P = (12,2)
14P =(15,5)
...

Bir şey dikkatinizi çekti mi? Toplamda denklemi sağlayan 22 adet (x,y) noktası söz konusu iken biz 11 elemanlı bir alt grup elde ettik ve bu grubun tekrar eden bir döngü içerisinde olduğunu görmekteyiz. Buradaki hesaplamalar için aşağıdaki örnek kod parçasını da kullanabiliriz. Fonksiyonları ve kullanım şekillerini anlamaya çalışın. İçeride bir de uzatılmış Euclid algoritması olarak isimlendirilmiş bir kısım var.

import collections

EllipticCurve = collections.namedtuple('EliptikEgri', 'name p a b g')

params = EllipticCurve(
    'y^2=x^3+ax+b', #denklem
    p=17, #toplam nokta sayisi
    a=2, #denklem a degeri
    b=3, #denklem b degeri
    g=(3,6) #generator noktasi
)

def ReverseOfMod(n, p):
    """
    n mod p isleminin tersini dondurur.
    egim hesaplamasi isleminde p1 = p2 ve p1 != p2 durumlari icin gerekli
    """
    if n == 0:
        raise ZeroDivisionError('division by zero')

    if n < 0:
        # n ** -1 = p - (-n) ** -1  (mod p)
        return p - ReverseOfMod(-n, p)

    # Uzatilmis Euclid Algoritmasi uygulanir (Extended Euclidean Algorithm)
    s, old_s = 0, 1
    t, old_t = 1, 0
    r, old_r = p, n

    while r != 0:
        d = old_r // r
        old_r, r = r, old_r - d * r
        old_s, s = s, old_s - d * s
        old_t, t = t, old_t - d * t

    gcd, x, y = old_r, old_s, old_t #gcd-greates common divisor - ebob
    return x % p

def FindNegativePoint(p):
    """
    negatif noktayi bulur
    """

    if p is None:
        return None

    x, y = p
    result = (x, -y % params.p)

    return result


def Add(p1, p2):
    """
    grup yasasindaki kriterlere gore p1+p1 islemini gerceklestirir
    """

    if p1 is None:
        # 0 + p2 = p2 durumu
        return p2
    if p2 is None:
        # p1 + 0 = p1 durumu
        return p1

    x1, y1 = p1
    x2, y2 = p2

    if x1 == x2 and y1 != y2:
        return None

    if x1 == x2:
        # p1==p2 durumu
        m = (3 * x1 * x1 + params.a) * ReverseOfMod(2 * y1, params.p)
    else:
        # p1!=p2 durumu
        m = (y1 - y2) * ReverseOfMod(x1 - x2, params.p)

    x3 = m * m - x1 - x2
    y3 = y1 + m * (x3 - x1)
    result = (x3 % params.p,-y3 % params.p)

    return result


def Multiply(n, p):
    """
    n * P islemini gerceklestirir
    """
    if n < 0:
        return Multiply(-n, FindNegativePoint(p))

    result = None
    nextP = p

    while n:
        if n & 1:
            result = Add(result, nextP)

        nextP = Add(nextP, nextP)

        n >>= 1
    return result


for i in range(0,17):
    print i,Multiply(i,(3,6))

Nokta sahası sonlu uzunlukta ve çok doğal olarak alt grup da öyle. Ancak denklem ve asal sayı değeri dikkatli seçilirse çok büyük bir grubun elde edilmesi söz konusu olabilir. Öyle ki geri çevirlemeye çalışıldığında bu inanılmaz derecede zor olur.

Bitcoin Cephesi(secp256k1)

Onlar Blockchain'in bu kriptografi kuramını göz önüne alarak aşağıdaki parametreleri içeren bir eğri tanımlamışlar. 

Denklem : y2=x3+7
Sonlu alan asal sayı değeri (p) = 2256 -232 - 2-2-2-2- 2- 1 
Giriş noktası G=( 79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 )
Bir gruptaki asal nokta sayısı n = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141

Dikkat edileceği üzere nokta ve asal sayı değerleri oldukça büyük. Bu da ayrık logaritma probleminin getireceği sorunun çözümünü oldukça zorlaştırır nitelikte. Her şeyden önce ortada 256bitlik bir asal sayı var. Bunun bir sonucu da en iyimser tahminle ortada 2256 olası gizli anahtarın olması ki herhangibirini tespit edebilmek için var olanlarından mümkün olduğunca çoğunu bilmek gerekiyor. Bunu anlatmak çok zor ama trilyonlarca yıl alabilecek bir zaman ortaya çıktığı söyleniyor(Teorik olarak)

Peki yazılımcı olarak biz bu değerleri kullanarak ne yapabiliriz? Aslında seçeceğimiz bir private key değeri ile public key üretebilir sonra bu iki anahtar bilgisinden yararlanarak dijital bir imza oluşturarak belgelerimizi kriptolayabiliriz. Bu amaçla kullanılabilecek pek çok kütüphane var. Hatta şu adreste güzel bir kod örneği de bulunmakta. İnceleyip denemenizi öneririm.

Sonuç

Eliptik Eğri denklemi Blockchain ve ondan türeyen pek çok yapı tarafından asimetrik şifre üretilip transaction'ların imzalanması maksadıyla kullanılmakta. Asitmerik şifrelemede public ve private olmak üzere iki anahtar söz konusu. Public Key herkes tarafından görülebilir bir bilgi ama private key tahmin edeceğiniz üzere kişiye özel. Private key değeri kullanılarak public key değerinin elde edilmesi mümkün. Bu değeri elde ederken yukarıdaki eliptik eğri denkleminden yararlanılmakta. Ancak public key değerini kullanarak private key bilgisine oluşturmak en azından önümüzdeki birkaç milyon yüz yıl(belki de fazlası) için mümkün değil. Blockchain bir transaction'ı imzalarken private key ile oluşturulmuş bir hash değeri kullanıyor. Hash bilgisinin geriye döndürülerek private key içeriğinin bulunması zaten mümkün değil lakin public key değerine sahip olan birisi kendi ürettiği private key'leri kullanarak oluşturacağı hash'leri karşılaştırmaya çalışabilir. Lakin burada onu bekleyen şey Eliptik Eğri Dijital Kriptografi Algoritması(Elliptic Curve Digital Signature Algorithm) oluyor; ki bu konu şu an için beni aşmakta. Kaynaklar arasında daha fazla kaybolmadan hatırladığım eski matematik denklemlerimi bir kenara bırakıyor ve hepinize mutlu günler diliyerek istirahata çekiliyorum.

Kaynaklar

Blockchain 101 - Foundational Math
Blockchain 101 - Elliptic Curve Cryptography
Modulo Denklik
MathWorl - Elliptic Curve
Learn Cryptography - CryptoCurrency(51 Attack)
Johannes Bauer - ECC
Andrea Corbellini - Elliptic Cure Cryptography - A Gentle Introduction
Andrea Corbellini - Elliptic Cure Cryptography - Finite Fields and Discrete Logarithms
Implementation of Elliptic Curve Digital Signature Algorithm
Elliptic Curve Scalar Multiplaction Calculator
BitcounWiki

Viewing all 525 articles
Browse latest View live