https://buraksenyurt.com/Burak Selim Şenyurt - GCP2024-01-02T09:38:25+00:00Matematik Mühendisi Bir Bilgisayar Programcısının NotlarıBurak Selim SenyurtBlogEngine.Net Syndication Generatorhttps://buraksenyurt.com/opml.axdBurak Selim SenyurtMatematik Mühendisi Bir Bilgisayar Programcısının Notlarıtr-TRBurak Selim Şenyurt0.0000000.000000https://buraksenyurt.com/post/firebase-cloud-messaging-ile-abonelere-bildirim-yollamakFirebase Cloud Messaging ile Abonelere Bildirim Yollamak2019-08-02T17:48:00+00:00bsenyurt<p><img style="float: right;" src="https://buraksenyurt.com/image.axd?picture=/2019/07/31/friendship.png" alt="" />Servis kapısı açıldığında gözlerini herkesten kaçırıp araca binerken heyecanlı ses tonuyla "Günaydın" diyerek en arka koltuğa geçen kadının ruh hali her yönüyle tanıdık geliyordu. Bir buçuk yıl kadar önce yine bu servise bindiğim ilk gün bende benzer kaygıları hissetmiştim. Oysa hayatımda ilk kez servis binmiyordum.</p>
<p>Ama işte o ilk biniş sırasında söylenen "Günaydın" kelimesi ardından ben ve şoförümüz İhsan Bey dışında kimsenin karşılık vermediği ve onun gözlerini aradığım sırada geçen kısa zaman diliminde aklından geçenleri tahmin ettiğim anlar, en arka koltuğa oturduğunu gördükten sonra toplum psikolojisine ayak uydurup önüme doğru bakmamla son bulmuştu.</p>
<p>Servis şirkete vardıktan sonra her birimiz fabrikanın farklı noktalarına doğru yürümeye başladık. Bizim binamız yolun karşı tarafında kalıyordu ve can güvenliği nedeniyle bir üst geçitten geçilerek ulaşılabiliyordu. Merdivenleri çıkarken onun ne durumda olduğunu bile unutmuşum. Tesadüfen arkamı dönüp baktığımda tek başına ve biraz da şaşkın bir şekilde ne yöne gideceğini anlamaya çalıştığını fark ettim. Yanlış yöne gittiği apaçık ortadaydı. Çelimsizliğinden, gencecik yüzünden ve taşıdığı not defterinden üniversite talebesi olan bir stajyer olduğu biraz da olsa anlaşılıyordu. Geçen yılın aynı vakitlerinde de benzer manzaralar fabrikanın çeşitli sabahlarında yaşanmıştı.</p>
<p>Merdivenleri tekrar indim ve arkasından ona yetişerek "Merhaba...Yeni başladınız sanırım. Nereye gidecektiniz?" dedim. Aynı günün akşamında koltuğuma oturmuş hareket saatini beklerken kapıda yine o tedirgin duruşuyla beliriverdi. İlk adımını attığında yüzündeki gerginlik az da olsa okunabiliyordu. Aklından geçen "iyi akşamlar diyeceğim ve sanırım kimse sallamayacak" düşüncesi bir bulut olup kafasının üzerinden belirmişti. Ama o sabah ki yardımın etkisinde olsa gerek bu kez yüzümü aradı ve görünce hafif bir tebessümle "iyi akşamlar" dedi. "İyi akşamlar. Eee ilk günün nasıl geçti bakalım..." diye karşılık verince bir yanımdaki koltuğa oturdu. Artık daha iyi hissediyordu.</p>
<p>Biraz sonraki derlemeye nasıl giriş yapacağımı bilemediğim günlerden birindeyiz anlayacağınız üzere. O yüzden yakın zamanda başıma gelen bir olayı sizinle paylaşarak başlamak istedim. Kıssadan hisse herkesin kendisine pay çıkaracağını düşünüyorum. Hepimiz stayjer olduk. Onların daha çok farkına varmamız gerektiği konusunda ufak bir hatırlatmam olsun burada.</p>
<p>...</p>
<p>Cumartesi gecesi çalışmalarının <a href="https://github.com/buraksenyurt/saturday-night-works/tree/master/No%2031%20-%20Push%20Notifications%20on%20PWA" target="_blank">31nci örneğinde</a> Firebase Cloud Messaging sistemini kullanarak uygulamalara<em>(örnek özelinde bir PWA programına)</em> nasıl bildirimde bulunulabileceğini anlamaya çalışmışım. Her zaman olduğu gibi örneği WestWorld<em>(Ubuntu 18.04, 64bit)</em> üzerinde geliştirmişim. Şimdi notların üstünden geçme, unutulanları hatırlama ve eksikleri giderme zamanı. Öyleyse gelin notlarımızı derlemeye başlayalım.</p>
<p>Tarayıcı üzerinde yaşayan ve çevrim dışı ya da çok düşük internet hızlarında da çalışabilme özelliğine sahip olan PWA<em>(Progressive Web Applications)</em> uygulamalarının en önemli kabiliyetlerinden birisi de Push Notification'dır. Bu, mobil platformlardan yapılan erişimler düşünüldüğünde oldukça önemli bir nimettir. Uygulamaya otomatik bildirim düşmesi veya arka plan veri güncellemeleri kullanıcı deneyimi açısından bakıldığında değerli bir işlevselliktir. Bu yetenekler uygulama için tekrardan submit operasyonuna gerek kalmadan güncel kalabilmeleri anlamına gelir.</p>
<p>Geliştireceğimiz örnek bir basketbol maçı için güncel haber bilgisinin abone olan uygulamalara gönderilmesi üzerine kurgulanmakta. Burada mesajlaşma servisi olarak Firebase Cloud Messaging altyapısını kullanacağımız için ilgi çekici bir örnek olduğunu ifade edebilirim. Şimdi hazırlıklarımıza başlayabiliriz.</p>
<div>
<div>
<div>
<h2>Ön Hazırlıklar</h2>
<div>İki uygulama geliştireceğiz. Birisi çok sade HTML içeriğine sahip olan PWA uygulaması. İkinci uygulama ise bir servis. Kullanıcıların bildirim servisine abone olma ve çıkma işlemlerinin yönetimi ile bağlı olanlara bildirim yapma görevini<em>(işte bu noktada Firebase Cloud Messaging sisteminden yararlanacak)</em> üstlenecek. İlk olarak basketbol haberlerini takip edeceğimiz basit önyüz uygulamasını geliştirmeye başlayım. Klasör yapısını aşağıdaki terminal komutları ile oluşturabiliriz.</div>
<div>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">mkdir basketkolik
cd basketkolik
touch index.html
touch sworker.js
touch main.js
touch efes_barca.html</pre>
<div>
<div>Ayrıca uygulama testlerini HTTP üzerinden kolayca yapabilmek için serve isimli bir npm paketinden yararlanacağız. Kurulumu için aşağıdaki terminal komutunu kullanmak yeterli.</div>
<div>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">sudo npm install -g serve</pre>
</div>
</div>
</div>
</div>
Kodları yazmaya başlamadan önce Google tarafıyla haberleşme noktasında gerekli olan bir manifesto dosyasının oluşturulması gerekiyor.</div>
<div>
<div>
<h2>Manifesto Dosyası</h2>
<div>
<div>
<div>Manifesto dosyası PWA'nın Firebase tarafında etkinleştirilecek Push Notification özelliği için gereklidir. İçerisinde Sender ID değerini barındırır<em>(ilerde karşınıza çıkacak)</em> ve önyüzün kullandığı main modülü, abonelik başlatılırken bu değeri karşı tarafa iletmekle yükümlüdür. Peki bu dosyayı nasıl üreteceğiz?</div>
<div> </div>
<div>Öncelikle <a href="https://app-manifest.firebaseapp.com/" target="_blank">Firebase</a> kontrol paneline gidilir ve PWA için metadata bilgilerini tutacak bir Web App Manifest dosyası üretilir. Ben örnekte aşağıdaki bilgileri kullandım.</div>
<div> </div>
<div><img src="https://buraksenyurt.com/image.axd?picture=/2019/07/31/credit_1.png" alt="" /></div>
</div>
</div>
</div>
</div>
<div>
<div>Sonrasında Zip dosyasını bilgisayara indirip proje klasörüne açmamız gerekiyor. Manifest.json dosyası ile birlikte images isimli bir klasör de gelecektir. Images klasöründe kendi eklediğimiz active.png dosyasının farklı cihazlar için standartlaştırılmış boyutları yer alır. Bu bilgiler manifest.json dosyasına da konur.</div>
<div> </div>
<div>İşimiz henüz bitmedi! Uygulama için Push Notification özelliğini de etkinleştirmek gerekiyor. Bunun için <a href="https://console.firebase.google.com/" target="_blank">Firebase Console</a> arabirimine gidip yeni bir proje oluşturmalı ve ardından proje ayarlarına<em>(Project Overview -> Project Settings)</em> ulaşıp Cloud Messaging sekmesine gelinmeli <em>(Ben "basketin-cepte-project'" isimli bir proje oluşturdum :P Hayaller başka tabii ama eldeki malzeme şimdilik bu) </em></div>
<div><br />
<div>Bu bölümde proje için oluşturulan Server Key ve Sender ID değerleri yer alır. Az önce bahsedildiği gibi Sender ID değerinin manifest.json dosyasına eklenmesi gerekiyor <em>(gcm_sender_id yazan kısma bakınız)</em></div>
</div>
<h2>PWA Kodları</h2>
<div>Artık basketkolik klasöründeki dosyalarımızı kodlamaya başlayabiliriz. index.html sayfası aslında haber kaynağına aboneliği başlatıp durdurabileceğimiz bir test sahası gibi. Tek dikkat edilmesi gereken nokta manifest dosyası ile bağ kurulmuş olması. Tasarım son derece aptalca yapıldı ama esas amacımız elbette şukela bir görsellik sunmanın dışında push notification kabiliyletlerini deneyimlemek. Index sayfası ile işe başlayalım.</div>
<div>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><!DOCTYPE html>
<html>
<head>
<title>Basketin Cepte</title>
<link rel="manifest" href="manifest.json">
<!--Bunu eklemeyi unutmamak lazım-->
</head>
<body>
<h3>Euroleague Haberleri</h3>
<p><i>Düğmeye bas ve ne olacağına bakalım</i></p>
<div id="divPush">
<img id="buttonPush" width="50px" src="active.png" />
</div>
<script src="main.js"></script>
</body>
</html></pre>
</div>
<div>efes_barca.html isim dosya da şimdilik bildirim alındığında gösterilecek içeriği barındırıyor. Bunun dinamik olduğunu bir düşünsenize. Abone olduktan sonra yeni bir haber geldiğinde efes_barcha benzeri dinamik HTML içerikleri kullanılacak. Tüm uygulamayı tamamladıktan sonra bu tip bir özellikle örneğinizi daha da zenginleştirebilirsiniz.</div>
<div>
<pre class="brush:html;auto-links:false;toolbar:false" contenteditable="false"><!DOCTYPE html>
<html>
<head>
<title>Euroleague'de Efes Farkı</title>
</head>
<body>
<p>Anadolu Efes, kritik maçta Barcelona'yı kendi evinde farklı yenerek...
</p>
</body>
</html></pre>
Sworker modülü aslında Service Worker görevini üstlenmekte. Kodlar arasına katmaya çalıştığım yorumlarla mümkün mertebe neler olduğunu anlamaya ve anlatmaya çalıştım.</div>
<div>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">/*
main.js içerisinde Service Worker olarak bildirimi yapılan dosyamız.
İki olay metodu içerir.
Uygulama herhangibir bildirim aldığında push metodu tetiklenir.
Bildirim almasını sağlamak için yazdığımız PusherAPI servisine talep göndermemiz yeterlidir. http://localhost:8080/news/push gibi.
Bu metod içerisinde showNotification ile bir bildirim penceresi gösterilir.
Pencereye tıklandığındaysa notificationclick isimli olay tetiklenir.
Bu olayın ele alındığı fonksiyonda ise temsilen bir web sayfası içeriği açtırılır.
*/
self.addEventListener('push', function (event) {
console.log('push olayı tetiklendi');
var title = 'Euroleague Haberleri';
var body = {
'body': 'Güncel skor bilgileri için tıklayın',
'tag': 'pwa',
'icon': './images/48x48.png'
};
event.waitUntil(
self.registration.showNotification(title, body)
);
});
self.addEventListener('notificationclick', function (event) {
//TODO: Burada statik bir sayfa içeriği gösterilmesi yerine haber bilgisini servisten alacak kodu ekleyebilirsiniz
console.log('Şimdi bildirime ait sayfa açılacak');
var url = './efes_barca.html';
event.notification.close();
event.waitUntil(clients.openWindow(url));
});</pre>
Bu taraf için son olarak main içeriğini de aşağıdaki gibi geliştirebiliriz.</div>
<div>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">// load aşamasında Service Worker için register işlemi yapılır
window.addEventListener('load', e => {
if (!('serviceWorker' in navigator)) {
console.log('Service worker desteklenmiyor');
return;
}
navigator.serviceWorker.register('sworker.js')
.then(function () {
console.log('Servis Worker kaydoldu');
})
.catch(function (err) {
console.log('Hımm...Sanırım bir hata oluştu : ', err);
});
});
// Push Notification durumunu günceller
// Aktifse active.png, değilse passive.png
function updateStatus(status) {
divPush.dataset.checked = status; // statu bilgisini set et
if (status) { //true ise aktif
buttonPush.src = "./assets/active.png";
}
else { // değilse pasif olan ikonu göster
buttonPush.src = "./assets/passive.png";
}
}
function isPushNotifyEnabled() {
if (Notification.permission === 'denied') {
alert('Kullanıcı Push Notification hizmetini bloklamış');
return;
}
if (!('PushManager' in window)) {
alert('Üzgünüm ama Push Notification bu tarayıcı modelinde desteklenmiyor');
return;
}
/*
Kullanıcı Push Notification'ı engellememiş ve tarayıcı da bunu destekliyorsa
buraya geliriz.
Eğer Service Worker hazır ve kaydolmuşsa abonelik yönetimine başlanabilir.
*/
navigator.serviceWorker.ready.then(function (reg) {
reg.pushManager.getSubscription() //abone ol
.then(function (subs) { // abonelik durumuna göre statüleri güncelle
if (subs) {
updateStatus(true);
}
else {
updateStatus(false);
}
})
.catch(function (err) { // bir hata oluştuysa tarayıcı console'una hata basıyoruz.
console.error('Bir hata oluştu : ', err);
});
});
}
// Uygulamayı Push Notification hizmetine abone etmek için
function subscribe() {
navigator.serviceWorker.ready
.then(function (reg) {
if (!reg.pushManager) {
alert('Tarayıcı Push Notification hizmetini desteklemiyor');
return false;
}
reg.pushManager.subscribe(
{ userVisibleOnly: true }
).then(function (subs) {
console.log('Abonelik başladı.');
console.log(subs);
sendSubsID(subs); // Abonelik IDsini REST servise gönderen metodu çağırdık
updateStatus(true);
}).catch(function (err) {
updateStatus(false);
console.error('Abone olma işlemi sırasında hata oluştu: ', err);
});
});
}
// Abonelikten çıkartmak için
function unsubscribe() {
navigator.serviceWorker.ready
.then(function (reg) {
reg.pushManager.getSubscription()
.then(function (subs) {
if (!subs) {
alert('Abonelikten çıkılamıyor yahu :S');
return;
}
subs.unsubscribe()
.then(function () {
console.log('Abonelikten çıkıldı');
console.log(subs);
deleteSubsID(subs); // Aboneliği silerken ID'yi çıkartacak olan REST servis metodunu çağırdık
updateStatus(false);
})
.catch(function (err) {
console.error(err);
});
})
.catch(function (err) {
console.error('Abonelikten çıkarken bir hata oluştu : ', err);
});
})
}
// Sunucuya Subscription ID bilgisini göndermek için kullanıyoruz
// Bunun için yazdığımız PusherAPI node servisini kullanıyoruz
function sendSubsID(subscription) {
var id = subscription.endpoint.split('gcm/send/')[1];
fetch('http://localhost:8080/subscribers', {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ subscriptionid: id })
});
}
function deleteSubsID(subscription) {
var id = subscription.endpoint.split('gcm/send/')[1];
fetch('http://localhost:8080/subscribers/' +
id, {
method: 'delete',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
}
// div ve button elementlerini alıyoruz
var divPush = document.getElementById('divPush');
var buttonPush = document.getElementById('buttonPush');
/*
div elementinde mouse ile tıklandığı zaman gerçekleşecek olayı
dinleyecek bir listener bildirimi yaptık
*/
divPush.addEventListener('click', function () {
//console.log('Click');
if (divPush.dataset.checked === 'true') {
//console.log('Unsubscribe');
unsubscribe();
}
else {
//console.log('Subscribe');
subscribe();
}
});
isPushNotifyEnabled();</pre>
</div>
<h2>İlk Test</h2>
<div>Kodları tamamladıktan sonra kısa bir test ile push notification hizmetinin çalışıp çalışmadığı hemen kontrol edilebilir. Bunun için terminalden</div>
<div>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">serve</pre>
komutunu verip uygulamayı ayağa kaldırmamız yeterli. Eğer aşağıdaki ekran görüntülerindekine benzer sonuçlar elde edebiliyorsak REST API uygulamasını yazmaya başlayabiliriz.</div>
<div> </div>
<div><img src="https://buraksenyurt.com/image.axd?picture=/2019/07/31/credit_2.png" alt="" /></div>
<div> </div>
<div><img src="https://buraksenyurt.com/image.axd?picture=/2019/07/31/credit_3.png" alt="" /></div>
<div>
<blockquote>
<div>Uygulama, Push Notification hizmeti için abone olunurken benzersiz bir ID değeri alır. Firebase Cloud Messaging sistem bu değeri kullanarak kime bildirim yapılacağını bilebilir.</div>
</blockquote>
<h2>REST API Uygulamasının Yazılması</h2>
<div>
<div>Abone olan uygulamaların ID bilgilerini yönetmek için Node.js tabanlı bir REST servisi yazabiliriz. Servis temel olarak PWA'nın FCM ile olan iletişiminde devreye girmektedir. Hem abonelik yönetimi hem de istemcilere bildirim yapılması ki bunu tek başına yapmayacaktır. Service Worker dolaylı olarak FCM üzerinden bu servisle yakın ilişki içerisindedir. </div>
<div> </div>
<div>Bu servisi ayrı bir klasörde projelendirmek iyi olur. Pek tabii node.js tarafında REST Servisi yazımını kolaylaştırmak için bazı paketlerden destek alınabilir. express dışında HTTP mesajlarındaki gövdeleri kolayca ele almak için body-parser, servisin Firebase Cloud Messaging ile konuşabilmesini sağlamak amacıyla da fcm-node paketi kullanılablir. morgan modülünü ise sunucu tarafındaki HTTP trafiğini loglamak için değerlendirebiliriz. Aşağıdaki terminal komutları ile klasör ağacını oluşturup server dosyasını kodlayarak derlememize devam edelim.</div>
</div>
<div>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">mkdir PusherAPI
cd PusherAPI
touch server.js
sudo npm install express body-parser fcm-node morgan</pre>
server modülü</div>
<div>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var FCM = require('fcm-node');
var morgan = require('morgan');
app.use(morgan('combined'));
// Firebase Cloud Messaging nesnesini örneklerken
// bizim için üretilen server key değerini veriyoruz
var fcm = new FCM('bu kod sizde var...');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(function (req, res, next) {
// CORS problemi yaşamamak için gerekli header tanımlamaları yapılır
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
next();
})
//abonelerin ID bilgilerini tutacağımız array.
// Bunun yerine daha kalıcı bir repository tercih edilebilir
var subscribers = []
// Web uygulaması HTTP Post ile subscriptionID gönderdiğinde çalışan metod
app.post('/subscribers/', function (req, res) {
// Mesaj gövdesinden abonelik bilgisini almaya çalışr
if (!req.body.hasOwnProperty('subscriptionid')) {
res.statusCode = 400;
res.send('Gönderilen bilgilerde eksik veya hata var');
return;
}
// bulduysan diziye ekle ve başarılı olduğu bilgisini ilet
subscribers.push(req.body.subscriptionid)
res.statusCode = 200; //HTTP 200 Ok basıyoruz
res.send('ID alındı');
});
// Web uygulaması abonelikten çıkarken HTTP Delete ile çağırdığı metod
app.delete('/subscribers/:id', function (req, res) {
// dizideki indis değerini URLden gelen id değerine göre bul
const index = subscribers.indexOf(req.params.id)
// varsa diziden çıkart
if (index !== -1) {
subscribers.splice(index, 1)
}
res.statusCode = 200;
res.send('ID silindi');
});
/*
Abonelere bildirim göndermek için tetiklenen REST metodu
localhost:8080/news/push çağrısı geldiğinde çalışır.
*/
app.get('/news/push', function (req, res) {
// Aboneler için mesaj hazırlanır
var message = {
registration_ids: subscribers,
collapse_key: 'i_love_this_game',
};
// Firebase Cloud Messaging üzerinden mesaj gönderilir
fcm.send(message, function (err, response) {
if (err) {
console.log(err)
} else {
console.log("Mesaj başarılı bir şekilde gönderildi: ", response);
}
});
res.sendStatus(200);
});
app.listen(8080);
console.log('Pusher API servisi 8080 üstünden dinlemede');</pre>
</div>
<h2>Çalışma Dinamikleri</h2>
</div>
<div>
<div>Uygulamanın çalışma dinamiklerini anlamak oldukça önemli. Index.html olarak düşündüğümüz web uygulamamız çalıştırıldığında iki aksiyonumuz var. Basketbol topuna basıp bir abonelik başlatmak veya tekrar basarak aboneliği durdurmak.</div>
<br />
<div>Abonelik başlatıldığında FCM benzersiz bir ID değeri üretir ve bunu PusherAPI servisi kendisine gelen çağrı ile kayıt altına alır<em>(diziye eklediğimiz yer)</em> Sonraki herhangi bir t zamanında PusherAPI servisinin abonelere bildirim gönderen HTTP metodu tetiklenirse, Firebase Cloud Messaging devreye girer ve dizideki ID bilgilerini kullanarak abonelerine bildirimde bulunur. Bildirimler web uygulaması tarafındaki Service Worker<em>(sworker.js)</em> tarafından push olayıyla yakalanır. Push olayı şimdilik sadece statik bir sayfa gösterimi yapmakta ki aslında asıl içeriği yine servis üstünden veya web aracılığıyla başka bir adresten aldırabiliriz.</div>
<h2>Çalışma Zamanı(Development Ortamı)</h2>
<div>Testler için PWA ve servis tarafını ayrı ayrı çalıştırmalıyız.</div>
<div>
<div>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">serve</pre>
terminal komutu ile web uygulamasını</div>
</div>
<div>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">node server.js</pre>
ile de REST servisini başlatabiliriz. Aboneliği başlattıktan sonra http://localhost:8080/news/push adresine talepte bulunursak bir bildirim mesajı ile karşılaşırız<em>(sworker daki push olayı tetiklenir)</em> Aynen aşağıdaki ekran görüntüsünde olduğu gibi.</div>
<div> </div>
<div><img src="https://buraksenyurt.com/image.axd?picture=/2019/07/31/credit_4.png" alt="" /></div>
<div> </div>
<div>Bildirim kutusuna tıklarsak statik olarak belirlediğimiz sayfa açılacaktır<em>(yani notificationclick olayı tetiklenir)</em></div>
<div> </div>
<div><img src="https://buraksenyurt.com/image.axd?picture=/2019/07/31/credit_5.png" alt="" /></div>
<h2>PWA ve Service Uygulamalarının Firebase Hosting'e Alınması</h2>
<div>Her iki uygulamada local geliştirme ortamında gayet güzel çalışıyor. Ancak bunu anlamlı hale getirmek için her iki ürünü de Firebase üzerine alıp genel kullanıma açmamız lazım. Web uygulamasını Firebase Hosting ile REST servisini de Firebase Function ile yayınlamalıyız. Bu işlemler için firebase-tools aracına ihtiyacımız olacak. Terminalden aşağıdaki komutu kullanarak ilgili aracı sisteme yükleyebiliriz.</div>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">sudo npm install -g firebase-tools</pre>
<h3>Basketkolik'in Dağıtımı</h3>
<div>Yeni bir dağıtım klasörü oluşturmalı, initializion işlemini gerçekleştirip basketkolik uygulama kodlarını oluşan public klasörü içerisine atmalıyız. Ardından üzerinde çalışacağımız projeyi seçip deploy işlemini yapabiliriz. Bu işlemler için aşağıdaki terminal komutlarından yararlanılabilir.</div>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">mkdir dist
cd dist
firebase init
cp -a ../basketkolik/. ./public/
firebase use --add basketin-cepte-project
firebase deploy</pre>
<div>firebase init işleminde bize bazı seçenekler sunulacaktır. Burada aşağıdaki görüntüde yer alan seçimlerle ilerleyebiliriz. En azından ben öyle yaptım.</div>
<div> </div>
<div><img src="https://buraksenyurt.com/image.axd?picture=/2019/07/31/credit_6.png" alt="" /></div>
<div> </div>
<div>Eğer dağıtım işlemi başarılı olursa aşağıdaki ekran görüntüsündekine benzer sonuçlar elde edilmelidir.</div>
<br />
<div><img src="https://buraksenyurt.com/image.axd?picture=/2019/07/31/credit_7.png" alt="" /></div>
<h3>PusherAPI servisinin Dağıtımı</h3>
<div>Hatırlanacağı üzere web uygulaması bir REST Servisi yardımıyla FCM sistemini kullanıyordu. PusherAPI isimli uygulama, Fireabase tarafı için bir Function anlamına gelmektedir<em>(Serverless App olarak düşünelim)</em> Ölçeklenebilirliği, HTTPS güvenliği, otomatik olarak ayağa kalkması gibi bir çok iş Google Cloud ortamı tarafından ele alınır. Şimdi aşağıdaki terminal komutu ile fonksiyon klasörünü oluşturalım <em>(dist klasörü içerisinde çalıştığımıza dikkat edelim)</em></div>
<div>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">firebase init functions</pre>
</div>
<div>Yine bazı seçenekler karşımıza gelecektir. Burada gelen sorulara şöyle cevaplar verebiliriz;</div>
<ul>
<li>Dil olarak Javascript seçelim.</li>
<li>ESLint kullanımına Yes diyelim.</li>
<li>npm dependency'lerin kurulmasına da Yes diyelim ki uygulamanın gereksinim duyduğu node paketleri de yüklensin.</li>
</ul>
<div>Devam eden adımda functions klasöründeki index dosyasının içeriğini PusherAPI'deki server içeriği ile değiştirmeliyiz. Ancak bu kez express'in firebase-functions ile kullanılması gerekiyor. İhtiyacımız olan express, body-parser ve fcm-node paketlerini üzerinde çalıştığımız functions klasörü içinede de yüklemeliyiz. Son olarak dist klasöründeki firebase.json dosyasına rewrites isimli yeni bir bölüm ekleyip fonksiyonumuzu deploy edebiliriz.</div>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">cd functions
sudo npm install express body-parser fcm-node
firebase deploy</pre>
<div>Yapmamız gereken bir şey daha var. Web uygulamasının kullandığı main dosyasının içeriğini, yeni eklediğimiz google functions ile uyumlu hale getirmek. Tahmin edileceği üzere gidilen servis adreslerini, oluşturulan firebase proje adresleri ile değiştirmemiz lazım <em>(dist/public/main.js içeriğini kontrol edin) </em>Web uygulamasındaki bu değişikliği Cloud ortamına taşımak içinse public klasöründeyken yeniden bir deploy işlemi başlatmamız yeterli olacaktır.</div>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">firebase deploy</pre>
<h2>Çalışma Zamanı<em>(Production Ortamı)</em></h2>
<div>Uygulama artık https://basketin-cepte-project.firebaseapp.com/ adresinden yayında <em>(En azından bir süre için yayındaydı ki aşağıdaki ekran görüntüsü de bunun kanıtıdır)</em></div>
<div> </div>
<div><img src="https://buraksenyurt.com/image.axd?picture=/2019/07/31/credit_8.png" alt="" /></div>
<h2>Ben Neler Öğrendim?</h2>
<div>Bu çalışmanın da bana kattığı bir çok şey oldu elbette. Özellikle bir uygulamaya uzak sunuculardan bildirim yollanması ve bunun abonelik temelli yapılması merak edip öğrenmek istediğim konulardan birisiydi. İşin içerisine basit bir PWA modeli de ekleyince çalışma ilgi çekici bir hal almıştı. Yapılan hazırlıklar düşünüldüğünde aslında bizi çok fazla yormayacak bir geliştirme süreci olduğunu ifade edebiliriz. Derlemeyi sonlandırmacan önce yanıma kar kalanların neler olduğunu aşağıdaki maddeler ile özetleyebilirim.</div>
<ul>
<li>Firebase Cloud Messaging<em>(FCM)</em> sisteminin kabaca ne işe yaradığını</li>
<li>PWA uygulamasının FCM ile nasıl haberleşebileceğini</li>
<li>Abone olan istemciye bildirimlerin nasıl gönderilebileceğini</li>
<li>Service Worker üzerindeki push ve notificationclick olaylarının ne anlama geldiğini</li>
<li>serve paketinin kullanımını</li>
<li>firebase terminal aracı ile deployment işlemlerinin nasıl yapıldığını</li>
<li>Web uygulaması ve Functions'ın Google Cloud tarafından bakıldığında farklılıklarını</li>
</ul>
<div>Bu arada proje büyük ihtimalle Google platformundan kaldırılmıştır. Malum istenmeyen bir yüklenme sonrası yüklü bir fatura ile karşılaşmamak adına küçük bir tedbir olduğunu ifade edebilirim. O nedenle kendiniz başarmaya çalışırsanız daha kıymetli olacaktır. Konseptin basketbol olması önemli değil. Abonelerinize bildirimlerde bulunacağınız herhangi bir senaryo olması yeterli olur. Bildirim yapan servisi de planlanmış bir düzeneğe bağlayabiliriz. Söz gelimi günün belirli anlarında bir konu ile ilgili bildirimlerin yönlendirilmesi işini üstlenebilir. Böylece geldik bir <a href="https://github.com/buraksenyurt/saturday-night-works" target="_blank">saturday-night-works</a> macerasının daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</div>
</div>
</div>
</div>2019-08-02T17:48:00+00:00firebasegoogle cloud platformprogressive web apppwapush notificationnodenode.jsexpressrest apifcmfcm-nodeservice workergoogle cloud functionsjavascriptnpmbsenyurtTarayıcı üzerinde yaşayan ve çevrim dışı ya da çok düşük internet hızlarında da çalışabilme özelliğine sahip olan PWA(Progressive Web Applications) uygulamalarının en önemli kabiliyetlerinden birisi de Push Notification'dır. Bu, mobil platformlardan yapılan erişimler düşünüldüğünde oldukça önemli bir nimettir. Arka planda içerik güncelleme (updating) özelliği de bir diğer önemli kabiliyettir. Bu yetenekler uygulama için tekrardan submit operasyonuna gerek kalmadan güncel kalabilmek anlamına da gelir.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=2829bfa0-4bdc-4425-a1ff-1ab2e1cbbb9d3https://buraksenyurt.com/trackback.axd?id=2829bfa0-4bdc-4425-a1ff-1ab2e1cbbb9dhttps://buraksenyurt.com/post/firebase-cloud-messaging-ile-abonelere-bildirim-yollamak#commenthttps://buraksenyurt.com/syndication.axd?post=2829bfa0-4bdc-4425-a1ff-1ab2e1cbbb9dhttps://buraksenyurt.com/post/google-cloud-fonksiyonlarini-firebase-ile-birlikte-kullanmakGoogle Cloud Fonksiyonlarını Firebase ile Birlikte Kullanmak2019-06-21T07:00:00+00:00bsenyurt<p><img style="float: right;" src="https://buraksenyurt.com/image.axd?picture=/2019/05/27/sabihagokcen.png" alt="" />Google'ın Doodle hizmetini takip ediyor musunuz bilemiyorum ancak ben zaman zaman orada hazırlanmış ikonik görsellerden harika hikayelere gidiyorum. Bu seferki yazının derlemesi sırasında da yolum bir şekilde onunla kesişti ve girişte kimden bahsedebilirim derken havacılılk tarihinin en önemli isimlerinden olan Türkiye'nin ilk kadın pilotu Sabiha Gökçen'i<em>(22 Mart 1913 - 22 Mart 2001)</em> anmaya karar verdim.</p>
<p>Amerikan Hava Kurmay Koleji'nin 1996 yılında Maxwell Hava Üssünde yapılan töreninde Dünya tarihine adını yazdıran 20 havacıdan birisi olarak ödül alan Sabiha Gökçen'in tarihde iz bırakan başarıları saymakla bitmez elbette. Lakin diğer pek çok başarısının yanında bu en çok dikkatimi çekenlerden birisiydi. İçindeki uçma arzusu ve sevgisi öyle büyük olmalı ki Fransız pilot Daniel Acton ile son uçuşunu yaptığında 83 yaşındaydı. Türk Hava Kurumu Türkkuşu'nda Başöğretmen olarak görev aldı ve 1955 yılına kadar bir çok değerli pilotun yetişmesine ön ayak oldu.</p>
<p>Arada bir sizde doodlelayın derim. Bazen çok değerli bilgilere ulaşabiliyoruz. Gelelim Google ile ne işimiz olduğuna<em>(Hoş onsuz hareket ettiğimiz bir günümüz de yok)</em> Bu kez <a href="https://github.com/buraksenyurt/saturday-night-works" target="_blank">Saturday-Night-Works</a> birinci fazdan <a href="https://github.com/buraksenyurt/saturday-night-works/tree/master/No%2027%20-%20Google%20Cloud%20Functions%20with%20Firebase" target="_blank">27 numaralı örneğin derlemesi</a> ile karşınızdayım. Konumuz Google Cloud Platform üzerinden Firebase tabanlı bir bulut fonksiyon sunmak.</p>
<p>Bulut çözümlerin sunduğu imkanlardan birisi de sunucu oluşturma, barındırma, yönetme gibi etkenleri düşünmemize gerek kalmayacak şekilde uygulama geliştirme ortamları sağlamaları. Bazen bulut platform üzerinde tutulan bir veri tabanı ile konuşan servis kodlarını yine o platformun sunucularında barındırmak suretiyle hizmet sunarız. Söz gelimi Google'ın Firebase veri tabanı ve onu kullanan servis tabanlı fonksiyonları Google Cloud Platform üzerinde konuşlandırabiliriz. Bu örnekteki amacımsa Firebase ile ilişkili bir uygulama servisini Google Cloud Platform üzerinde fonksiyonlaştırabilmekmiş. Her zaman olduğu gibi örneği WestWorld<em>(Ubuntu 18.04, 64bit)</em> üzerinde geliştirmişim. Öyleyse gelin notlarımızı derlemeye başlayalım.</p>
<p>Örnekte Firebase'in Realtime Database seçeneği kullanılmakta. Veriyi JSON tipinde tutan bir NoSQL sistemi olarak düşünülebilir. Veri, bağlı olan tüm istemciler için gerçek zamanlı<em>(realtime)</em> olarak eşlenir. Dahası, istemci uygulama kapansa bile veriyi hatırlar. Cloud-Hosted bir veri tabanıdır. Bir başka deyişle veri tabanı sunucusu google üzerinde durmaktadır. Özellikle Cross-Platform tipinden uygulamalar söz konusuysa<em>(iOS, Android, Javascript veya Typescript fark etmez)</em> tüm bağlı istemcilerle aynı verinin senkronize olarak paylaşılmasını sağlamak gibi önemli bir özelliği vardır. Diğer yandan söz konusu Realtime Database ürünü dışında Cloud Firestore isimli daha önceden üzerinden durup düşündüğümüz bir veri tabanı modeli daha vardır. Firebase'in orjinal veri tabanı olan Realtime modelinin daha geliştirilmiş bir versiyonu olarak düşünebiliriz. Her iki ürün arasındaki farklılıkları kabaca aşağıdaki gibi özetleyebiliriz.</p>
<ul>
<li>Realtime modelinde veri JSON ağaç yapısı şeklinde saklanırken Firestore'da koleksiyon biçiminde organize edilmiş dokümanlar söz konusudur<em>(Firestore, Mongo'yu hatırlattı burada bana)</em></li>
<li>Firestore özellikle karmaşık ve hiyerarşik veri kümelerini ölçeklerken Realtime modele göre daha başarılıdır.</li>
<li>Realtime veri tabanı iOS ve Android gibi mobil platformlar için çevrim dışı(<em>offline)</em> çalışma desteği sunar. Firestore buna ek olarak Web tabanlı istemciler için de offline çalışma desteği sağlar.</li>
<li>Sıralama ve filtreleme imkanları Cloud Firestore'da Realtime modeline göre çok daha geniştir.</li>
<li>Firestore'da bir transaction tamamlanıncaya kadar otomatik olarak tekrar ve tekrar denenir.</li>
<li>Realtime veri tabanı modelinde ölçekleme için Sharding uygulanması gerekirken Firestore'da bu iş otomatik olarak yapılır.</li>
</ul>
<p>Ben uygulaması çok daha basit olduğundan Realtime Database modelini tercih ettim.</p>
<h2>İlk Hazırlıklar</h2>
<p>Her şeyden önce Google Cloud Platform üzerinde bir hesabımızın olması lazım. Hesabımız ile login olduktan sonra <a href="https://console.firebase.google.com/" target="_blank">Firebase Console adresine</a> gidip bir proje oluşturacağız. Söz gelimi project-new-hope gibi bir isimle...</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/05/27/credit_1.png" alt="" /></p>
<p>Projeyi komut satırından yönetebilmek önemli. Nitekim yazdığımız kodları kolayca deploy edebilmeliyiz. Bu nedenle Firebase CLI<em>(Command Line Interface)</em> aracına ihtiyacımız var. Kendisini npm ile aşağıdaki gibi yükleyebiliriz<em>(Dolayısıyla sistemimizde node ve npm yüklü olmalıdır)</em></p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">npm install -g firebase-tools</pre>
<p>Yükleme işlemi başarılı olduktan sonra proje ile aynı isimde bir klasör oluşturup, içerisinde sırasıyla login ve functions komutlarını kullanarak ilerleyebiliriz. Bu komutlarla Firebase ortamına giriş yapma ve projenin başlangıç iskeletinin oluşturulması işlemleri yapılmaktadır.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">mkdir project-new-hope
cd project-new-hope
firebase login
firebase init functions</pre>
<p>Login işlemi sonrası arabirim bizi tarayıcıya yönlendirecek ve platform için giriş yapmamız istenecektir. Başarılı login sonrası tekrardan console ekranına dönüş yapmış oluruz.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/05/27/credit_2.png" alt="" /></p>
<p>init functions çağrısı ile yeni bir google cloud function oluşturma işlemine başlanır. Dört soru sorulacaktır<em>(En azından çalışmanın yapıldığı tarih itibariyle böyleydi)</em> Projeyi zaten Firebase Console'unda oluşturmuştuk. Klasör adını aynı verdiğimiz için varsayılan olarak onu kullanacağını belirtebiliriz. Dil olarak Typescript ve Javascript desteği sorulmakta ki ben ikincisi tercih ettim. Üçüncü adımda <a href="https://eslint.org/" target="_blank">ESLint</a> kullanıp kullanmayacağımız soruluyor. Şimdilik 'No' seçeneğini işaretleyerek ilerlenebilir ancak gerçek hayat senaryolarında etkinleştirmek iyi bir fikirdir.<em> (İleriye yönelik problem yaratabilecek olası kod hatalarının önceden tespitinin kritikliği sebebiyle)</em> Projenin bağımlılık duyduğu npm paketleri varsa bunların install edilmesini de istediğimizden son soruda 'Yes' seçminini yapmalıyız.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/05/27/credit_3.png" alt="" /></p>
<p>Komut çalışmasını tamamladıktan sonra aşağıdaki klasör yapısının oluştuğunu görebiliriz.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/05/27/credit_4.png" alt="" /></p>
<p>Bundan sonra index.js dosyası ile oynayıp örnek bir dağıtım<em>(deployment)</em> işlemi gerçekleştirebiliriz de. Index sayfasında yorum satırı içerisine alınmış bir kod parçası bulunmaktadır. Bu kısmı açarak hemen <em>Hello World</em> sürecini işletmemiz mümkün. Ama bunun için, yapılan değişiklikleri platforma almamız lazım. Aşağıdaki terminal komutu ile bunu sağlayabiliriz. Örnekteki amacımıza göre sadece fonksiyonların taşınması söz konusudur.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">firebase deploy --only functions</pre>
<h2>Sorun Yaşayabiliriz</h2>
<p>Yukarıdaki terminal komutunu denediğimde aktif bir proje olmadığına dair bir hata mesajı aldım ve deployment işlemi başarısız oldu. Bunun üzerine önce aktif proje listesine baktım<em>(firebase list)</em> ve sonrasında <em>use --add</em> ile tekrardan proje seçimi yaptım. Bir alias tanımladıktan sonra<em>(ki her nedense proje adının aynısını vermişim :S)</em> tekrardan deploy işlemini denedim. Bu seferde sadece fonksiyon olarak dağıtım yapmak istediğimi belirtmediğim için başka bir hata aldım. Nihayetinde çalıştırdığım terminal komutu işe yaradı ve proje GCP'a deploy edildi.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">firebase list
firebase use --add
firebase deploy --only functions</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/05/27/credit_5.png" alt="" /></p>
<p>Firebase Dashboard'una gittiğimizde helloworld isimli API fonksiyonunun<em>(ki index.js dosyasından export edilen metodumuzdur)</em> eklenmiş olduğunu görebiliriz.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/05/27/credit_6.png" alt="" /></p>
<p>Çalışmanın bu ilk yalın versiyonunda Google'ın index.js içerisine koyduğu yorum satırları kaldırılarak bir deneme yapılmıştır. Bu taşıma işlemi sonrası Firebase tarafında üretilen fonksiyona ait API adresini aşağıdaki gibi curl ile çağırdığımızda 'Hello from Firebase!' yazısını görebiliriz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">curl -get https://us-central1-project-new-hope.cloudfunctions.net/helloWorld</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/05/27/credit_7.png" alt="" /></p>
<h2>Kod Tarafı</h2>
<p>Asıl işi yapan örneğimiz ise basit bir REST hizmeti. POST ve GET mesajlarını destekleyen metotlar içeriyor ve temel olarak veri ekleme ve listeleme fonksiyonelliklerini sağlıyor. Arka planda Firebase veri tabanının Realtime çalışan modelini kullanıyor. Arka plandan kastımız GCP üzerindeki Firebase veri tabanı. Yani kendi makinemizde geliştirdiğimiz bir API servisini, firebase veri tabanını kullanacak şekilde GCP üzerinde konuşlandırmış oluyoruz. İkinci örnek için gerekli bir kaç npm paketi var. REST<em>(Representational State Transfer)</em> modelini node tarafında kolayca kullanabilmek için express ve CORS<em>(Cross-Origin Resource Sharing)</em> etkisini rahatça yönetebilmek için cors :D Aşağıdaki terminal komutları ile onları projemize ekleyebiliriz.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">npm install --save express cors</pre>
<blockquote>
<p>İlgili paketleri functions klasöründeyken yüklememiz gerekiyor. Nitekim deploy sırasında bu JSON dosyasındaki paket bilgileri, GCP tarafında da yüklenmeye çalışacak. Dolayısıyla GCP'nin, kendi ortamında kullanacağı paketlerin neler olduğunu bilmesi lazım.</p>
</blockquote>
<p>index.js dosyasına ait kod içeriğini aşağıdaki gibi geliştirebiliriz.</p>
<pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false">const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors');
const admin = require('firebase-admin');
admin.initializeApp();
const app = express();
app.use(cors()); //CORS özelliğini express nesnesi içine enjekte ettik
// HTTP Get çağrısı gelmesi halinde çalışacak metodumuz
app.get("/", (req, res) => {
return admin.database().ref('/somedata').on("value", snapshot => {
// HTTP 200 Ok cevabı ile birlikte somedata içeriğini döndürüyoruz
return res.status(200).send(snapshot.val());
}, err => {
// Bir hata varsa HTTP Internal Server Error mesajı ile birlikte içeriğini döndürüyoruz
return res.status(500).send('There is something go wrong ' + err);
});
});
// HTTP Post çağrısını veritabanına veri eklemek için kullanacağız
app.post("/", (req, res) => {
const payload = req.body; // gelen içeriği bir alalım
// push metodu ile veriyi yazıyoruz.
// işlem başarılı olursa then bloğu devreye girecektir
// bir hata oluşması halinde catch bloğu çalışır
return admin.database().ref('/somedata').push(payload)
.then(() => {
// HTTP 200 Ok - yani işlem başarılı oldu diyoruz
return res.status(200).send('Eklendi');
}).catch(err => {
// İşlem başarısız oldu
// HTTP 500 Internal server error ile hata mesajını yollayabiliriz
return res.status(500).send('There is something go wrong ' + err);
});
});
// Servisten dışarıya açtığımız fonksiyonlar
// somedata fonksiyonumuz için app isimli express nesnemiz ve doğal olarak Get, Post metodları ele alınacak
exports.somedata = functions.https.onRequest(app);
// Servis hayatta mı metodumuz :P
// Ping'e Pong dönüyorsa yaşıyordur deriz en kısa yoldan.
exports.ping = functions.https.onRequest((request, response) => {
response.send("Pong!");
});</pre>
<p>Kod nihai halini aldıktan sonra tekrardan dağıtım işlemi yapılmalıdır.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">firebase deploy --only functions</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/05/27/credit_8.png" alt="" /></p>
<p>Dağıtım işlemi sonrasında somedata ve ping referans adresli endpoint bilgilerini dashboard üzerinde görebilmemiz gerekiyor.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/05/27/credit_9.png" alt="" /></p>
<p>Şimdi somedata fonksiyonunun Post metodunu kullanarak bir kaç örnek veri girişi yapalım. Postman gibi bir araçtan yararlanarak bu işlemleri kolayca gerçekleştirebiliriz.</p>
<blockquote>
<p>Hızlıca bir test yapmak için ping fonksiyonunu da çağırabilirsiniz. https://us-central1-project-new-hope.cloudfunctions.net/ping adresine talep göndermeniz yeterlidir.</p>
</blockquote>
<pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false">Adres : https://us-central1-project-new-hope.cloudfunctions.net/somedata/
Metod : HTTP Post
Body : JSON
Örnek Veri : {
"Id":1000,
"Quote":"Let’s go invent tomorrow rather than worrying about what happened yesterday.",
"Owner":"Steve Jobs"
}</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/05/27/credit_10.png" alt="" /></p>
<p>Bir kaç deneme girişi yaparak veriyi çoğaltabiliriz. JSON formatlı olmak suretiyle istediğimiz şema yapısında veriler yollamamız mümkün. Firebase sayfasındaki Database kısmına baktığımıza aşağıdakine benzer sonuçları görürürüz.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/05/27/credit_11.png" alt="" /></p>
<p>Pek tabii HTTP Get çağrıları sonuncunda da aktardığımız tüm verileri çekebiliriz. Bunun için aşağıdaki adrese talepte bulunmak yeterlidir.</p>
<pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false">Adres : https://us-central1-project-new-hope.cloudfunctions.net/somedata/
Metod : HTTP Get</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2019/05/27/credit_12.png" alt="" /></p>
<h2>Başka Neler Yapılabilir?</h2>
<p>Ben bir an önce deneyimlemenin heyecanından olsa gerek bu tip Hello World örneklerinde Get, Post harici fonksiyonları uygulamayı çoğunlukla atlıyorum. Dolayısıyla siz tembellik etmeyerek Put, Delete ve filtre bazlı Get metodlarını da örneğe katabilirsiniz. Hatta bu örneğin aksine Realtime Database yerine Cloud Firestore modelini kullanmayı denemenizi de şiddetle öneririm. Ayrıca şema olarak daha düzgün bir veri modeli üzerinden ilerlenebilir.</p>
<blockquote>
<p>Malum bulut hizmetleri belli bir noktadan sonra kullanımlarımıza göre ücret alıyorlar. Bu nedenle yukarıdaki servislere an itibariyle ulaşamayabilirsiniz. Nitekim Azure, Google Cloud Platform veya Amazon Web Services gibi ortamlarda hazırladığım kaynakları işim bittikten bir süre sonra mutlaka kaldırıyorum. Daha önceden yaşadığım bazı acı tecrübeler nedeniyle...</p>
</blockquote>
<h2>Ben Neler Öğrendim?</h2>
<p>Bu çalışma kapsamında daha çok GCP üzerinde bulut fonksiyon barındırma ve veri tabanı ile ilişkilendirme konularını inceleme fırsatı bulmuş oldum. Pek tabii bu çalışmanın da bana kattığı bir şeyler oldu. Bunları aşağıdaki maddeler halinde özetleyebilirim.</p>
<ul>
<li>Firebase üzerinde bir projenin nasıl oluşturulacağını</li>
<li>firebase-tools ile proje başlatma, fonksiyon dağıtma gibi temel işlemlerin terminalden nasıl yapılacağını</li>
<li>Kendi geliştirme ortamımızda yazılan node.js tabanlı bir API hizmetini Function olarak Firebase'e nasıl deploy edeceğimi</li>
<li>Realtime veri tabanı modelinin kabaca ne olduğunu</li>
</ul>
<p>Böylece geldik bir <a href="https://github.com/buraksenyurt/saturday-night-works" target="_blank">cumartesi gecesi macerası</a>nın daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.</p>2019-06-21T07:00:00+00:00firebasereal time databasegooglegoogle cloud platformgoogle cloud functionsnodenpmnode.jsjavascriptfirebase-toolspostmanexpresscorscloud computingcloud functionseslintbsenyurtBulut çözümlerin sunduğu imkanlardan birisi de sunucu oluşturma, barındırma, yönetme gibi etkenleri düşünmemize gerek kalmayacak şekilde uygulama geliştirme ortamları sağlamalarıdır. Bazen bulut platform üzerinde sunulan bir veritabanı ile konuşan servis kodlarını yine o platformun sunucularında barındırmak suretiyle hizmet sağlarız. Söz gelimi Google'ın Firebase veritabanı ve onu kullanan servis tabanlı fonksiyonları Google Cloud Platform üzerinde konuşlandırabiliriz. Bu örnekteki amacım da Firebase ile ilişkili bir uygulama servisini Google Cloud Platform üzerinde fonksiyonlaştırabilmek. Her zaman olduğu gibi örneği WestWorld (Ubuntu 18.04, 64bit) üzerinde geliştiriyorum.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=f0fdfb72-fff5-4d8b-b897-2bbd8b58213a1https://buraksenyurt.com/trackback.axd?id=f0fdfb72-fff5-4d8b-b897-2bbd8b58213ahttps://buraksenyurt.com/post/google-cloud-fonksiyonlarini-firebase-ile-birlikte-kullanmak#commenthttps://buraksenyurt.com/syndication.axd?post=f0fdfb72-fff5-4d8b-b897-2bbd8b58213ahttps://buraksenyurt.com/post/Google-Cloud-PubSub-Service-MacerasıGoogle Cloud Pub/Sub Service Macerası2018-07-19T21:10:00+00:00bsenyurt<p><img style="float: right;" src="https://buraksenyurt.com/image.axd?picture=/2018/02/surfing.gif" alt="" />Merhaba Arkadaşlar,</p>
<p>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.</p>
<p>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 <a href="https://www.incentro.com/en/" target="_blank">Incentro</a> firmasının<em>(Internet sayfaları çok hoş)</em> direktörü Kees van Bemmel'ın kaleminde çıkan yazıyı okuduğumda. Makale, Cloud Platform temelli olarak geliştirilen bir çözüm hakkındaydı.<em> </em></p>
<p>Ş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ı.</p>
<p>Şö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. <a href="http://cloudplatform.googleblog.com/2018/01/how-we-built-a-serverless-digital-archive-with-machine-learning-APIs-Cloud-Pub-Sub-and-Cloud-Functions.html" target="_blank">Buradaki yazıyı</a> okumanızı şiddetle tavsiye ederim. </p>
<blockquote>
<p>Ş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<em>(mesela rüzgar sörfünü otomatik olarak algılayıp windsurfing takısı ile işaretlenmesi)</em> ve hatta konuşmalarındaki metinsel ifadelere göre de<em>("alaçatı'nın rüzgarları sörf yapmak için idealmiş ahbap...")</em> 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</p>
</blockquote>
<p>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<em>(az bakarak yapılanı kabul, hiç bakmadan tek seferde yapılanı makbuldur)</em> ç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.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2018/02/gcpps_1.gif" alt="" /></p>
<p>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<em>(yazının konusu olan Pub/Sub hizmetine)</em> yönlendirecekti. Bu yönlendirme sırasında resim, video ve ses ile ilgili işleme fonksiyonları<em>(Google Cloud Functions)</em> 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. </p>
<p>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.</p>
<p>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<em>(ben araştırırken 7 gündü)</em> korunmaktadır.</p>
<p>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ı<em>(pipeline)</em> Öyleyse gelin adım adım ilerleyerek konuyu anlamaya çalışalım.</p>
<h1>gCloud ile İlk Deneyim</h1>
<p>Google bu konu ile ilişkili oldukça zengin ve basit öğreti dökümanları sunmakta<em>(Diğer bulut bilişim sistemlerinde olduğu gibi)</em> 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 </p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2018/02/gcpsp_2n.gif" alt="" /></p>
<p>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.</p>
<blockquote>
<p>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.</p>
</blockquote>
<p>Bundan sonra iş West-World terminalindeydi. gCloud komutunu kullanarak<em>(daha önceden West-World'e kurmuştum)</em> 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ü.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2018/02/gcpps_6.gif" alt="" /></p>
<p>Ö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;</p>
<p>codeTopic isimli bir konu başlığı oluşturmak için şu komutu kullanıyoruz,</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">gcloud pubsub topics create codeTopic</pre>
<p>Ben oluşturulan topikleri nasıl görebileceğimizi de merak ettiğimden şöyle bir komut buldum.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">gcloud pubsub topics list</pre>
<p>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.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">gcloud pubsub subscriptions create --topic codeTopic westworldSubscription</pre>
<p>Eğer abonelerin bir listesini görmek istersek de şu komut işimize yarayacaktır.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">gcloud pubsub subscriptions list</pre>
<p>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.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">gcloud pubsub topics publish codeTopic --message "convert this message to c#"</pre>
<p>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 <em>(<a href="https://cloud.google.com/pubsub/docs/subscriber#push_pull" target="_blank">Şu adreste</a> Push ve Pull metodlarının çalışması ve hangi durumda hangisinin tercih edilmesi gerektiğine dair bir takım bilgiler bulunmakta)</em></p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">gcloud pubsub subscriptions pull --auto-ack westworldSubscription</pre>
<p>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.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">gcloud pubsub topics delete codeTopic
gcloud pubsub subscriptions delete westworldSubscription</pre>
<h1>.Net Core Tarafı</h1>
<p>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. </p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">dotnet new console -o gcppubsubhello</pre>
<p>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.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">dotnet add package Google.Cloud.PubSub.V1 --version 1.0.0-beta16</pre>
<p>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.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">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);
}
}
}</pre>
<p>İş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.<br /><br /></p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2018/02/gcpps_7.gif" alt="" /></p>
<p>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<em>(sonradan aynı içeriğe sahip mesajlar tekrar geldiğinde farklı olacak şekilde)</em> birer ackID değeri bulunur. Kodu arka arkaya çalıştırdığımda aşağıdaki sonuçları elde ettim.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2018/02/gcpps_8.gif" alt="" /></p>
<p>İlk çalıştırma normal sonuçlansa da ikinci çalıştırmada bir exception almıştım. Aslında hata oldukça basitti.</p>
<pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false">Unhandled Exception: Grpc.Core.RpcException: Status(StatusCode=AlreadyExists, Detail="Resource already exists in the project (resource=codeTopic).")</pre>
<p>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 ;)</p>
<p>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.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">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);</pre>
<p>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.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2018/02/gcpps_9.gif" alt="" width="640" height="173" /></p>
<p>Pek tabii kodu ikince kez denediğimde zaten var olan bir aboneyi tekrar oluşturmaya çalıştığım için exception almam gayet normaldi.</p>
<pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false">Grpc.Core.RpcException: Status(StatusCode=AlreadyExists, Detail="Resource already exists in the project (resource=einstein).")</pre>
<p>Çö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? :) )</p>
<p>Bir abonem olduğuna göre onu kullanarak codeTopic üzerine bırakılan mesajları okumayı deneyebilirdim. İşte kodlar.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">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();
}
}
}</pre>
<p>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) </p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2018/02/gcpps_10.gif" alt="" /></p>
<p>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</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">export GOOGLE_APPLICATION_CREDENTIALS="my-starwars-game-project-d977a50a19f5.json"</pre>
<p>ş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 <a href="https://cloud.google.com/docs/authentication/production" target="_blank">şu adresindeki yazıya</a> bakabilirsiniz.</p>
<p>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.</p>2018-07-19T21:10:00+00:00googlegoogle cloud platformpublisher subscriber modelmachine learningimage processinggcloudhttp.net coregogo programming languagebsenyurtBu makalemizde Google Cloud Platform'un önemli servislerinden birisi olan Pub/Sub API'yi incelemeye çalışıyoruz. gCloud ile komut satırından topic oluşturup, buna abone oluyor ve mesaj yayınlayıp, yayınlanan mesajı okuyoruz. Ardından .Net Core ile geliştirdiğimiz uygulamada Google.Cloud.PubSub.V1 paketini kullanarak mesaj yayınlama ve yayınlanan mesajları okuma işlemlerini nasıl gerçekleştirebileceğimize bakıyoruz.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=356662ac-f164-4a8a-b9c8-1d05bd7f781e2https://buraksenyurt.com/trackback.axd?id=356662ac-f164-4a8a-b9c8-1d05bd7f781ehttps://buraksenyurt.com/post/Google-Cloud-PubSub-Service-Maceras%C4%B1#commenthttps://buraksenyurt.com/syndication.axd?post=356662ac-f164-4a8a-b9c8-1d05bd7f781ehttps://buraksenyurt.com/post/Google-Cloud-Storage-KullanımıGoogle Cloud Storage Kullanımı2018-05-08T07:23:00+00:00bsenyurt<p><img style="float: right;" src="https://buraksenyurt.com/image.axd?picture=/2018/02/gcpstorage_1.gif" alt="" />Merhaba Arkadaşlar,</p>
<p>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...</p>
<p>Görmekte olduğunuz fotoğraf Instagram arşivimden bir kare. O zamanlar denediğim aynasız fotoğraf makinem<em>(Sony Alpha idi. Hafif ve kullanışlıydı)</em> 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.</p>
<p>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<em>(her tür içerik söz konusu olabilir anladığım kadarıyla)</em> erişmemiz mümkün. Fotoğraf, makale, doküman vb içerikler depolama denince aklımıza ilk gelen çeşitler.</p>
<p>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<em>(Kova gibi de düşünülebilir)</em> bir fotoğraf attıktan sonra, Pub/Sub API'den de yararlanarak<em>(geçenlerde incelemiştik hatırlarsanız)</em> 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.</p>
<p>Storage API'sini kullanmak oldukça kolay. Komut satırından gsutil aracı<em>(Google Cloud SDK'ye sahip olduğunuzu düşünüyorum)</em> 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.</p>
<p>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.</p>
<h1>gsUtil ile Storage İşlemleri</h1>
<p>İ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.</p>
<p>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.</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">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</pre>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2018/02/gcpstorage_4.gif" alt="" /></p>
<p>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)</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2018/02/gcstorage_2.gif" alt="" /></p>
<p>cp parametresini kullanarak bir bucket'a dosya yüklememiz de mümkün. Ben legom.jpg dosyasını yükledim<em>(farklı dosya tiplerinden içerikler de kullanılabilir) </em>Sonuç aşağıdaki resimde görüldüğü gibiydi.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2018/02/gcpstorage_3.gif" alt="" /></p>
<p>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? ;)</p>
<p>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 :) </p>
<h1>Credential Mevzusu</h1>
<p>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<em>(ya da hangi servisi kullandırtmak istiyorsak onun için)</em> bir role belirlenmesidir. </p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2018/02/gcpstorage_5.gif" alt="" width="640" height="480" /></p>
<p>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.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2018/02/gcpstorage_6.gif" alt="" /></p>
<p>Bu dosyayı kod tarafındaki servise ait Credential bilgilerini yüklemek için kullanacağız.</p>
<blockquote>
<p>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 <a href="https://cloud.google.com/docs/authentication/production" target="_blank">buradaki yazıya bakmanızı</a> öneririm. </p>
</blockquote>
<h1>.Net Core Tarafı</h1>
<p>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<em>(Düşündüm de günümüzde her yer RESTful servis sanki)</em> Dolayısıyla ilk terminal komutlarımız şunlar...</p>
<pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false">dotnet new console -o howtostorage
dotnet add package Google.Cloud.Storage.V1</pre>
<p>Program.cs içeriğini aşağıdaki gibi geliştirebiliriz.</p>
<pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false">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.");
}
}
}</pre>
<p>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.</p>
<p>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. </p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2018/02/gcpstorage_7.gif" alt="" /></p>
<p>Tuşa basıp ilerlediğimdeyse hem yüklediğim dosyaların hem de bucket'ın kendisinin silindiğini gördüm.</p>
<p><img src="https://buraksenyurt.com/image.axd?picture=/2018/02/gcpstorage_8.gif" alt="" /></p>
<p>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<em>(diye düşünüyorum)</em> 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.</p>2018-05-08T07:23:00+00:00.net coregoogle cloud platformgoogle cloud storagerest apigsutilbucketstoragecloud computingStorageClientrestfulapijsonbsenyurtBu yazımızda Google Cloud Storage hizmetini nasıl kullanabileceğimizi incelemeye çalışıyoruz. Özellikle fiziksel depolama olarak kullanabileceğimiz bu hizmeti hem komut satırından hem de .Net Core ortamından kullanmayı deniyoruz. Bir Bucket oluşturup, fotoğraf yüklüyor, yüklediğimiz içerikleri kendi ortamımıza çekiyoruz.https://buraksenyurt.com/pingback.axdhttps://buraksenyurt.com/post.aspx?id=ed3ccc24-0d97-4620-8069-d816a86ed81f1https://buraksenyurt.com/trackback.axd?id=ed3ccc24-0d97-4620-8069-d816a86ed81fhttps://buraksenyurt.com/post/Google-Cloud-Storage-Kullan%C4%B1m%C4%B1#commenthttps://buraksenyurt.com/syndication.axd?post=ed3ccc24-0d97-4620-8069-d816a86ed81f