Quantcast
Are you the publisher? Claim or contact us about this channel


Embed this content in your HTML

Search

Report adult content:

click to rate:

Account: (login)

More Channels


Showcase


Channel Catalog



Channel Description:

Matematik Mühendisi Bir .Net,Ruby,Go ve Python Severin Maceraları
    0 0

    1985 Yazı - Hikayenin Başladığı Yer

    Almanya’daki kuzenlerimden biri olan Haluk abi orta öğrenimi için dayımlar tarafından İstanbul’a gönderilmişti. Şu anda 94 yaşında olan anneannem ve rahmetli Hasan dedemin Barbaros mahallesindeki bahçeli evlerinin üst katında sokak tarafına bakan oturma odasında yaşıyordu. Benim ilkokul sıralarında olduğum zamanlardı. Rahmetli Ali dayım orta okul sınıf geçme hediyesi olarak Haluk abime üstünde Commodore 64 yazan bir bilgisayar almıştı. Vaktinin çoğunu resim yaparak ve 37 ekran renkli televizyona bağlanmış Commodore’unda oyun oynayarak geçiriyordu.

    Çılgın pilot Murdock’lı A takımının, John Jay Rambo’nun, He-Man’in, “bir alışveriş bir fiş” ile KDV’nin, siyah okul önlüklerinin, şeytan Rıdvan gol kralı Tanju’nun olduğu dönemlerdi. Mahallenin bir kaç sokak ötesindeki toprak futbol sahasının yanındaki pasajda müzik kasetleri, plaklar ve VHS’ler satan küçük bir dükkan vardı. Commodore’un popülerleşmesiyle birlikte oyun kasetleri doldurmaya da başlamıştı. Bir kaset içinde onlarca oyun olabiliyordu. Summer Games, Green Beret, River Raid, 1942, PaperBoy, Hawkeye, Emlyn Hughes Soccer ve aklıma gelmeyen niceleri…

    Hangi gün olduğunu hatırlamıyorum lakin akşam üstü vakitleriydi. Haluk abiyi camda bekliyordum. Beni pek oynatmazdı ama yanında oturup oynadığı oyunları izlememe izin verirdi. Yokuştan aşağıya doğru yürüdüğünü gördüm. Anneannemin lezzetini hiç unutamadığım o nefis reçellerine malzeme yaptığı rengarenk güllerle süslediği bahçenin kapısını açıp hızlı adımlarla girişe doğru ilerledi. Merdivenlerden üst kata ikişer ikişer çıktığını anlayabiliyordum. Nefes nefese kalmış bir şekilde odaya girdi ve “Yeni oyunlar çıkmış Burak!” dedi gülümseyerek.

    Ama hemen oynayamazdık. İlk başta cihazın önceki oyunlar nedeniyle bozulan kafasının düzeltilmesi gerekiyordu. Commodore oyunları okumak için bir kasetçalar kullanıyordu. İçindeki kasetin bantına yazılı bitleri kullanarak bizlerin saatlerce Tv başında vakit geçirmesine neden olan bir eğlence makinesiydi.

    Haluk abim saatçi tornavidasını kullanarak kasetçaların üstündeki vidayı sağa sola doğru birkaç kez oynattı. Ekranda sarhoş birisi gibi dolanan eğri büğrü çizgiler bu hareketlerle düz bir doğruya dönüşmeye başladı. Bu, kafa ayarının tamamlandığı anlamına geliyordu. Haluk abim oyun kasetini taktı ve Play tuşuna bastı. İlk oyun başladı. Ekranın alt tarafındaki uçak gemisinden kalkan ikinci dünya savaşına ait bir tayyare yukarı doğru ilerliyordu. Karşısına çıkan düşman uçaklarına ateş açıyor, aşağıdaki hedeflere bomba atıyor, Fuel yazan yerlerden geçerek yakıt takviyesi yapıyordu. Sonra vuruldu. Haluk abi tekrar başladı. Yanında çıt çıkarmadan heyecanla ekranı izliyordum. Joystick kolunu bir o tarafa bir bu tarafa sürükleyip duruyordu. Bu kez biraz daha ilerlemeyi başarmıştı ama yine vuruldu ve tekrar başladı. Tekrar, tekrar ve tekrar…

    Dakikalar saatleri tamamlamaya başladı. Sırtımız sokağa dönük olduğundan kararan havanın farkına bile varamamıştık. Arada akşam ezanı ya da yatsı okunmuştu belki ama fark etmemiştik. Sokak sakinleri zaten çoktan evlerine çekilmişti. Zifiri karanlıkta TV ekranına bakıyorduk sadece. Derken kulağımda kısa süreli bir acı hissettim. Bir kaç yıl sonra bir atari salonunda oyun oynarken aynı acıyı tekrar hissedecektim :) Başımı şöyle bir geriye doğru kaldırırken onun tebessüm eden yüzüyle karşılaştığımda Haluk abi sanki oyunun içindeymiş gibi tam konsantre devam ediyordu. İkimizde rahmetli babamın ne odanın kapsını açışını ne arkamıza geçişini fark etmiştik. Belki bir kaç dakikadır tepemizde dikilmiş bekliyordu hatta. Meğer saatler gece yarısını çoktan geçmişti. Ufak bir çocuğun o saatte bilgisayar oyunu başında ne işi vardı!?

    Nedense yıllarca o cihazın sadece 64 kilobyte kapasiteli bir bellek ile bizleri ekran başına saatlerce bağlayan oyunlarının nasıl geliştirildiğini düşünmemiştim. Günümüzde sahip olduğumuz bilgisayarları bir düşünün. Gigabyte’larca ram’i olan, çok çekirdekli, müthiş ekran kartlı makinelerimiz var. Quantum’un sınırlarında gezip yüksek hızlara ulaşarak bilimde çığır açacak gelişmelere imza atıyoruz. Peki gerçekten bir programcının ultra mega süper kuponlu bir bilgisayara sahip olması şart mı? Neden hep en iyisini arıyoruz?

    1993 - Üniversite 1nci Sınıf

    Y.T.Ü. Matematik Mühendisliği bölümüne girdiğim yıl programcı olmak istediğime karar vermiştim. İlk yılın birinci ve ikinci dönemi grafik özellikleri arttırılmış GWBasic dilini ders olarak alıyorduk. Kişisel bilgisayarım olmadığı için bir çok çalışmayı sadece belli saatlerde açık olan okul labaratuvarında yapmak zorundaydım. O yıl 286 işlemcili bilgisayarların matematiksel işlem birimi arttırılmış 386 modelleri ile değiştirilmesi söz konusuydu. Beş çeyreklikler yerini 1.44Mb’lık disketlere bırakıyordu. Açık gri renkteki bilgisayar kasalarında hard disk bulunmuyordu. Bu nedenle MS-DOS işletim sistemlerini hocamızın verdiği disketlerden yükleyerek sistemi açabiliyorduk(Bu zamanın bootable USB’leri gibi düşünebilirsiniz) Siyah beyaz tüplü monitörler üzerinde zaman içerisinde Pascal, Cobol, C, C++ gibi lisanları da denedik ama olmuyordu. Kişisel bilgisayarımın olması şarttı. Ne var ki dönemin fiyatları en az bugünkü kadar el yakıyordu.

    İmdadıma Siemens’te çalışan eniştem yetişti. Geçici bir süre de olsa çalışmam için Siemens Nixdorf marka, elektronik daktilodan hallice çevrilmiş epey ağır bir laptop getirdi. Ağırlığı nedeniyle dambıl almama bile gerek yoktu. Siyah beyaz ekranı vardı ve üzerinde Windows 3.1 işletim sistemi koşuyordu. Tabii programlama bir yana üzerinde denediğim şeylerden birisi de Duke Nukem oynamaktı. Windows‘un üstünde kabuk olarak koştuğu çekirdek işletim sistemi MS-DOS ile çalışan bir oyundu. Siyah beyaz ekran o kadar geç tazeleme hızına sahipti ki karakteri koştururken arkada bıraktığı gölgeleri gri tonlu kareler halinde görebiliyordunuz. Biraz ağır olsa da bir laptop’un seyyarlığını tatma hissi en azından o yıllarda muhteşemdi.

    Tabii bir süre sonra kendi kişisel bilgisayarıma kavuştum. 14 inç monitörü olan 486DX-33mhz işlemcili bir bilgisayar. 8Mb RAM’inin olduğunu hatırlıyorum. Ekranı şenlendiren 2 Mb’lık Diamond Stealth marka bir kartı ve muhteşem ses veren Creative 32bit işlemcisi vardı. Tabii kısa bir süre geçmesine rağmen disket sürücüleri hala kullanılıyor bununla birlikte CD’ler de yüksek kapasiteli depolama birimleri olarak boy gösteriyordu. Artık kasete oyun çeken pek yoktu ama disketlere programlar koyuluyordu. Meşhur Yazıcıoğlu işhanına gidip 21 disketten oluşan Delphi 2.0 kurulum paketini aldığımı daha dün gibi hatırlarım.

    Delphi’nin bende ayır bir yeri vardır. İlk kez onunla yazdığımız ve oluklu mukavva üreticisi bir matbaa için geliştirdiğimiz programla para kazanmıştık. 90ların ikinci yarısında Amerika’dan Java isimi bir kitap getirtip bana “Burak! Gelecek bu. Web!” diyen sevgili Orkun ile birlikte 250 dolar kazanmıştık. Ben Taksim’deki Elit kitabevinden Delphi 2 Unleashed isimli 1400 sayfalık bir programlama kitabı alırken ileriyi gören Orkun 14.4 Kbps hızında bir modem alarak Ataköy’deki evinin odasından internete açılmıştı.

    1997 Şubatı - Compex Fuarı

    O yıllar ne kadar bilgisayar dergisi varsa alıp okumaktaydım. Yeni çıkan işlemcileri, Visual Basic ve Delphi tarafındaki gelişmeleri, Java’nın yükselişini, web teknolojilerinin hayatımıza girişini izliyordum. Üniversitedeki yakın arkadaşlarım bilgisayarlara olan merakımı biliyordu. Bu nedenle yeni bir bilgisayar toplayacakları zaman veya format atmaları gerektiğinde kapımı çalarlardı.

    Efes’in alt yapısında yıllarca oynayıp Matematik Mühendisi olmaya karar vermiş 1.96lık sevgili Serkan’da en yakın dostlarımdandı. Abdi İpekçi’de birlikte maç izlediğimiz çok olmuştu ama senede bir bilgisayar fuarına da giderdik. Böylece gelişmeleri canlı canlı görebilirdik. Üstelik bazı firmalar bu fuarlarda indirimler uygulardı. Pentium işlemcilerin yeni serilerinin geldiği heyecanlı günlerdi. Moore yasası sürekli olarak işliyor işlemciler iki seneden daha kısa sürede transistor sayılarını katlayarak hızlanıyordu.

    Compex o yıl ocak sonu şubat başı açılmıştı. Serkan, babası ve ben üçümüz birlikte Tepebaşı’nda açılan fuarın yolunu tuttuk. Davetiyelerimizi Pc World dergisinden kapmıştık. Serkan yeni bir bilgisayar almak istiyordu. Tabii ki baş danışman bendim :) Bir stand’tan diğerine gidiyor, broşürleri alıp modelleri inceliyorduk. Sonunda bir tanesinde karar kıldık. Doğruyu söylemek gerekirse fiyatlara da bakıyorduk. Modelin iki versiyonu vardı. Birisi 32 diğeri 64Mb Ram kapasiteliydi. Pek tabii bu devirde olduğu gibi iki katına çıkan Ram farkı bilgisayar fiyatını da epeyce etkliyordu. Ben Serkan’a 64Mb olanını almasını tavsiye ettim. Biraz çekingen halde ve tereddüt ederek babasına doğru döndü ve “Baba…32 Mb yerine 64 Mb olanı alalım” dedi. On yaşında oğlu olan bir baba olarak benim bugün vereceğim tepkinin bir benzerini 1997 kışında Serkan’ın babası verdi; “Sen önce bir 32liği doldur sonra 64lüğüne bakarız” :D

    Bu hatıram beni hep güldürür ve durup düşünürüm :) Neden en yüksek konfigurasyona sahip bir bilgisayar toplama çabasındaydık. Bir programcı için ortam şartları büyük bir engel oluşturmamalıydı. Optimize edilmesi gereken bir şeyler varsa bunun yolunu kendisi bulmalıydı.

    2006 Sonbaharı

    O zamanlar büyük bir hevesle ve gönüllü olarak katıldığım topluluk çalışmalarım nedeniyle bir vakit benden savunma istemiş yazılım firmasından ayrıldığım günler. Çalıştığım süre boyunca şirketin bana verdiği IBM Thinkpad T41 model bilgisayarı kullanmıştım. Tabii geri vermek durumunda kalınca bir anda bilgisayarsız kaldım. Evdeyse hiç aşina olmadığım yeşil renkli, dış kapağının saydamlığı nedeniyle içi görünen 1998 model bir iMac G3 vardı. İnanılmaz estetik bir tasarımdı. Tüm Apple’lar da olduğu gibi.

    Derken başına oturup onu açtım ve bir kaç arkadaşıma iş aradığıma dair mail atmak üzere harekete geçtim. Ne yazık ki orjinal mac klavyesinde @ sembolünü bir türlü bulamıyordum. Tarayıcı epey tuhaf gelmişti. Başka bir tarayıcıyı nasıl yükleyeceğimi bile bilmiyordum. Gerçekten yabancı topraklarda kalmıştım. Bir kaç gün öncesine kadar Microsoft’un .Net Framework 2.0 sürümü üzerinde C# 2.0 ile geliştirmeler yapmaya çalışan ben sudan çıkmış balık misali çaresiz hissediyordum.

    Ancak bir kaç yıl önce yaptığım keskin Linux geçişine keşke o yıllarda cesaret etseydim demeden edemiyorum. Gerçekten güçlü bir Laptop, PC ve Windows işlerimi yapabilmem için şart mıydı?

    2019 Eylül - Günümüz

    Yaz başına kadar yaklaşık 8 yıl önce aldığım Dell marka bir laptop kullanıyordum. WestWorld olarak isimlendirdiğim alet Ubuntu’da koşuyordu. Son olarak 18.04 sürümüne çıkmıştım. 4 çekirdekli intel işlemci, 8 Gb RAM ve 250GB disk kapasitesi hayli hayli yetiyordu. Ekranı bir kaç yıl önce bozulduğundan harici monitör kullanıyordum. Performansı ile ilgili bir sorunum yoktu. Nitekim artık eskisi gibi her şeyi makineye yüklemeye çalışmıyordum. SQL Server’a mı ihtiyacım var? Neden ki? Pekala PostgreSQL’de de denemelerimi yapabilirim. Peki onu kurmak zorunda mıyım? Elbette hayır! Docker ne güne duruyor :) İlle de SQL gerekiyorsa peki? O zaman Azure’a uğrayabilirim. Ya IDE!? Her yeni laptop alındığında mutlak suretle Visual Studio’nun en kallavi sürümünü yüklemiyor muyuz? Haydi itiraf edin. Ultimate sürümlerini torrent’lerden bulup yüklüyorsunuz değil mi? Yanına SQL Server Management Studio…Office’in tam sürümü…Oysa ki Visual Studio Code açık kaynak platformlar için harika bir IDE. Office’i gerçekten kurmak gerekli mi? Üstelik Office 365 varken ve dokümanlarımıza online olarak her yerden ulaşabiliyorken?

    Ama işte WestWorld’ün o pancar motoru misali gürültülü fan sesi yok muydu? Müzik dinlemeden çalıştığım sessiz gecelerimin keyfini kaçırıyordu. Yıllar geçtikçe daha fazla zorlanmaya başlamıştı. Soğutucular deniyor, ara ara salon süpürgesi ile tozlarını çekiyor, Ubuntu’nun process’lerini kontrol edip sistemi yoranları ayıklamaya çalışıyordum. Yine de olmuyordu. Elim yeni bir bilgisayar almaya da gitmiyordu. Fiyatlar gerçekten yüksek. Her zaman böyleydi belki de ama dövizdeki artış, okul taksidi, basketbol ayakkabısı derken hep geriye atmak zorunda kaldığım bir maliyetti.
    Sonra günlerden bir gün bütçeyi bir nebze olsun ayarlamayı başardım. İyi bir indirime giren Macbook Mini modelini bir süredir kovalıyordum. Ahch-to adını vereceğim aletin fiyatı 3K’nın altına düşmüştü. Monitorüm zaten olduğu için Mini PC tadında isteklerimi karşılayacaktı. Üstelik uzun zamandır üzerinde geliştirme yapmayı merak ettiğim macOS işletim sistemini de deneyimleme fırsatı bulacaktım. Bu kez Ubuntu çalışmalarından dolayı terminal penceresine biraz daha aşinaydım ve cesaretim vardı.

    Yine de ortada ufak bir pürüz vardı. 4Gb Ram…Gözüme epey az gelmişti. Şirketin verdiği 16Gb RAM’li intel Core i7 Pro işlemcili alet bile bazı durumlarda takılabiliyordu. Baktığım mininin 8Gb olan modelindeki intel işlemcisi de daha iyiydi. Ama fiyat neredeyse %60 oranında fazlalaşıyordu. Sonunda bilgisayarı aldım. İlk kurulumlarımı yaptım. Visual Studio Code, xCode, git, Node.Js, docker ve daha bir sürü şeyi üzerinde denemeye başladım. Ancak korktuğum başıma gelmişti. Ubuntu’dan bile yavaştı sistem. Reaksiyon süresi çok düşüktü. Chrome tab’ları can çekiştiği için terk edip Safari’ye dönmüştüm ama pek bir şey değişmemişti. Her nedense makine açıldıktan çok uzun süre sonra kendini toplamaya başlıyor, tabir yerinde ise t anında sadece bir işe odaklanarak nefes alabiliyordu. “Keşke” dedim yine. Keşke diğer modeli alsaydım. Güçlü olanı. Daha pahalıydı belki de ama performansı iyi olacaktı.

    Sonra düşünmeye başladım. Neden en iyisi gerekiyordu programlama yapmam için. Merak ettiğim şeyler arasında kullanacaklarımı pekala bulutta konumlandırabilirdim. Performans maliyeti yüksek IDE’lere gereksinimim yoktu gerçekten de. Ama içime bir huzursuzluk düşmüştü.

    Bunun üzerine ucuz maliyetli Raspberry Pi 3B+ almaya karar verdim. 1 Gb Ram’i olan cihazın üzerinde Debian tabanlı Raspbian sürümünü kullanacaktım. 16 Gb’lık bir MicroSD kartı bootable disk olarak hazırladım. Ufacık entegrenin hard diski yoktu ama WiFi ile internete bağlanabiliyordu. Daha ilk gece üstünde bir çok şey yapmış, sadeliğine, sessizliğine ve minimal gereksinimlerine hayran kalmıştım. Şimdilik…

    Şu adresten öğrendiğim kadarıyla Ubuntu Mate sürümünün en azından beta olarak Raspberry Pi 2,3 modellerine kurulumuna ait bir paylaşım var. Diğer yandan 2019 tarihli bu yazıya göre Ubuntu ile sınırlı değiliz. Bir çok işletim sistemini Raspberry Pi üzerinde kullanmamız mümkün. Windows IoT Core ve Ubuntu Core’da bunlara dahil.

    Forbes’un bir haberine göre Ubuntu Mate’in Raspberry Pi 4'ü destekleyeceği ifade edilse de resmi olarak buna dair bir doğrulama en azından yazıyı hazırladığım tarih itibariyle yoktu. Sadece Estimated Time of Arrival konsepti ile üzerinde çalışıldığına dair söylemler var. Sanırım bu desteğin gelip gelmeyeceğini zaman içerisinde öğreneceğiz.

    Raspbian’da Bir Gece…

    Artık bir şeyleri kurmaya çalışıp, programlama ortamını hazırlamak üzere harekete geçebilirdim. Saat 22yi geçmişti. S(h)arp Efe ve üst kattaki gürültücü velet çoktan uykuya dalmıştı. Mahalle sakinleşmiş ve çalışma odam tam olarak istediğim sessizliğe gelmişti. Arkamdaki kütüphanenin üst rafına kaldırdığım emektar WestWorld’e göz ucuyla şöyle bir baktım ve tebessümle “Umarım tatilin iyi geçiyordur” diyerek klavyeme döndüm. Planladığım belli bir yol haritası yoktu. Doğaçlama gidecektim. Öncelikle Raspbian diyarında neler var ne yok bakayım dedim. Aklıma ilk önce Raspberry Pi’ler ile özdeşleşen Python ortamı geldi.

    Raspbian üzerinde Python 2 ve 3 sürümleri ile pip paket yöneticisi zaten yüklü olarak geliyor ama benim Node.js ve npm ile yapacağım çalışmalar da var. Lakin bazı ürünlerin doğru sürümlerini yükleyebilmek ya da desteklerinin olup olmadığını görmek için ARM işlemcisinin versiyonunu öğrenmem gerekmişti. Bunun için şu terminal komutunu kullanarak çalışmalarıma başladım.

    uname -m

    Sonra aşağıdaki komutlarla nodejs’i sisteme yükledim. Beraberinde npm paket yöneticisi de geldi tabi.

    curl -sL https://deb.nodesource.com/setup_10.x | sudo bash -
    sudo apt install nodejs
    node — version
    npm — version

    İşte elimin altında nodejs. Rastgele aklıma gelen enstrümaları toplamaya devam ettim. Mesela şunu merak ediyordum; Raspbian üzerinde PostgreSQL’in Docker Container’ını çalıştıramaz mıydım? Hani kendi başına çalışan hafif bir microservice konuşlandıracağım bir sürü Raspberry cihazımız olur mu hayalinden yola çıkarak(Hatta çalıştığım firmadaki bazı yeni nesil ürünlerin SQL Server’dan PostgreSQL platformuna göçü için yapılan çalışmaları düşünerek)Öncesinde Docker bu sistemde çalışır mı bunu öğrenmem gerekiyordu. Bunun üzerine terminali aşağıdaki komutlarla şenlendirdim.

    sudo curl -fsSL https://get.docker.com -o get-docker.sh
    sudo sh get-docker.sh
    sudo docker container run hello-world

    Tabii container’ın başarılı bir şekilde çalıştığını görünce SD kartın çabucak dolmasından endişe edip başlatılan container’lar ile imajı silmeyi de ihmal etmedim. Savurganlığın lüzumu yoktu :)

    sudo docker container ls -a
    sudo docker container rm f7db1b 354818
    sudo docker image ls
    sudo docker image rm 618e43

    Pek tabii bir Container’ı ayağa kaldırmış olmak iştahımın daha da artmasına neden oldu. Bunun üzerine PostgreSQL imajını yükleyeyim ve iki SQL sorgusu çalıştırayım dedim.

    sudo docker run -d — name c_postgres -v London:/var/lib/postgresql/data -p 54321:5432 postgres:11
    sudo docker exec -it c_postgres psql -U postgres
    CREATE DATABASE AdventureWorks;
    CREATE TABLE IF NOT EXISTS Category (CategoryId SERIAL PRIMARY KEY,Name varchar);
    INSERT INTO Category (Name) VALUES (‘Book’);
    INSERT INTO Category (Name) VALUES (‘Movie’);
    SELECT * FROM Category;

    Veritabanı oluşturabildiğimi, kayıt atıp çekebildiğimi görünce epey mutlu oldum. Çalışmalarım sırasında ihtiyacım olabilecek veri depoları için PostgreSQL’i burada deneyimleyebilirim(Hatta SQLite gibi sürümleri de denesem gayet iyi olur)

    Daha neleri kontrol edebilirim diye düşünürken asıl göz ağrım .Net Core’a ayrı bir sayfa açmam gerektiğini fark ettim. Nitekim C# programlama dilini ve .Net Core platformunu deneyimleyeceğim bir ortam olması önemliydi. Biraz araştırma yaptıktan sonra ARM sürümü için Microsoft’un şu adresindeki kaynaktan yararlanarak Raspbian üzerine .Net Core SDK 2.2 versiyonunu yükleyebildim. Tabi güncel .Net Core sürümlerinden hangilerinin hayatta kaldığına ara ara bakmakta yarar var. Özellikle ürünleştirilmiş sürümlerimiz varsa bu önemli bir nokta. Microsoft’un şu adresindeki görsel imgeler iyi birer yardımcı.

    Gerekli kurulumun ardından basit bir Console uygulaması oluşturup denemem yeterliydi. Henüz Visual Studio Code editörü yüklenebiliyor mu bilmiyordum ama Program.cs içeriğini düzenlemek için Raspbian ile gelen Geany pekala kafiydi.

    Gerçi Visual Studio Code’un açık kaynak olarak Raspberry Pi için genişletilmiş code-oss isimli bir versiyonunu şu adresten indirip kısa süre kullandım. Ancak yorumlarda da belirtilen blank screen probleminin çözümü için update’leri kapatmak zorunda kalmak beni biraz rahatsız etti. Belki ilerde işlemcinin armhf sürümü için tam Visual Studio Code desteği gelir.

    dotnet new console -o hello-from-pi

    Şu an için tek sıkıntı uygulamanın çalışmasının çok yavaş olması. Basit bir Hello World uygulaması için dotnet run komutunun epey beklettiğini fark ettim. Henüz bunun sebebini anlayamadım. Belki ve muhtemelen ARM işlemcisinden kaynaklı lakin bu canımı sıkan bir durum değil. Çünkü çıt çıkartmayan ve ceket cebime koyup istediğim yere götürebileceğim bir bilgisayarım var :) (Gittiğim yerde HDMI girişli bir monitor/tv, klavye ve mouse olması şartıyla tabii)

    Tipik bir desktop kullanıcısının başlangıçta yadırgayacağı tek şey varsayılan olarak Raspberry Pi’nin Açma/Kapama düğmesine sahip olmayışıdır. İlk gece işletim sistemini bir kaç sefer kitleyince güç kablosunu çıkartıp tekrar takarak ilkel bir şey yaptığımı da düşündüm aslında. Fakat buna çok takılmadım. Yine de şu adresteki yönergeleri takip ederek güç düğmesi entegre edilebileceğini öğrendim. Yanlışlıkla mavi kabloyu keserek devrenin patlamasından korkanlar dükkandan hazır anahtar modülü alıp bağlayabilir de ;)

    Pek tabii merak ettiğim ve denemek istediğim bir çok konu var. Git(Raspbian ile yüklü geliyor) komut satırı aracı ile GitHub projelerine bağlanmak, cihazı NGinX veya Apache sunucusu olarak kurgulayıp web servisi hizmeti sunacak şekilde ayağa kaldırmak, Erlang dilini öğrenmek(ki sorunsuz bir şekilde sisteme kuruldu ve hello world uygulaması çalıştırılabildi — bknz aşağıdaki resim), Flutter çatısının platform üzerinde kullanılıp kullanılamayacağına bakmak, Google Cloud Platform öğretilerini takip ederek bulut tabanlı temel geliştirici operasyonlarını Raspi ile birlikte denemek(gCloud CLI) ve benzerleri…

    Aslında Raspberry Pi’yi bir IoT cihazı gibi kullanmaktansa düşük maliyetli ve programlama deneyimi yaşayabileceğim bir araç olarak ele almak şu anki hedeflerimden birisi. En nihayetinde üzerinde gelen Scratch ile çocuklara temel programlama deneyimini de yaşatıyor ki ben hala büyümedim.

    Bir Öğle Arası…Şekerpınar

    Gerçekten de programlama yapmak için çok güçlü bir makineye ihtiyacım var mı? Yeni bir maceraya atılmış gibi hissediyorum. Bazen teknolojinin baş döndürücü gelişmesine kızsam da yeni ufuklara yelken açmamıza olanak sağladığı için hevesleniyorum. Oldukça minimal bir cihaz üzerinde programlama öğrenmeye çalışmak…Motivasyonum tam olarak bu. Piyasadaki bilgisayarlara nazaran çok düşük maliyetli bu minion pc için elde edeceğim deneyime göre 128 Gb Micro SD karta geçmeyi bile düşünebilirim. Hatta 4Gb Ram kapasiteli Raspberry Pi 4 sürümü maliyet bazında ciddi olarak düşünülebilir ki PC’lere hafif bir alternatif olarak ön plana çıkmakta(Şuradaki karşılaştırma konu hakkında fikir verebilir) Aradığım sorunun cevabını ancak bu şekilde bulabileceğime inanıyorum. Tabii şu önemli parametreyi de unutmamak lazım; Bu karışımı üretim ortamları haricinde yazılım geliştirme adına bir şeyleri öğrenmek için kurcalayacağım deneysel bir çalışma sahası olarak görüyorum. ARM işlemcisi bazı geliştirme platformlarınca desteklenmiyor olsa dahi…


    0 0

    Basketbolu neden bu kadar çok seviyorum diye düşündüm geçenlerde. Oturduğumuz sitenin basket sahasını futbol oynamak için kullanan onca çocuk ve genç gibi bir gerçek varken ben neden bu spora böylesine sevdalıydım. İnanılmaz enerjisi ve sürekli değiştirdiği NBA şapkaları ile rahmetli İsmet Badem mi sevdirmişti? Yoksa final serisi maçları sabahın kaçında olursa olsun uyanamayıp okula geç gitmeme neden olan majestelerinin maçları mı? Basketbolun tüm efsanelerini kendi kardeşiymiş gibi tanıyan ve maçları kendine has heyecanı ile anlatan Murat Murathanoğlu muydu yoksa?

    Belki de Koraç kupasını alarak Avrupa'da bir ilke imza atan Efes'in Abdi İpekçi salonundaki Stefanel Milano maçına girmek için kuyrukta beklerken arabasından bizi seyreden yaşıtım Mirsad Türkcan'ın onca seyirciyi coşkuyla selamlamasıydı. Kim bilir belki de hücum süresi henüz otuz saniyeyken Peter Naumovski'nin eliyle tshirt'ünün sağ yakasını ağzına götürerek verdiği setin adıydı. Belki de zamanında her gün büyük bir iç motivasyonla gittiğim turuncu bankanın CBL(corporate basketball league) seçmelerinde koçun bana gelip "abi kusura bakma" dedikten sonra yaşımı öğrenip "sen ciddi misin abi? Ben bu kadar büyük olduğunu bilmiyordum. Çok daha genç duruyorsun. Basketbol sevgine hayran kaldım" söylemine rağmen takıma almayışı ve Bill Murray'ın Space Jam'de Larry Bird ile olan konuşmasında ona "You can't play" demesini hatırlayışım mıydı? İnanın hiç bilmiyorum. Ama çok sevip de hiç bir zaman beceremediğim bu oyunu mesleki çalışmalarımda kullanmaya bayılıyorum. İşte öyle bir çalışmanın girizgahındasın şu anda sevgili okur :)

    cumartesi gecesi çalışmasındaki amacım Azure platformundaki SignalR hizmetini kullanarak abone programlara çeşitli tipte bildirimlerde bulunabilmekti. Normal SignalR senaryosundan farklı olarak istemciler ve tetikleyici arasındaki eş zamanlı iletişimi(Real Time Communications) Azure platformundaki bir SignalR servisi ile gerçekleştirmek istemiştim. Senaryoda bildirimleri gören en az bir istemci(ki n tane olması daha anlamlı), local ortamda çalışan ve bildirim yayan bir Azure Function uygulaması ve Azure platformunda konuşlandırılan bir SignalR servisi olmasını planlamıştım. Ayrıca Azure üzerinde koşan bu SignalR servisini Serverless modda çalışacak şekilde ayarlamayı planlıyordum. Bir takım sonuçlara ulaşmayı başardım. Şimdi çalışmaya ait notları derleme zamanı. Öyleyse ne duruyoruz. Haydi başlayalım.

    SignalR servisi tüm Azure fonskiyonları ile kullanılabilir. Örneğin Azure Cosmos DB'deki değişiklikleri SignalR servisi ile istemcilere yollayabiliriz. Benzer şeyi kuyruk mesajlarını veya HTTP taleplerini işleyen Azure fonksiyonları için de sağlayabiliriz. Kısacası Azure fonksiyonlarından yapılan tetiklemeler sonrasında SignalR servislerinden yararlanarak bağlı olan aboneleri bilgilendirebiliriz. Şimdi WestWorld'ün gereksinimlerini tamamlayaraktan örneğimizi geliştirmeye başlayalım.

    Ön Gereksinimler

    Azure platformunda SignalR servisini oluşturmadan önce WestWorld(Ubuntu 18.04, 64bit) tarafında Azure Function geliştirebilmek için gerekli kurulumları yapmam gerekiyordu. İlk olarak Azure Functions Core Tools'un yüklenmesi lazım. Aşağıdaki terminal komutları ile bunu gerçekleştirmek mümkün. Önce Microsoft ürün anahtarını Ubuntu ortamına kaydediyor ve sonrasında bir güncelleme yapıp devamında azure-functions-core-tools paketini yüklüyoruz.

    curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
    sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
    
    sudo apt-get update
    
    sudo apt-get install azure-functions-core-tools

    Kurulumdan sonra terminalden Azure Function projeleri oluşturmaya başlanabilir. Lakin bu işin Visual Studio Code tarafında daha kolay bir yolu var. O da Azure Functions isimli aracı kullanmak.

    Visual Studio Code'a gelen bu araçla kolayca Azure Function projeleri oluşturabiliriz.

    Azure SignalR Servisinin Hazırlanması

    Adım adım ilerlemeye çalışalım. Öncelikle Azure platformunda bir SignalR servisi oluşturmamız gerekiyor. Ben Azure Portal adresinden SignalR Service öğesini aratarak işe başladım. Sonrasında aşağıdaki ekran görüntüsünde yer alan bilgiler ile servisi oluşturdum.

    Free Tier planında, learning-rg Resource Group altında, basketcini.service.signalr.net isimli bir SignalR servisimiz var. Bu servisinin oluşması biraz zaman alabilir ki ben bir süre beklediğimi hatırlıyorum. Servis etkinleştikten sonra özelliklerine giderek Serverless modda çalışacak şekilde ayarlayabiliriz. Bunun için Service Mode özelliğini Serverless'a çekmek yeterli. Tabii ekran görüntüsünden de fark edeceğiniz üzere PREVIEW modunda. Kuvvetle muhtemel sizin denemelerinizi yapacağınız durumda son halini almış olabilir.

    Bu SignalR servisi ile local makinede çalışacak ve tetikleyici görevini üstlenecek Azure Function uygulamasının haberleşebilmesi için, Key değerlerine ihtiyacımız olacak. Bu değerleri Azure Function uygulamasının local.settings.json dosyasında kullanmamız gerekiyor. O nedenle aşağıdaki ekran görüntüsündeki gibi ilgili değerleri kopyalayıp güvenli bir yerlerde saklayın.

    Azure Functions Projesinin Oluşturulması

    Yüklenen Azure Functions aracından Create New Project seçimini yaparak ilerleyebiliriz. Proje için bir klasör belirleyip(Ben NotifierApp isimli klasörü kullandım) dil olarak C#'ı tercih ederek devam edelim. Sonrasında Create Function seçeneği ile projeye Scorer isimli bir fonksiyon ekleyelim. Ben bu işlem sırasında sorulan sorulara aşağıdaki cevapları verdim. Siz kendi projenize özgün hareket ederseniz daha iyi olabilir. Özetle HTTP metodları ile tetiklenen bir fonksiyon söz konusu diyebiliriz.

    Fonksiyon Adı : Scorer
    Klasör : NotifierApp
    Tipi : Http Trigger
    Namespace : Basketcini.Function
    Erişim Yetkisi : Anonymous

    Örnekte Table Storage seçeneği değerlendirilmiştir. Bunun için öncelikle Azure Portal üzerinde learningsignalrstorage isimli bir Storage Account oluşturdum ve Access Keys kısmında verilen Connection Strings bilgisini kullandım. Yani bildirimlerin depolanacağı Storage alanını sevgili Azure'a devrettim. Çünkü WestWorld'ün disk kapasitesi epeyce azalmış durumdaydı :P

    Azure Functions Projesinde Yapılanlar

    Azure fonksiyonu oluşturulduktan sonra elbette biraz kodlama yapmamız gerekecek. Ama öncesinde bizim için gerekli nuget paketlerini yüklemeliyiz. Aşağıdaki terminal komutlarını NotifierApp klasöründe çalıştırarak devam edelim.

    dotnet add package Microsoft.Azure.WebJobs.Extensions.EventGrid 
    dotnet add package Microsoft.Azure.WebJobs.Extensions.SignalRService 
    dotnet add package Microsoft.Azure.WebJobs.Extensions.Storage

    Önemli değişikliklerden birisi local.settings.json dosyasında yer alıyor. Burada Azure SignalR servisine ait Connection String bilgisi ve CORS tanımı(Senaryoya göre isimsiz tüm istemciler Azure Function Api'sini kullanabilecek) eklemek lazım. Nasıl yapıldığını söylemek isterdim ama gitignore dosyasında bu json içeriğini dışarıda bırakmışım. Yani hatırlamıyorum :) Yani sizin keşfetmeniz gerekecek ;)

    Bunun haricinde skor durumunu ve anlık olarak meydana gelen olay bilgisini tutan Timeline ve Action isimli sınıfları da aşağıdaki gibi kodlayabiliriz. Biliyorum henüz senaryo tam olarak şekillenmiş değil. Ama çalışma zamanına geldiğimizde ne olduğunu gayet iyi anlayacaksınız. Action sınıfı ile başlayalım.

    namespace Basketcini.Function
    {
        /*
            Table Storage'e yazılacak veri içeriğini temsil eden sınıftır.
            Azure Table Storage'a aşağıdaki özellikler birer alan olarak açılacaktır.
         */
        public class Action
        {
            public string PartitionKey { get; set; }
            public string RowKey { get; set; }
            public string Player { get; set; }
            public string Summary { get; set; }
        }
    }

    ve Timeline sınıfımız;

    namespace Basketcini.Function
    {
        /*
            Abonelere döndürülecek veri içeriğini taşıyacan temsili sınıftır.
            Kim, hangi olayı gerçekleştirdi bilgisini tutar.
        */
        public class Timeline
        {
            public string Who { get; set; }
            public string WhatHappend { get; set; }
        }
    }

    Scorer isimli Function sınıfında da üç metod bulunuyor. Birisi tetikleyici olarak yeni bir olay gerçekleştirmek için, birisi istemcinin kendisini SignalR Hub'ına bağlaması için(negotiation aşaması), birisi de servisin istemciye olay bildirimlerini basması için(push message aşaması) Her zaman ki gibi kod içerisindeki yorum satırlarında anladıklarımı basitçe anlatmaya çalıştım.

    using System;
    using System.IO;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Azure.WebJobs.Extensions.Http;
    using Microsoft.Azure.WebJobs.Extensions.SignalRService;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Logging;
    using Newtonsoft.Json;
    
    namespace Basketcini.Function
    {
        public static class Scorer
        {
            /*
            Scorer fonskiyonu HTTP Post tipinden tetiklemeleri karşılar.
            Oluşan aksiyonları saklamak için Table Storage kullanılır. Actions isimli tablo Table niteliği ile bildirilmiştir.
            Ayrıca gerçekleşen olaylar bir kuyruğa atılır(Queue niteliğinin olduğu kısım)
            Console'a log yazdırmak için ILogger türevli log değişkeni kullanılır.
            */
            [FunctionName("Scorer")]
            public static async Task<IActionResult> Run(
                [HttpTrigger(AuthorizationLevel.Function, "post")] Timeline timelineEvent,
                [Table("Actions")]IAsyncCollector<Action> actions,
                [Queue("new-action-notification")]IAsyncCollector<Timeline> actionNotifications,
                ILogger log)
            {
                log.LogInformation("HTTP tetikleme gerçekleşti");
                log.LogInformation($"{timelineEvent.Who} için {timelineEvent.WhatHappend} olayı");
    
                /* HTTP Post metodu ile gelen timeline bilgilerini de kullanarak bir Action nesnesi 
                oluşturuyor ve bunu Table Storage'e atıyoruz.
                Amaç, meydana gelen olaylarla ilgili gelen bilgileri bir tabloda kalıcı olarak saklamak.
                Pek tabii bunun yerine farklı repository'ler de tercih edilebilir. Cosmos Db gibi örneğin.
                */
                await actions.AddAsync(new Action
                {
                    PartitionKey = "US",
                    RowKey = Guid.NewGuid().ToString(),
                    Player = timelineEvent.Who,
                    Summary = timelineEvent.WhatHappend
                });
    
                /* 
                    new-action-notification ile ilintili olan kuyruğa gerçekleşen olay bilgilerini atıyoruz.
                    İstemci tarafını bu kuyruk içeriği ile besleyebiliriz.
                */
                await actionNotifications.AddAsync(timelineEvent);
    
                return new OkResult();
            }
    
            /*
            Azure SignalR servisine bağlanmak için kullanılan metodumuz. 
            HTTP Post ile tetiklenir.
            Fonksiyon bir SignalRConnectionInfo nesnesini döndürür.
            Bu nesne Azure SignalR'a bağlanırken gerekli benzersiz id ve access token bilgisini içerir.
            SignalR Hub-Name olarak notifications ismi kullanılır.
             */
            [FunctionName("negotiate")]
            public static SignalRConnectionInfo GetNotificationSignal(
                [HttpTrigger(AuthorizationLevel.Anonymous, "post")]HttpRequest request,
                [SignalRConnectionInfo(HubName = "notifications")]SignalRConnectionInfo connection,
                ILogger log
            )
            {
                log.LogInformation("Negotiating...");
                return connection;
            }
    
            /*
            Abone olan tarafa veri göndermek (push) için kullanılan fonksiyondur.
            QueueTrigger niteliğindeki isimlendirme ve tipin Scorer fonksiyonundaki ile aynı olduğuna dikkat edelim.
            İstemciye mesaj taşıyan nesne bir SignalRMessage örneğidir. 
            Bu nesnenin Arguments özelliğinde timeline içeriği (yani gerçekleşen maç olayları) taşınır.
            Peki aboneler buradaki olayları nasıl dinleyecek dersiniz? Bunun içinde Target özelliğine atanan içerik önem kazanır. 
            Örneğimizide aboneler 'actionHappend' isimli olayı dinleyerek mesajları yakalayacaktır.
             */
            [FunctionName("PushTimelineNotification")]
            public static async Task PushNofitication(
                [QueueTrigger("new-action-notification")]Timeline timeline,
                [SignalR(HubName = "notifications")]IAsyncCollector<SignalRMessage> message,
                ILogger log
            )
            {
                log.LogInformation($"{timeline.Who} için gerçekleşen olay bildirimi");
    
                await message.AddAsync(
                    new SignalRMessage
                    {
                        Target = "actionHappend",
                        Arguments = new[] { timeline }
                    }
                );
            }
        }
    }

    İstemci Uygulama Tarafı

    İstemci tarafı Node.js tabanlı basit bir Console uygulaması. Aslında web tabanlı bir arayüzü takip etmem gerekiyordu ancak amacım kısa yoldan SignalR servisinden akan verileri görmek olduğundan Node.js kullanmayı tercih ettim. Siz istemci tarafında tamamen özgünsünüz. SignalR tarafı ile rahat konuşabilmek için @aspnet/signalr isimli npm paketini kullanabiliriz. Terminalden aşağıdaki komutları kullanarak kobay istemcimizi oluşturalım.

    mkdir FollowerApp
    cd FollowerApp
    npm init
    touch index.js
    npm install @aspnet/signalr

    İstemci tarafında index.js ve package.json dosyalarını kodlayacağız. Aşağıda index sınıfına ait kod içeriğini bulabilirsiniz. Uygulama Hub'a bağlandıktan sonra bildirimleri dinler modda yaşamını sürdürecek diyebiliriz.

    const signalR = require("@aspnet/signalr"); // signalR istemci modülünü bildirdik
    
    /* 
        Hub bağlantı bilgisini inşa ediyoruz.
        withUrl parametresi Azure Function uygulamasının yayın yaptığı adrestir
    */
    const connection = new signalR.HubConnectionBuilder()
        .withUrl('http://localhost:4503/api')
        .build();
    
    console.log('Bağlantı sağlanıyor...');
    
    /*
        Bağlantıyı başlatıyoruz. Başarılı ise then metodunun içeriği,
        bir hata oluşursa da catch metodunun içeriği çalışır.
    */
    connection.start()
        .then(() => console.log('Bağlantı sağlandı...'))
        .catch(console.error);
    
    /*
        actionHappend olayını dinlemeye başladık.
        Eğer SignalR servisi üzerinden bir push mesajı söz konusu olursa
        bu olay üzerinden geçeceği için istemci tarafından yakalanıp
        doSomething metodu çağırılacaktır.
        doSomething'e gelen parametre Azure Function'daki
        PushTimelineNotification fonksiyonundan dönen mesajın Arguments içeriğini taşır.
    
    */
    connection.on("actionHappend", doSomething);
    
    function doSomething(action) {
        console.log(action);
    }
    
    connection.onclose(() => console.log('Bağlantı koparılıyor...'));

    ve package.json

    {
      "name": "followerapp",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "dev": "node index.js"
      },
      "author": "",
      "license": "ISC",
      "dependencies": {
        "@aspnet/signalr": "^1.1.2"
      }
    }

    Çalışma Zamanı(NotifierApp Uygulaması)

    Bu adam neler anlattı, neler yazdı diyor gibisiniz biliyorum. O nedenle çalışma zamanına geçmeden önce senaryodan bahsetmem çok doğru olacaktır. WestWorld üzerinde NotifierApp isimli Azure Function uygulaması ayağa kalkar. Bu, Azure SignalR servisi ile haberleşen programımız. Postman ile hayali olarak o anda oynanan bir basketbol maçından çeşitli bilgiler göndereceğiz. Sayı oldu, blok yapıldı vs gibi. Bu bilgiler Azure tarafındaki SignalR servisimiz tarafından karşılanacak ve Table Storage üstünde kuyruğa yazılacak. Yine WestWorld üzerinde çalışan bir başka uygulama(Etkili bir görsellik için bir web sayfası ya da konuyu anlamak için bir console uygulaması olabilir) Local ortamda çalışan Azure Function servisine bağlanıp actionHappend olaylarını dinleyecek. Postman üzerinden maça ait bir basketbol olayı gönderildikçe bu bilgilerin tamamının yer aldığı kuyruk içeriği abone olan istemcilere otomatik olarak dağıtılacak. Sonuçta canlı bir maçın gerçekleşen anlık olayları bu haber kanalını dinleyen istemcilerine eş zamanlı olarak basılmış olacak(en azından senaryonun bu şekilde çalışmasını bekliyoruz)

    Yazılan Azure Function uygulamasını çalıştırmak için terminalden aşağıdaki komutu vermek yeterli. Tabii bu komutu Azure Function projesinin olduğu klasörde icra etmeliyiz ;)

    func host start

    Function uygulamamız şu anda local ortamda çalışır durumda olmalı ve Azure SignalR ile haberleşmesi gerekli. En azından WestWorld üzerinde bu şekilde işledi. Şimdi Postman aracını kullanarak api/Scorer adresine bir HTTP Post talebi gönderebiliriz. Örneğin aşağıdaki gibi.

    Url : http://localhost:4503/api/Scorer
    Method : HTTP Post
    Body : {
    "Who":"Mitsiç",
    "WhatHappend":"3 sayılık basket. Skor 33-21 Anadolu Efes önde"
    }

    Bir şeyleri doğru yazmış olmalıyım ki log mesajlarında istediğim hareketliliği gördüm. Hatta Azure Storage tarafında bir tablonun oluşturulduğunu ve gönderdiğim bilginin içerisine yazıldığını da fark ettim(Tekrar eden bilgileri nasıl normalize etmek gerekir bunun yolunu bulmak lazım) Şu aşamaya gelen okurlarım, umarım sizler de benzer sonuçları görmüşsünüzdür.

    Çalışma Zamanı(İstemci/Abone olan taraf)

    Bildirim yapmayı başardık. Bildirimlerin kuyruğa gittiğini de gördük. Peki ya abonelerden ne haber? Senaryonun tam işlerliğini görmek için her iki uygulamayı da birlikte çalıştırmak lazım elbette. Node.js tabanlı FollowerApp için terminalden aşağıdaki komutu vermek yeterli.

    npm run dev

    İlk ekran görüntüsü istemci ile Azure SignalR servisinin, Azure Function uygulaması aracılığıyla el sıkışmasını gösteriyor.

    Alt ekran görüntüsünde dikkat edileceği üzere Negotiation başarıyla sağlandıktan sonra bir id ve token bilgisinin üretildiği görülmekte. Buradaki çıktı, Azure Function uygulamasındaki negotiate sonrası döndürdüğümüz connection bilgisine ait. Dikkat çekici noktalardan birisi de Web Socket adresi. Görebildiniz mi?

    İkinci ekran görüntüsünde http://localhost:4503/api/Scorer adresine HTTP Post talebi ile örnek bir olay bilgisi gönderilmekte. Bu talep sonrası uygulamalardaki log hareketliliklerine dikkat etmek lazım. Oluşan içerik bağlı olan istemciye yansımış olmalıdır. Bu yılın flaş takımı Anadolu Efes'ten 4 ve 5 numara pozisyonlarında oynayabilen ve üçlük yüzdesi de fena olmayan Moaerman epey ribaund toplamış sanki.

    Üçüncü çalışma zamanı görüntüsünde ekrana ikinci bir istemci dahil etmekteyiz. Bu durumda push edilen bilgiler bağlı olan tüm abonelere gönderilecektir ki istediğimiz senaryolardan birisi de bu(Bırayn Danstın mı? Yok artık Babi diksın mı? :D )

    Eğer bu senaryoda yaptığımız gibi bir maçın canlı anlatımını çevrimiçi tüm abonelere göndermek istiyorsak, sonradan dahil olanların maçın başından itibaren kaçırdıkları olayları da görmesini isteyebiliriz. Burada Table Storage veya benzeri bir depoda maç bazlı tutulacak verileri, istemci ilk bağlandığında ona nasıl yollayabiliriz doğrusu çok merak ediyorum. İşte size güzel bir TODO ;)

    Ben Neler Öğrendim?

    Aslında hepsi bu. Temel bir kurgu ile Azure tarafındaki SignalR servisimizi kullanarak bir push notification sürecini deneyimledik diyebilirim. Her cumartesi gecesi çalışmasında olduğu gibi bu uygulamadan da bir şeyler öğrendim elbette. Bunları aşağıdaki gibi sıralayabilirim. Unutana kadar bendeler :)

    • Azure tarafında bir SignalR Servisinin nasıl oluşturulacağını
    • Geliştirme ortamında bir Azure Function projesinin nasıl inşa edilebileceğini
    • SignalR üzerinden Hub dinleyicisi istemcilerde @aspnet/signalr npm paketinin nasıl kullanılabileceğini
    • Azure Storage oluşturmadan Function projesindeki Table Storage'ın kullanılamayacağını
    • SignalR servisini kullanan Azure Function projesinin herhangi bir istemci tarafından kullanılabilmesi için CORS tarafında '*' kullanılması gerektiğini(Bunu makalede bulamayacaksınız sizin keşfetmeniz gerekebilir:( )
    • Azure Function tarafında abonelerin SignalR ile el sıkıştığı fonksiyon adının 'negotiate' olması gerektiğini(Farklı bir isim kullanınca istemci tarafında HTTP 404 NotFound hatası aldım)
    • Benzer şekilde SignalR Hubname olarak notifications kullanılması gerektiğini(Farklı bir isimlendirme kullanınca oluşan bilgilerin SignalR servisi tarafından yorumlandığını ama abonelere akmadığına şahit oldum)

    Böylece geldik doğduğum, yaşadığım ve asla kopamayacağım İstanbul plakalı cumartesi gecesi derlemesinin sonuna. Umarım sizler için de yararlı bir çalışma olmuştur. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.


    0 0

    Sir Ken Robinson, çocukların hayal güçlerini sınırlayan eğitim sistemini eleştirdiği TED'in en çok izlenen sunumunda William Shakespeare ile ilgili güzel bir anektod paylaşır. Konuşmasının ilgili bölümünde profesör onun bir zamanlar yedi yaşında bir çocuk olduğunu dile getirir. Kısa bir an için duraksar ve ne diyeceğini merak eden seyirciye "...Shakespeare'i hiç çocuk olarak düşünmemiştiniz, değil mi?" der :)

    Hepimiz onu ünlü İngiliz şair ve yazar olarak bilir Romeo Juliet, Macbeth, Othello ve diğer trajedileri ile hatırlarız. Hatta "olmak yada olmamak, işte bütün mesele bu" sözleri hafızalarımıza kazınmıştır. Ancak çoğumuz onun da bir zamanlar çocuk olduğunu ve bir öğretmenin edebiyat dersine girdiğini düşünmeyiz(Bunu Ken Robinson gayet güzel bir şekilde düşündürtüyor) Onun da hepimiz gibi çocukken kurduğu hayaller olduğunu bu cümleleri duyana kadar da fark etmeyiz. Çok şükür ki sekiz numaralı çalışmamın içerisinde geçen bir kelime benim onu, onun yardımıyla Sir Ken Robinson'u ve sonrasında da bu güzel anekdotu hatırlamamı sağladı. Nihayetinde Shakespeare'in ölümsüz eserlerinden olan Hamlet'in Heroku tarafından bana önerilmesi işte bu kısa girizgahın hayat bulmasına vesile oldu. 

    Sekiz numaralı örnekteki amacım node.js ile çalıştırılan basit bir React uygulamasını Heroku üzerine taşımaktı. React ile node haberleşmesinde express paketini kullanmıştım. Bu paket deneyimlediğim kadarıyla HTTP yönlendiricisi olarak kullanılmaktaydı. React tarafına gelen HTTP taleplerini karşılarken kullanılabilmekte. Diğer yandan React tarafında çok fazla tecrübem olmadığından benim için hala kapalı kutu olma özelliğini taşıyor. Bir nevi ona da merhaba demek istediğim bir çalışma olduğunu ifade edebilirim.

    Heroku 2007 yılında işe başladığında sadece Ruby on Rails bazlı web uygulamalarına destek veren bir bulut bilişim sistemiydi ancak Platform as a Service(PaaS) olarak olgunlaştıktan sonra Java, Node.js, Scala, Python, Go, Closure ve benzeri bir çok dil ile geliştirilen uygulmalar için de hizmet vermeye başladı. Aslında heroku üzerindeki ilk denememi 2018 yılında yapmış ve şöyle bir yazı yazmıştım. Teknolojinin gelişimi düşünüldüğünde aradan yadsınamayacak kadar çok zaman geçmiş diyebilirim. O yüzden bu tip platformlara ara ara dönüş yaparak farklı enstrümanlarla kullanmayı denemek güncel kalmamız açısından önemli. Öyleyse vakit kaybetmeden sekiz numaralı Cumartesi gecesi çalışmasını derlemeye başlayalım.

    Gerekli Hazırlıklar

    Tabii öncelikle Heroku üzerinde bir hesap açmak gerekiyor. Ben gerekli hesabı açtıktan sonra WestWorld(Ubuntu 18.04, 64bit) üzerinde Heroku CLI(command-line interface) kurulumunu da yaptım. Böylece heroku ile ilgili işlemlerimizi terminal komutlarını kullanarak kolayca gerçekleştirebiliriz.

    sudo snap install --classic heroku

    Kurulum sonrası login olmamız gerekecektir.

    heroku login -i

    Yukarıdaki terminal komutunu çalıştırdıktan sonra credential bilgileri sorulur(-i parametresini heroku login bilgilerinin kalıcı olması için kullanabiliriz) Heroku tarafı ile iletişimi kurduğumuza göre uygulamanın çatısını oluşturmaya başlayabiliriz. Öncelikle app isimli bir klasör açıp aşağıdaki terminal komutu ile node tarafını başlatalım. Biraz sonra kuracağımız React uygulamamız ile konuşacağı basit node sunucusu bu klasörde yer alacak.

    npm init

    Bazı yardımcı paketlerimiz var. Bunları şu terminal komutu ile yükleyebiliriz.

    npm i --save-dev express nodemon concurrently

    express, servis tarafını daha kolay kullanabilmemiz için gerekli özellikleri sunan bir paket. nodemon ile de node.js tarafında yapılan değişikliklerin otomatik olarak algılanması sağlanıyor. Yani uygulamayı tekrar başlatmaya gerek kalmadan kod tarafındaki değişikliklerin çalışma zamanına yansıtılması sağlanabilir. concurrently paketi hem express hem react uygulamalarının aynı anda başlatılması için kullanılmakta. Paket yüklemeleri tamamlandıktan sonra app kök klasörü altında server.js isimli bir dosya oluşturup kodlamasını sonradan tamamlamak üzere çalışmamıza devam edebiliriz.

    React Uygulamasının Oluşturulması

    React uygulmasını standart bir hello-world şablonu şeklinde açacağız. Bunun için aşağıdaki terminal komutunu kullanmamız yeterli.

    npm i -g create-react-app
    create-react-app fromwestworld

    Bu arada create-react-app komutu ile uygulamayı oluştururken şunu fark ettim ki proje adında sadece küçük harf kullanılabiliyor. İlerde değişir mi, siz bunu okurken değişmiş midir, neden böyledir tam bilemiyorum ama bir unix isimlendirme standardından kaynaklıdır diye düşünüyorum(Fikri olan?)

    Bir süre geçtikten sonra fromwestworld klasörü içerisinde React için gerekli ne varsa oluşturulduğunu görürüz. Oluşturulan bu şablonu çok fazla bozmadan kullanabiliriz. 

    Şimdi geliştirme ortamı için fromwestworld klasöründeki package.json içerisine bir proxy tanımı ekleyerek devam edelim. Biraz sonra kodlayacağımız node sunucumuz 5005 numaralı porttan yayın yapacak(geliştirme ortamı için farklı bir portta tercih edilebilir) Bu bildirim ile React uygulmasının geliştirme ortamında konuşacağı servis adresi ifade edilir. Bir başka deyişle React'a gelen HTTP taleplerinin yönlendirileceği adresi belirtiyoruz.

    "proxy": "http://localhost:5005",

    Şablonla gelen app.js içeriğini aşağıdaki gibi değiştirebiliriz. İçeriğin şu anda çok fazla önemi yok. Hedefimiz olan Heroku deployment için mümkün mertebe basit kodlar kullanmakta yarar var.

    import React, { Component } from 'react';
    import logo from './logo.svg';
    import './App.css';
    
    class App extends Component {
      render() {
        return (
          <div className="App"><header className="App-header"><img src={logo} className="App-logo" alt="logo" /><p>
                Edit <code>src/App.js</code> and save to reload.</p><a
                className="App-link"
                href="https://www.buraksenyurt.com"
                target="_blank"
                rel="noopener noreferrer">
                Bu benim blog sayfamdır :)
              </a></header></div>
        );
      }
    }
    
    export default App;

    React uygulamasının iletişimde olacağı sunucuya ait server.js dosyasını da aşağıdaki gibi yazabiliriz. 

    var express = require('express');
    var app = express();
    var path = require('path');
    var port = process.env.PORT || 5005; //heroku'nun portu veya local geliştirme için belirlediğimiz 5005 nolu port
    
    // statik klasör bildirimini yapıyoruz
    app.use(express.static(path.join(__dirname, 'fromwestworld/build')));
    
    //canlı ortamdaysak yani uygulamamız Heroku'ya alınmışsa
    if (process.env.NODE_ENV === 'production') {
        // build klasöründen index.html dosyasını alıp get talebinin karşılığı olarak istemciye sunuyoruz
        app.use(express.static(path.join(__dirname, 'fromwestworld/build')));
        app.get('*', (req, res) => {
            res.sendfile(path.join(__dirname = 'fromwestworld/build/index.html'));
        })
    }
    
    // Eğer canlı ortamda(heroku'da) değilsek ve amacımız sadece localhost'ta test ise
    // index.html'i public klasöründen sunuyoruz
    app.get('*', (req, res) => {
        res.sendFile(path.join(__dirname + '/fromwestworld/public/index.html'));
    })
    
    // express sunucusunu çalıştırıyoruz
    app.listen(port, (req, res) => {
        console.log(`sunucumuz ${port} nolu porttan yayındadır`);
    })

    Temel olarak bulunulan ortama(development veya production) göre public klasöründe yer alan(ve benim üzerinde hiçbir değişiklik yapmadığım) index.html sayfasının sunulması söz konusu. Kod tarafındaki bu değişiklilere ilaveten root klasör olarak düşüneceğimiz app altındaki package.json dosyasındaki scripts kısmını da kurcalamalıyız. Güncel hali aşağıdaki gibi.

    "scripts": {
    "client-install": "npm install --prefix fromwestworld",
    "start": "node index.js",
    "server": "nodemon index.js",
    "client": "npm start --prefix fromwestworld",
    "dev": "concurrently \"npm run server\" \"npm run fromwestworld\""
    }

    Bunlardan start haricindekiler npm run arkasına eklenen komutlardır. Örneğin npm start ile node index.js komutu ve dolayısıyla uygulama çalışır. Diğer yandan npm run server ile nodemon devreye alınır ve kodda yapılan değişiklik anında çalışma zamanına yansır. npm run client, sunucuyu başlatmadan react uygulamasını çalıştırma görevini üstlenir. npm run client-install sayesinde ise React uygulaması için gerekli tüm bağımlılıklar ilgili ortama(örnekte Heroku olacaktır) yüklenir. npm run dev ile development ortamı ayağa kalkar ve hem node sunucusu hem de react uygulaması aynı anda başlatılır.

    Uygulamayı komple çalıştırmak için app klasöründeyken

    npm run dev

    terminal komutunu işletebiliriz. concurrently paketi bu noktada bize avantaj sağlamaktadır. Eş zamanlı olarak "npm run server" ve "npm run client" komutlarının işletilmesinde rol alır.

    Bu işlem sonrasında önce node sunucusu yüklenir. Sunucu çalışmaya başladıktan sonra React uygulaması tetiklenir ve localhost:3000 nolu porttan ilgili içeriğe ulaşılır. Hatırlayacağınız üzere node sunucusu 5005 numaralı porttan hizmet veriyordu. React uygulamasında yapılan proxy bildirimi, 3000 nolu adrese gelen HTTP taleplerinin arkadaki node sunucusuna yönlendirilmesinde rol alır. Dolayısıyla React uygulaması çalışmaya başladığında oluşan HTTP Get talebi server.js dosyasındaki get metodlarına düşer. Buradaki bildirimlere göre fromwestworld içerisindeki index.html dosyasının sunulması söz konusudur. index.html içeriğinde dikkat edileceği üzere root id'li bir div elementi vardır. Yine dikkat edilecek olursa index.js tarafı da aşağıdaki gibidir (Hiç değiştirmedim)

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './App';
    import * as serviceWorker from './serviceWorker';
    
    ReactDOM.render(<App />, document.getElementById('root'));
    
    serviceWorker.unregister();

    ReactDOM.render metoduna dikkat edelim. root isimli DOM elementini yakalayıp buraya app isimli bileşenin basılması sağlanmaktadır. Özetlemek gerekirse React uygulaması kendi içeriklerini sunarken arka plandaki node sunucusu ile anlaşmaktadır. Buna göre server.js tarafında sunucu bazlı materyalleri(veri tabanı, asenkron operasyonlar vb) kullanarak bunların react bileşenlerince ele alınması sağlanabilir.

    Çalışmayı gerçekleştiğimde West-World tarafında uygulamanın açılması biraz zaman almıştı. Sizin de benim gibi sebat edip panik yapmadan beklemeniz gerekebilir. İşte çalışma zamanı görüntüleri.

    Her şey yolunda giderse localhost:3000 adresinden aşağıdaki içeriğe ulaşabiliriz.

    Uygulamanın Heroku Platformuna Alınması

    Öncelikle Heroku üzerinde bir uygulama oluşturmamız gerekiyor. Bunu aşağıdaki terminal komutuyla yapabiliriz.

    heroku create

    Bana proje adı olarak Heroku'nun otomatik olarak ürettiği frozen-hamlet-75426 denk geldi. Ayrıca uygulama kodlarını atabilmek için github ve ulaşacağım web adresi bilgisi de iletildi.

    Uygulamanın web adresi https://frozen-hamlet-75426.herokuapp.com/ şeklinde. github adresi ise https://git.heroku.com/frozen-hamlet-75426.git. Hatta sonuçları Heroku Dashboard üzerinden de görebiliriz(Tabii siz örneği denerken güncel hali Heroku üzerinde olmayabilir. Kendiniz için bir tane yapsanız daha iyi olur)

    Uygulama klasöründeki json dosyasında yer alan heroku-postbuild script'i bu aşamada önem kazanıyor. Kodlar git ortamına taşındıktan sonra bir build işlemi gerekiyor. Heroku bu script kısmını ele alıyor.

    "heroku-postbuild":"NPM_CONFIG_PRODUCTION=false npm install --prefix fromwestworld && npm run build --prefix fromwestworld"

    Bu düzenlemenin ardından yazılan kodların geliştirme ortamından github üzerine atılması gerekiyor. Yani Heroku'nun kod deposu olarak github'ı kullandığını ifade edebiliriz. Aşağıdaki komutlarla devam edelim öyleyse.

    heroku git:remote -a frozen-hamlet-75426
    git add .
    git commit -am 'Heroku React Express örneği eklendi'
    git push heroku master

    Yapılanları aşağıdaki gibi özetleyebiliriz.

    1. Heroku için git remote adresini belirle
    2. Tüm değişiklikleri stage'e al
    3. Değişiklikleri onayla(commit)
    4. ve kodun son halini master branch'e push'la

    Kodun github'a alınması aynı zamanda heroku'nun da ilgili uygulamayı gerekli build betiklerini çalıştırarak devreye alması anlamına gelmekte. Dolayısıyla bir süre sonra https://frozen-hamlet-75426.herokuapp.com/ adresine gitmek sonuçları görmemiz açısından yeterli olacaktır.

    Bazı Hatalarım da Olmadı Değil

    Çalışma sırasında bu basit hello-world uygulamasını tek seferde Heroku'ya taşıyamadığımı ifade etmek isterim. Eğer taşıma sırasında sorunlarla karşılaşırsanız bunları görmek için terminalden

    heroku logs --tail

    komutunu kullanabilirsiniz. Yaşadığım sorunları ve çözümlerini aşağıdaki maddelerde bulabilirsiniz.

    • İlk hatam server.js dosyasında process.env.PORT yerine process.env.port kullanmış olmamdı. Heroku ortamı bu port'u anlamadığı için 5005 nolu porttan yayın yapmaya çalıştı ki bu mümkün değildi.
    • İkinci hatam package.json içerisinde ortam için gerekli node engine versiyonunu söylememiş olmamdı. Nitekim Heroku tarafı node'un hangi sürümünü kullanacağını bilmek ister.
    • Diğer problemse bağımlı olunan npm paketleri için package.json dosyasında dependencies yerine devDependencies sektörünü bırakmış olmamdı. Üretim ortamı için dependencies kısmına bakılıyor.
    • Ayrıca .gitignore dosyasını koymayıp node_modules ve package-log.json öğelerini hariç tutmadığım için bu klasörleri de komple push'lamış oldum(Sonraki versiyonda düzelttim tabii)

    Bu hususlara dikkat ettiğimiz takdirde ürünü başarılı bir şekilde yayına almış oluruz. Tabii uygulamanın şu an için yaptığı hiçbir şey yok. Oysa ki PostgreSQL kullanaraktan veri odaklı basit bir Hello World uygulaması pekala geliştirilebilir. Daha önceden sıkça dile getirdiğim üzere bu kutsal görevi siz değerli okurlarıma bırakıyorum :)

    Ben Neler Öğrendim?

    Uzun süre sonra derlemek üzere ele aldığım bu çalışmada Heroku üzerine bir React uygulamasının nasıl alındığını hatırlayıp bilgilerimi tazeleme fırsatı buldum. Kabaca yaptıklarımın üstünden geçtikten sonra öğrendiklerimi aşağıdaki maddeler halinde ifade edebileceğimi düşünüyorum.

    • Heroku'da hesap açma ve uygulama oluşturma adımlarını
    • Heroku CLI üzerinden dağıtım işlemlerinin nasıl yapıldığını
    • Bir node sunucusu üzerinden bir React uygulamasının ayağa kaldırılmasını
    • En temel düzeyde app react bileşeninin nerede nasıl kullanıldığını
    • Deployment sırasında veya çalışma zamanındaki hatalara ait loglara nasıl bakıldığını

    Böylece geldik bir cumartesi gecesi derlemesinin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.


    0 0

    Yıl 2015. Hindistan'ın Bengaluru şehrinde doğan bir Startup(Sonradan San Fransico'da da bir ofis sahibi olacaklar), Microsoft'un BizSpark programından destek buluyor. Kurucuları Rajoshi Ghosh(Aslen bioinformatik araştırmacısı) ve Tanmai Gopal(Bulut sistemleri, fonksiyonel programlama ve GraphQL konusunda uzman) isimli iki Hintli. Şirketlerine şeytanın sanskritçedeki adını veriyorlar; Hasura! Aslında O, fonksiyonel dillerin kralı Haskell ile yazılmış bir platform ve şimdilerde Heroku ile daha yakın arkadaş.

    Ekibin amacı geliştiricilerin hayatını kolaylaştıracak, yüksek hızlı, kolayca ölçeklenebilir, sade ve Kubernetes ile dost PaaS(Platform as a Service) ile BaaS(Back-end as a Service) ortamları sunmak.

    İsimlendirmenin gerçek hikayesi tam olarak nedir bilemiyorum ama eğlenceli bir logoları olduğu kesin :) Startup'ların en sevdiğim yanlarından birisi de bu. Özgün, tabulara takılmadan, etki bırakacak şekilde düşünülen isimleri, renkleri, logoları...Belki de arka planda sessiz sedasız süreçler çalıştıran back-end servislerini birer iblis olarak düşündüklerinden bu ismi kullanmışlardır. Hatta Heroku üzerinde koştuğunu öğrenince Japonca bir kelime olduğunu bile düşünmüştüm. Hu novs?! Ama işin özü verdikleri önemli hizmetler olduğu. Bunlardan birisi de GraphQL motorları.

    API'ler için türlendirilmiş(typed) sorgulama dillerinden birisi olarak öne çıkan GraphQL'e bir süredir uğramıyordum. Daha doğrusu GraphQL sorgusu çalıştırılabilecek şekilde API servis hazırlıklarını yapmaya üşeniyordum. Bu nedenle işi kolaylaştıran ve Heroku üzerinden sunulan Hasura GraphQL Engine hizmetine bakmaya karar vermiştim. Hasura, veriyi PostgreSQL kullanarak saklıyor ve ayrıca API'yi bir Docker Container içerisinden sunuyor. Amacım Hasura tarafında hazırlayacağım iki kobay veri setini, Vue.js tabanlı bir istemcisinden tüketmekti. Basitçe listeleme ve veri ekleme işlerini yapabilsem başlangıç için yeterli olacaktı. Öyleyse ne duruyoruz. 37nci saturday-night-works çalışmasının derlemesine başlayalım. İlk olarak Hasura servisini hazırlayacağız.

    Hasura GraphQL Engine Tarafının Geliştirilmesi

    Pek tabii Heroku üzerinde bir hesabımızın olması gerekiyor. Sonrasında şu adrese gidip elements kısmından Hasura GraphQL Engine'i seçmek yeterli.

    Gelinen yerden Deploy to Heroku diyerek projeyi oluşturabiliriz.

    Ben aşağıdaki bilgileri kullanarak bir proje oluşturdum.

    Deploy başarılı bir şekilde tamamlandıktan sonra,

    View seçeneği ile yönetim paneline geçebiliriz.

    Dikkat edileceği üzere GraphQL sorgularını çalıştırabileceğimiz bir arayüz otomatik olarak sunuluyor. Ancak öncesinde örnek veri setleri hazırlamalıyız. Bunun için Data sekmesinden yararlanabiliriz.

    Arabirimin kullanımı oldukça kolay. Ben aşağıdaki özelliklere sahip tabloları oluşturdum.

    categories isimli tablomuzda unique tipte, get_random_uuid() fonksiyonu ile eklenen satır için rastgele üretilen categoryId ve text tipinden title isimli alanlar bulunuyor. categoryId, aynı zamanda primary key türünden bir alan.

    products tablosunda da UUID tipinden productId, text tipinden description, number tipinden listPrice ve yine UUID tipinden categoryId isimli alanlar mevcut. categoryId alanını, ürünleri kategoriye bağlamak için(foreign key relations) kullanıyoruz. Ama bu alanı foreign key yapmak için Modify penceresine geçmeliyiz. 

    İlişkinin geçerlilik kazanması içinse, categories tablosunun Relationships penceresine gidip önerilen bağlantıyı eklemek gerekiyor. 

    Bu durumda categories üzerinden products'a gidebiliriz. Ters ilişkiyi de kurabiliriz ve bir ürünle birlikte bağlı olduğu kategorinin bilgisini de yansıtabiliriz ki ürünleri çektiğimizde hangi kategoride olduğunu da göstermek güzel olur. Bunu nasıl yapabileceğinizi bir deneyin isterim.

    Hasura'nın Postgresql tarafındaki örnek tablolarımız hazır. İstersek Insert Row penceresinden tablolara örnek veri girişleri yapabilir ve GraphiQL pencresinden sorgular çalıştırabiliriz. Ben yaptığım denemelerle alakalı bir kaç örnek ekran görüntüsü paylaşayım. Arabirimin sağ tarafında yer alan Docs menüsüne de bakabilirsiniz. Burada query ve mutation örnekleri, hazırladığımız veri setleri için otomatik olarak oluşturuluyorlar.

    Örnek Sorgular

    Veri setimizi oluşturduktan sonra arabirim üzerinden bazı GraphQL sorgularını deneyebiliriz. Ben aşağıdaki örnekleri denedim.

    Kategorilerin başlıklarını almak.

    query{
      categories{
        title
      }
    }

    Kategorilere bağlı ürünleri çekmek.

    query{
      categories{
        title
        products{
          description
          listPrice
        }
      }
    }

    Ürünlerin tam listesi ve bağlı olduğu kategori adlarını çekmek.

    query{
      products{
        description
        listPrice
        category{
          title
        }
      }
    }

    Listeleme işlemleri dışında veri girişi de yapabiliriz. Bunun için mutation kullanıldığını daha önceden öğrenmiştim. Örneğin yeni bir kategoriyi aşağıdaki gibi ekleyebiliriz.

    mutation {
      insert_categories(objects: [{
        title: "Çorap",
      }]) {
        returning {
          categoryId
        }
      }

    Hasura, GraphQL API’si arkasında PostgreSQL veri tabanını kullanırken SQLden aşina olduğumuz bir çok sorgulama metodunu da hazır olarak sunar. Örneğin fiyatı 300 birimin üstünde olan ürünleri aşağıdaki sorgu ile çekebiliriz.

    {
      products(where: {listPrice: {_gt: 300}}) {
        description
        listPrice
        category {
          title
        }
      }
    }

    Where metodu sorgu şemasına otomatik olarak eklenmiştir. _gt tahmin edileceği üzere greater than anlamındadır. Yukarıdaki sorguya fiyata göre tersten sıralama opsiyonunu da koyabiliriz. Sadece where koşulu arkasından order_by çağrısı yapmamız yeterlidir.

    {
      products(where: {listPrice: {_gt: 300}}, order_by: {listPrice: desc}) {
        description
        listPrice
        category {
          title
        }
      }
    }

    Çok büyük veri setleri düşünüldüğünde ön yüzler için sayfalama önemlidir. Bunun için limit ve offset değerlerini kullanabiliriz. Örneğin 5nci üründen itibaren 5 ürünün getirilmesi için aşağıdaki sorgu kullanılabilir.

    {
      products(limit: 5, offset: 5) {
        description
        listPrice
        category {
          title
        }
      }
    }

    Hasura Query Engine’in sorgu seçenekleri ile ilgili olarak buradaki dokümanı takip edebilirsiniz.

    İstemci(Vue) Tarafı

    Gelelim ilgili servisi tüketecek olan istemci uygulamamıza. İstemci tarafını basit bir Vue projesi olarak geliştirmeye karar vermiştim. Aşağıdaki terminal komutunu kullanıp varsayılan ayarları ile projeyi oluşturabiliriz. Ayrıca GraphQL tarafı ile konuşabilmek için gerekli npm paketlerini de yüklememiz gerekiyor. Apollo(ilerleyen ünitelerde ondan bir GraphQL Server yazmayı denemiştim), GraphQL servisimiz ile kolay bir şekilde iletişim kurmamızı sağlayacak. Görsel taraf içinse bootstrap kullanabiliriz.

    sudo vue create nba-client
    sudo npm install vue-apollo apollo-client apollo-cache-inmemory apollo-link-http graphql-tag graphql bootstrap --save

    Kod Tarafı

    Vue uygulaması tarafında yapacaklarımız kabaca şöyle(Kod dosyalarındaki yorum bloklarında daha detaylı bilgiler mevcut)

    Components klasörüne tek ürün için kullanılabilecek ProductItem isimli bir bileşen ekliyoruz. Anasayfa listelemesinde tekrarlanacak türden bir bileşen olacak bu. Bileşende product özelliği üzerinden içerideki elementlere veri bağlama işlemini gerçekleştirmekteyiz. {{nesne.özellik}} notasyonlarının nasıl kullanıldığına dikkat edelim.

    <template><div :key="product.productId" class="card w-75"><div class="card-header"><p class="card-text">{{product.description}}</p></div><div class="card-body text-left"><h6 class="card-subtitle mb-2 text-muted">{{product.listPrice}} Lira</h6><h6 class="card-subtitle mb-2">'{{product.category.title}}' kategorisinden</h6></div><div class="card-footer text-right"><a href="#" class="btn btn-primary">Sepete Ekle</a></div><hr/></div></template><script>
    export default {
      name: "ProductItem",
      props: ["product"]
    };</script>

    Ürünlerin listesini gösterebilmek içinse ProductList isimli bir bileşen kullanacağız. Bunu da components altında aşağıdaki gibi yazabiliriz.

    <template><div><!--
          products dizisindeki her bir ürün için product-item öğesi ekliyoruz.
          Bu öğe bir ProductItem bileşeni esasında
          --><product-item v-for="product in products" :key="product.productId" :product="product"></product-item></div></template><script>
    /*
      div içerisinde kullandığımız product-item elementi için ProductItem bileşenini eklememi gerekiyor.
      gql ise GraphQL sorgularını çalıştırabilmemiz için gerekli
    */
    import ProductItem from "./ProductItem";
    import gql from "graphql-tag";
    
    /*
      GraphQL sorgumuz.
      Tüm ürün listeini, kategori adları ile birlikte getirecek
    */
    const selectAllProducts = gql`
      query getProducts{
      products{
        productId
        description
        listPrice
        category{
          title
        }
      }
    }
    `;
    
    /*
      Sorguyu GraphQL API'sine gönderebilmek için apollo'ya ihtiyacımız var.
      products dizisini query parametresine verilen değişken ile çekiyoruz.
    */
    export default {
      name: "ProductList",
      components: { ProductItem }, // Sayfada bu bileşeni kullandığımız için eklendi
      data() {
        return {
          products: []
        };
      },
      apollo: {
        products: {
          query: selectAllProducts
        }
      }
    };</script>

    Ürün ekleme işini ProductAdd isimli bileşen üstleniyor. Yine components sekmesinde konuşlandıracağımız tipin kod içeriği aşağıdaki gibi olmalı.

    <template><!-- Veri girişi için basit bir formumuz var. Input değerlerini v-model niteliklerine verilen isimlerle bileşene bağlıyoruz --><form @submit="submit"><fieldset><div class="form-group w-75"><input
              class="form-control"
              aria-describedby="descriptionHelp"
              type="text"
              placeholder="Ürün bilgisi"
              v-model="description"><small
              id="descriptionHelp"
              class="form-text text-muted">Satışı olan basketbol malzemesi hakkında kısa bir bilgi...</small></div><div class="form-group w-75"><input class="form-control" type="number" v-model="listPrice"><small id="listPriceHelp" class="form-text text-muted">Ürünün mağaza satış fiyatı</small></div><div class="form-group w-75"><input
              class="form-control"
              type="text"
              placeholder="Halledene kadar kategorinin UUID bilgisi :D"
              v-model="categoryId"></div><!-- Kategoriyi drop down olarak nasıl ekleyebiliriz? --></fieldset><div class="form-group w-75 text-right"><button class="btn btn-success" type="submit">Dükkana Yolla</button></div></form></template><script>
    import gql from "graphql-tag";
    //import { InMemoryCache } from "apollo-cache-inmemory";
    
    /*
      Bu veri girişi yapmak için kullanacağımız mutation sorgumuz.
      insert_products'u Hasura tarafında kullanmıştık hatırlarsanız.
    
      mutation parametrelerini belirlerken veri türlerine dikkat etmemiz lazım.
      Söz gelimi listPrice, Hasura tarafında Numeric tanımlandı. CategoryId değeri
      ise UUID formatında. Buna göre case-sensitive olarak veri tiplerini söylüyoruz.
      Aslında bunu anlamak için numeric! yerine Numeric! yazıp deneyin. HTTP 400
      Bad Request alıyor olmalısınız.
    */
    const addNewProduct = gql`
      mutation addProduct(
        $description: String!
        $listPrice: numeric!
        $categoryId: uuid!
      ) {
        insert_products(
          objects: [
            {
              description: $description
              listPrice: $listPrice
              categoryId: $categoryId
            }
          ]
        ) {
          returning {
            productId
          }
        }
      }
    `;
    
    export default {
      name: "ProductAdd",
      data() {
        return {
          description: "",
          listPrice: 0,
          categoryId: ""
        };
      },
      apollo: {},
      methods: {
        /*
        form submit edildiği zaman devreye giren metodumuz.
        $data ile formdaki veri içeriğini description, listPrice ve categoryId olarak yakalıyoruz
        */
        submit(e) {
          e.preventDefault();
          const { description, listPrice, categoryId } = this.$data;
    
          /*
          apollo'nun mutate metodu ile addNewProduct isimli mutation sorgusunu çalıştırıyoruz.
          Sorgunun beklediği değişkenler this.$data ile zaten yakalanmışlardı.
          */
          this.$apollo.mutate({
            mutation: addNewProduct,
            variables: {
              description,
              listPrice,
              categoryId
            },
            refetchQueries: ["ProductList"] // Insert işlemini takiben ürün lstesini tekrardan talep ediyoruz
          });
        }
      }
    };</script>

    Uygulamanın ana bileşeni olan App.Vue'da product-add ve product-list isimli nesnelerimizi aşağıdaki gibi yerleştirebiliriz.

    <template><div id="app"><h2 class="text-left">Yeni Ürün</h2><!-- Bileşenleri altalta dizdik --><product-add/><h2 class="text-left">Basketbol Ürünleri</h2><product-list/></div></template><script>
    /*
      Ana bileşen içerisinde kullanılan alt bileşenlerin import edilmesi
    */
    import ProductList from "./components/ProductList.vue";
    import ProductAdd from "./components/ProductAdd.vue";
    
    export default {
      name: "app",
      components: {
        ProductList,
        ProductAdd
      }
    };
    </script>

    Main.js içerisinde de önemli kodlamalarımız var. Amaç Hasura'yı ve GraphQL'i kullanabilir hale getirmek. Kodlarını aşağıdaki gibi geliştirebiliriz.

    import Vue from 'vue';
    import App from './App.vue';
    import { ApolloClient } from 'apollo-client';
    import { HttpLink } from 'apollo-link-http';
    import { InMemoryCache } from 'apollo-cache-inmemory';
    import 'bootstrap/dist/css/bootstrap.min.css';
    import VueApollo from 'vue-apollo';
    
    Vue.config.productionTip = false;
    
    // Hasura GraphQL Api adresimiz
    const hasuraLink = new HttpLink({ uri: 'https://basketin-cepte.herokuapp.com/v1alpha1/graphql' });
    
    /* 
      Servis iletişimini sağlayan nesne
      GraphQL istemcileri veriyi genellikle cache'de tutar.
      Tarayıcı ilk olarak cache'ten okuma yapar. 
      Performans ve network trafiğini azaltmış oluruz bu şekilde.
    */
    const apolloClient = new ApolloClient({
      link: hasuraLink, // Kullanacağı servis adresini veriyoruz
      connectToDevTools: true, // Chrome'da dev tools üzerinde Apollo tab'ının çıkmasını sağlar. Debug işlerimiz kolaylaşır
      cache: new InMemoryCache() // ApolloClient'ın varsayılan Cache uyarlaması için InMemoryCache kullanılıyor. 
    });
    
    // Vue ortamının GraphQL ile entegre edebilmek için VueApollo kütüphanesini entegre ediyoruz. (https://akryum.github.io/vue-apollo/)
    Vue.use(VueApollo);
    
    /* 
      Vue tarafında GraphQL sorguları oluşturabilmek ve veri girişleri(mutations)
      yapabilmek için ApolloProvider örneği kullanmamız gerekiyor.
      VueApollo'den üretilen bu nesnenin Hasura tarafına işlemleri commit
      edebilmesi içinse yukarıdaki apolloClient'ı parametre olarak atıyoruz
    */
    const apolloProvider = new VueApollo({
      defaultClient: apolloClient
    });
    
    new Vue({
      apolloProvider,// Vue uygulamamızın ApolloProvider'ı kullanabilmesi için eklendi
      render: h => h(App),
    }).$mount('#app');

    TODO (Benim tembelliğimden size düşen)

    Bu servisi JWT Authentication bünyesine almak lazım. İşte size güzel bir araştırma konusu. Başlangıç noktası olarak Auth0'ın şu dokümanına bakılabilir. Ben şu an için sadece HASURA_GRAPHQL_ADMIN_SECRET kullanarak servis adresine erişimi kısıtlamış durumdayım. Zaten büyük ihtimalle yazıyı okuduğunuzda onun yerinde yeller estiğine şahit olacaksınız.

    Çalışma Zamanı

    Hasura servisimiz ve istemci taraftaki uygulamamız hazır. Artık çalışma zamanına geçip sonuçları irdeleyebiliriz. Programı başlatmak için

    npm run serve

    terminal komutunu vermemiz yeterli. Sonrasında http://localhost:8080 adresine giderek ana sayfaya ulaşabiliriz. Aynen aşağıdakine benzer bir görüntü elde etmemiz gerekiyor.

    Yeni ürün ekleme bileşeni konulduktan sonrasına ait örnek bir ekran görüntüsünü de buraya iliştirelim.

    Hatta yeni bir forma eklediğimizde gönderilen Graphql Mutation sorgusundan dönen değer, F12 sonrası Network sekmesinden yakalayabiliriz.

    throw new UnDoneException("Yeni ürün ekleme sayfasında kategori seçiminde combobox kullanımı yapılmalı");

    Ben Neler Öğrendim?

    Doğruyu söylemek gerekirse bu çalışma benim için oldukça keyifliydi. Heroku platformunu oldukça beğeniyorum. Şirkette Vue tabanlı ürünlerimiz var ama onlar üzerinden çok iyi değilim. Dolayısıyla Vue tarafında bir şeyler yapmış olmak beni mutlu ediyor. Peki bu çalışma kapsamında neler mi öğrendim. İşte listem...

    • Heroku'da Docker Container içerisinde çalışan ve PostgreSQL verilerini GraphQL ile sorgulanabilir olarak sunan Hasura isimli bir motor olduğunu
    • Hasura arabirimden tablolar arası ilişkileri nasıl kuracağımı
    • Bir kaç basit GraphQL sorgusunu(sorgularda sayfalama yapmak, ilişkili veri getirmek, where koşulları kullanmak)
    • Vue tarafında GraphQL sorgularının nasıl gönderilebileceğini
    • Component içinde Component kullanımlarını(App bileşeni dışında product-list içinde product-item kullanımı)
    • Temel component tasarlama adımlarını
    • Vue tarafından bir Mutation sorgusunun nasıl gönderilebileceğini ve schema veri tiplerine dikkat etmem gerektiğini

    Böylece geldik bir cumartesi gecesi derlemesinin daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.


    0 0

    İngilizcede bazen gemi kaptanlarına Captain yerine Skipper dendiğini biliyor muydunuz? Aslında Hollandalıların schipper, schip en nihayetinde de ship kelimelerinden türeyerek gelmiş bir ifade. Her ikisi de kaptanı ifade etmekte ama Skipper daha çok bir hitap şekli. Hatta yer yer takım kaptanları veya uçak pilotları için de kullanılıyor. Skipper kelimesinin kullanıldığı yerleri düşününce aklıma The Hunt For Red October filminde USS Dallas kaptanı Mancuso'nun CIA'den Jack Ryan'a "That's right? Skipper's Ramius?" demesi geliyor.

    Esasında bu hitap şeklinin bana anımsattığı daha güzel şeyler var. Blizzard geliştiricilerinin Warcraft II'sini oynadığım zamanlarda insan kuvvetlerindeki gemilere Skipper diye sesleniliyordu. Karakterlerin o müthiş ses efektleri hala aklımda. "Ay ay sör", "Ayy keptın", "Set seyıl", "Sıkipp?", "Andır veyy!" :D Yazılı olarak seslendirmeye çalıştım ama dinleseniz çok daha iyi olabilir. Diğer pek çok karakterin sesi de harikaydı. Mesela köylülerin "Yeş mi lord" diyişindeki şirinlik ya da okçuların tonlamasındaki keskinlik. Youtube'dan silinene kadar şu adresten dinleyebilir veya aratabilirsiniz. Bugünkü konumuza gündem olmasının sebebi ise skipper isimli bir nesne dizisini kullanacak olmamız. Öyleyse başlayalım.

    Öğrenecek bir çok şeyler araştırırken(ki samimi olmak gerekirse 24 saat uykusuz kalıp bir şeyleri öğrensek bile zamanın yetmeyeceği ve güneşe daha uzak bir gezegende yaşamamız gerektiği ortaya çıkıyor) AlaSQL isimli bir çalışma ile karşılaştım. Tarayıcı üzerinde çalışabilen istemci taraflı bir In-Memory veritabanı olarak geçiyor. Tamamen saf Javascript ile yazılmış. Geleneksel ilişkisel veritabanı özelliklerinin çoğunu barındırıyor. Group, join, union gibi fonksiyonellikleri karşılıyor. In-Memory tutulan veriyi kalıcı olarak saklamakta mümkün. Hatta bu noktada localStorage olarak ifade edilen yerel depolama alanlarından veri okunup tekrar yazılabiliyor. IndexedDB veya Excel gibi ürünleri fiziki repository olarak kullanabiliyor. Ayrıca JSON nesnelerle çalışabiliyoruz ki bu da NoSQL desteği anlamına gelmekte. Bu nedenle SQL ve NoSQL rahatlığını bir arada sunan hafif bir veritabanı gibi düşünülebilir.

    Açık kaynak kodlu, dokümantasyonu oldukça zengin bir proje. Yine de endüstriyel anlamda olgunlaştığına dair emareler görülmeden canlı ortamlarda kullanmak riskli olabilir diye düşünürken github projesinden çıkıp org alan adına geçerek biraz daha ciddiye alınmaya başladığını fark ettim. Yine de deneysel çalışmalarda ele almakta yarar var. Benim 25nci cumartesi gecesi çalışmasındaki amacım onu yalın bir React uygulamasında deneyimlemeye çalışmaktı. Öyleyse gelin notlarımızı toparlamaya başlayalım.

    Kurulum ve Hazırlıklar

    Ben her zaman olduğu gibi örneğimi WestWorld(Ubuntu 18.04, 64bit)üzerinde deneyimledim. Ancak komutlar plaform bağımsız olarak ele alınabilir. Ah bu arada sisteminizde node'un yüklü olduğunu varsayıyorum. React uygulamasını kolayca oluşturabilmek için aşağıdaki terminal komutunu kullanabiliriz.

    npx create-react-app submarine

    AlaSQL'i kullanabilmek içinse uygulama klasöründe gerekli npm paketinin yüklenmesi yeterli olacaktır. Ayrıca görselliği zenginleştirmek için ben Bootstrap'i kullanmayı tercih ettim.

    cd submarine
    npm install --save-dev alasql bootstrap

    React şablonu aslında senaryomuzdan bağımsız bir çok gereksiz dosya içerebilir. Bunları silip manifest.json içeriğini bir parça değiştirebiliriz. Örneğin uygulamanın tanımlayıcısı olan short_name ve name değerlerini aşağıdaki hale getirebiliriz.

    {
      "short_name": "Tac-War-Mag",
      "name": "Tactical World Magazine",
    // diğer kısımlar

    Pek tabii en önemli kısım App.js dosyasında yapılanlar. Burası uygulamanın ayağa kalktıktan sonra oluşturulan ana bileşeni(component) Ana sayfanın HTML içeriği ile birlikte SQL ilişkili kodlarını barındırmakta. Ben mümkün mertebe içeriği yorum satırları ile zenginleştirerek açıklamaya çalıştım.

    import React, { Component } from 'react';
    import 'bootstrap/dist/css/bootstrap.css'; // az biraz bootstrap ile görselliği düzeltelim
    import * as alasql from 'alasql'; // alasql ile konuşmamızı sağlayacak modül bildirimimiz
    
    class App extends Component {
    
      /* yapıcı metod gibi düşünebiliriz sanırım
      genellikle local state değişkenlerini başlatmak ve onlara değer atamak
      için kullanılır
      */
      constructor(props) {
        super(props);
    
        /* 
        state'i değişebilir veriler için kullanırız. state değişikliğinde
        bileşenin otomatik olarak yeniden render edilmesi söz konusu olur
        */
        this.state = { skippers: [] };
      }
    
      /*
      componentWillMount metodu, ilgili bileşen Document Object Model'e bağlanmadan
      önce çalışır.
      Bizim örneğimizde veritabanını ve tablo kontrolünün yapılması ve
      yoklarsa yaratılmaları için ideal bir yerdir
      */
      componentWillMount() {
        /*
        Klasik SQL ifadeleri ile TacticalWorldDb isimli bir veritabanı olup
        olmadığını kontrol ediyor ve eğer yoksa oluşturup onu kullanacağımızı belirtiyoruz.
        SQL ifadelerini çalıştırmak için alasql metodunu çağırmak yeterli.
        */
        alasql(`
                CREATE LOCALSTORAGE DATABASE IF NOT EXISTS TacticalWorldDb;
                ATTACH LOCALSTORAGE DATABASE TacticalWorldDb;
                USE TacticalWorldDb;            
                `);
    
        /*  
          Şimdi tablomuzu ele alalım. Submarine isimli tablomuzda
          id, name, displacement ve country alanları yer alıyor. 
          Id alanı için otomatik artan bir primary key'de kullandık.
          Örneği abartmamak adına alan dozajını belli bir seviyede tuttuk.
        */
        alasql(`
                CREATE TABLE IF NOT EXISTS Submarine (
                  id INT AUTOINCREMENT PRIMARY KEY,
                  name VARCHAR(25) NOT NULL,
                  displacement NUMBER NOT NULL,
                  country VARCHAR(25) NOT NULL
                );
              `);
      }
      /*
      İlk satırda yer alan alasql komutu ile Submarine tablosundaki verileri displacement değerine
      göre büyükten küçüğe sıralı olacak şekilde çekiyoruz.
      Ardından state içeriğini bu tablo verisiyle ilişkilendiriyoruz.
      */
      getAll() {
        const submarineTable = alasql('SELECT * FROM Submarine ORDER BY displacement DESC');
        this.setState({ skippers: submarineTable });
        // console.log(submarineTable); // Kontrol amaçlı açıp F12 ile geçilecek kısımda Console sekmesinden takip edebiliriz. Bir JSON array olmasını bekliyoruz
      }
    
      /*
      Bileşen DOM nesnesine bağlandıktan sonra çalışan metodumuzdur.
      Burası örneğin tablo içeriğini çekip state nesnesine almak için
      son derece ideal bir yerdir.
      */
      componentDidMount() {
        this.getAll();
      }
    
      /*
      Yeni bir satır eklemek için aşağıdaki metodu kullanacağız.
      denizaltının adı, tonajı ve menşeği gibi bilgileri
      this.refs özelliği üzerinden yakalyabiliriz. this.refs DOM
      elemanlarına erişmek için kullanılmakta. Bu şekilde 
      formdaki input kontrollerini yakalayıp value niteliklerini
      okuyarak gerekli veriyi çekebiliriz
    
      Insert sorgusu için yine alasaql nesnesinden yararlanıyoruz.
      Bu sefer parametre içeriğini tek ? içerisinde yollamaktayız.
      Parametre değerleri aslında bir json nesnesi içinden yollanıyor.
      key olarak kolon adını, value olarak da refs üzerinden gelen bileşene ait value özelliğini veriyoruz.
      Id alanının otomatik arttırmak içinse autoval fonksiyonu devreye girmekte.
    
      Pek tabii yeni eklenen kayıt nedeniyle bileşeni güncellemek lazım.
      getAll metodu burada devreye girmekte
      */
      addSkipper() {
        const { name, displacement, country } = this.refs;
        if (!name.value) return;
        // console.log(dicplacement.value); // Kontrol amaçlı. Browser'dan F12 ile değerlere bakılabilir
        alasql('INSERT INTO Submarine VALUES ?',
          [{
            id: alasql.autoval('Submarine', 'id', true),
            name: name.value,
            displacement: displacement.value,
            country: country.value
          }]
        );
        this.getAll();
      }
    
      /*
      Silme operasyonunu yapan metodumuz.
      Parametre olarak gelen id değerine göre bir DELETE ifadesi çağırılı
      ve tüm liste tekrardan çekilir.
      */
      deleteSkipper(id) {
        alasql('DELETE FROM Submarine WHERE id = ?', id);
        this.getAll();
      }
    
      /* 
        State değişikliği gibi durumlarda bileşen güncellenmiş demektir. 
        Bu durumda render fonkisyonu devreye girer.
    
        render metodu bir HTML içeriği döndürmektedir.
        form sınıfındaki input kontrollerinin ref niteliklerine dikkat edelim.
        Bunları addSkipper metodunda this.refs ile alıyoruz.
    
        iki button bileşenimiz var ve her ikisinin onClick metodları ilgili fonksiyonları
        işaret ediyor.
    
        HTML sayfası iki kısımdan oluşuyor. Yeni bir veri girişi yaptığımız form ve tablo verisini
        gösteren bölüm. Tablo içeriğini birer satır olarak ekrana basmak için map fonksiyonundan
        yararlanıyoruz. map fonksiyonu lambda görünümlü blok içerisine sırası gelen satır bilgisini
        atıyor. Örnekte ship isimli değişken bu taşıyıcı rolünü üstlenmekte. ship değişkeni üzerinden
        tablo kolon adlarını kullanarak asıl verilere ulaşıyoruz.
      */
      render() {
    
        const { skippers } = this.state;
    
        return (
          <main className="container"><h2 className="mt-4">En Büyük Denizlatılar</h2><div className="row mt-4"><form><div className="form-group mx-sm-3 mb-2"><input type="text" ref="name" className="form-control" id="inputName" placeholder="Sınıfı" /></div><div className="form-group mx-sm-3 mb-2"><input type="text" ref="displacement" className="form-control" id="inputDisplacement" placeholder="Tonajı" /></div><div className="form-group mx-sm-3 mb-2"><input type="text" ref="country" className="form-control" id="inputCountry" placeholder="Sahibi..." /></div><div className="form-group mx-sm-3 mb-2"><button type="button" className="bnt btn-primary mb-2" onClick={e => this.addSkipper()}>Ekle</button></div></form></div><div><table className="table table-primary table-striped"><thead><tr><th scope="col">Sınıfı</th><th scope="col">Tonajı</th><th scope="col">Ülkesi</th><th></th></tr></thead><tbody>
                  {
                    skippers.length === 0 && <tr><td colSpan="5">Henüz veri yok</td></tr>
                  }
                  {
                    skippers.length > 0 && skippers.map(ship => (<tr><td>{ship.name}</td><td>{ship.displacement}</td><td>{ship.country}</td><td><button className="btn btn-danger" onClick={e => this.deleteSkipper(ship.id)}>Sil</button></td></tr>
                    ))
                  }</tbody></table></div></main >
        );
      }
    }
    
    export default App;

    Aslında CRUD(Create Read Update Delete) operasyonlarını sunmaya çalıştığımız bir arayüz var. Senaryomuzda dünyanın en büyük denizaltılarını listelediğimiz, ekleyip çıkarttığımız bir web bileşeni söz konusu. Tahmin edeceğiniz üzere benim hep atladığım bir şey daha var...Güncelleme eksik :| Artık o kısmını da siz değerli okurlarıma bırakıyorum.

    Öyleyse aşağıdaki terminal komutunu vererek uygulamamızı çalıştıralım (Bu arada react uygulamasını şablondan oluşturduğumuz için package.json içerisindeki scripts kısmı otomatik olarak ayarlanmıştır. start anahtar kelimesini kullanmamızın sebebi bu)

    npm run start

    İşte çalışma zamanına ait bir kaç görüntüsü.

    Her şey yeni başlarken ve hiç veri yokken ana sayfa aşağıdaki gibi açılmalıdır.

    Bir kaç satır ekledikten sonraki durum ise şöyle olacaktır.

    Local Storage Nerede?

    Peki veriyi tarayıcımız nerede tutuyor? Sonuçta In-Memory bir veritabanı olduğundan bahsediyoruz. Lakin içerik tarayıcı tarafında bir alanda konumlanıyor. Varsayılan senaryoda veri Local Storage bölümünde depolanmakta. Uygulamayı çalıştırdıktan sonra Chrome DevTools'a geçip Application sekmesine giderek içeriğini görebiliriz. Dikkat edileceği üzere TacticalWorldDb.Submarine isimli bir tablo bulunuyor ve verilerimiz içerisinde JSON nesneler olarak tutuluyor (Diğer yandan depolama alanı olarak componentWillAmount metodu içerisindeki SQL komutumuzda LocalStorage'ı ifade ettiğimizi hatırlayalım)

    Storage sekmesine bakarsak Local Storage dışında IndexedDB seçeneği de bulunmaktadır. Eğer bu alanı kullanmak istersek

    ATTACH INDEXEDDB DATABASE TacticalWorldDB

    gibi bir SQL ifadesinden yararlanmamız gerekiyor. Tabii bu arada önemli bir soru da gündeme geliyor. Uygulamayı kapattığımızda veriye ne olacak? Bunu cevaplayabilmeniz için kodu buraya kadar geliştirmiş olmanız gerekiyor :)

    Ben Neler Öğrendim?

    Bazen sıfırdan başlanacak bir ürün için ya da lisans maliyetleri ve diğer sebepler nedeniyle modernize edilecek bir proje için verinin nerede neyle tutulacağını araştırmak isteyebiliriz. Böyle durumlarda alternatifleri POC(Proof of Concept) tadındaki deneysel programlarda denemek faydalıdır. Bende buna istinaden AlaSQL'i incelemiştim. Yanıma kar olarak kalanlarsa şöyle.

    • Hazır bir react uygulama iskeletinin nasıl oluşturulduğunu
    • React sayfası içerisindeki yaşam döngüsüne dahil olan componentWillMount, componentDidMount ve render metodlarının hangi aşamalarda devreye girdiğini
    • Alasql paketinin react uygulamasına nasıl dahil edildiğini ve temel SQL ifadelerini(veritabanı nesnelerini oluşturmak, insert ve delete sorgularını çalıştırmak vb)
    • state özelliğini ne amaçla kullanabileceğimi
    • Veritabanından çekilen JSON dizisinin map fonksiyonu ile nasıl etkileştiğini
    • refs özelliği ile kontrollerin metotlarda nasıl ele alınabildiğini

    Böylece geldik bir saturday-night-works notu derlemesinin daha sonuna. Yolun açık olsun Skipper :) Tekrardan görüşünceye dek hepinize mutlu günler dilerim.


    Warnning: Do NOT Get Caught While Searching!!
    Your IP : - Country : - City:
    Your ISP TRACKS Your Online Activity! Hide your IP ADDRESS with a VPN!
    Before you searching always remember to change your IP adress to not be followed!
    PROTECT YOURSELF & SUPPORT US! Purchase a VPN Today!