WebAssembly Nedir? Blazor + Kafka + SignalR ile Gerçek Zamanlı Web Uygulaması

Biraz geçmişe gidelim.
2006 yılında Google Web Toolkit(GWT) ile Java kodunu JavaScript koduna compile etti. 2007 yılında ise Phyton kodu pyjamas ile JavaScript koduna compile edildi.
High Level programlama dillerinde durum böyleyken, C/C++ programlama dilleri ise emscripten 2012 Kasım ayında release olduktan sonra JavaScript’e compile edildiler.
C/C++’ın adı geçtiği zaman performanstan bahsetmemek olmaz. emscripten nasıl çalışıyor inceleyelim.

Bu mimaride dikkatinizi LLVM’e (Low Level Virtual Machine) çekmek istiyorum. LLVM’in yaptığı işi çok basit şekilde özetlersek, compiled kod üzerinde öyle bir optimizasyon yapıyor ki sonuç olarak ortaya çıkan compiled JavaScript (Subset of JavaScript), düz bir şekilde yazdığımız JavaScript’den daha performanslı çalışıyor.
Alon Zakai (konunun derinliklerine inmek isteyenlere, bu ismi ve yaptıklarını araştırmasını tavsiye ederim. Referanslar bölümünde bir kaç sunumunu paylaşacağım.) Bu noktada aklınıza compiled JavaScript, düz JavaScript’den nasıl daha hızlı çalışıyor sorusu gelebilir. Alon Zakai’nin hazırlamış olduğu sunumu inceleyebilirsiniz.
C/C++ tarafında yapılan bu araştırmalar 21 Mart 2013 tarihinde, Firefox JavaScript motoru üzerinde asm.js optimizasyon modülü olarak yerini alıyor.
asm.js ile ilgili bilmemiz gereken temel nokta, native code (C/C++) performansından sadece 2x yavaş bir performans elde etmemizi sağlıyor.
Luke Wagner ismini de unutmayalım. 2013 yılında yazmış olduğu blog yazısında asm.js’in getirdiklerine ulaşabilirsiniz.
asm.js her ne kadar performans olarak ciddi artışlar gerçekleştirsede uygulamaların boyutu arttıkça performans tarafında farklı problemlerin ortaya çıktığı görülmüştür. Bu problemlerin çözülmesi ve asm.js ile elde edilen başarının bir adım ötesine geçmek amacıyla 2015 yılında Luke Wagner önderliğinde WebAssembly Community Group kuruldu. Grubun kurulmasından 2 ay sonra Microsoft yayınladığı haber ile WebAssembly Community Group altında çalıştıklarını duyurdu.

Şubat 2017'de resmi logo belirlendi ve Mart 2017'de Firefox WebAssembly’i destekleyen ilk browser oldu.
WebAssembly (.wasm) ile beraber JavaScript (.js) browserlar tarafından desteklenen tek dil olma özelliğini kaybetti. Tabi bu demek değil ki .wasm .js’in yerini alacak, ama performans gerektiren konularda yerini alacağı bir gerçek çünkü asm.js ile başlayan süreçten WebAssembly’e gelene kadar görüyoruz ki temel ihtiyaç JavaScript’in performans eksikliklerini gidermek.
Sizlerin de bu konu hakkında ki görüşlerini merak ediyorum. Yorum yaparak bu konular hakkında soru cevap veya bir tartışma ortamı oluşturabilirsiniz. 💬
Günümüzde ise WebAssembly dört büyük Web Browser tarafından desteklenmektedir. WebAssembly’nin yolcuğunu ve Microsoft’un Web Assembly Community Group’a ne kadar erken dahil olduğunu öğrendikten sonra, Microsoft’un Blazor Framework’ünü incelemeye başlayabiliriz.
Blazor
Blazor’a baktığımız zaman Client Side ve Server Side olarak iki farklı çözüm sunduğunu görüyoruz.
Blazor Server Side

Blazor Server Side Hosting Model, isminden anlayacağımız üzere web uygulamamızın tamamen server üzerinde çalıştığı bir mimariye sahiptir.
Server üzerinde bulunan uygulamamız DOM(Document Object Model) üzerinde güncellemeler gerçekleştirmek için SignalR kullanmaktadır. SignalR two-way connection açarak, DOM ve Server güncellemeleri için gerekli iletişim kanallarını oluşturur.
Blazor Client Side

Blazor Client Side Hosting Model, web uygulamamızın tamamen web browser üzerinde çalıştığı bir mimariye sahiptir. WebAssembly sayesinde .NET üzerinde yazmış olduğumuz C# kodumuz browser üzerinde çalışabilmektedir.

Client Side Blazor uygulamasının DOM ile nasıl iletişime geçtiğine baktığımız zaman JavaScript’in aslında herhangi bir yere gitmediğini görüyoruz. WebAssembly DOM ile iletişime geçmek için JavaScript interop çağrıları gerçekleştiriyor. Bu sayede JavaScript tarafından .NET ve .NET tarafından da JavaScript fonksiyonları çağrılabiliyor. İkinci bakmamız gereken nokta ise WebAssembly üzerinde bulunan “.NET Runtime” ‘ın tamamı browser tarafından yüklenmektedir. Bu noktada aklımıza gelen ilk soru uygulamamızın boyutunun ne olacağı. Blazor WebAssembly 3.2.0 versiyonu ile beraber gelen Brotli sıkıştırma formatını kullandığımız zaman 1.5MB ve altında bir uygulama boyutuna sahip olmayı bekliyoruz. Bu konu birazdan bahsedeceğimiz artılar ve eksiler karşılaştırmasında teknik karar alırken gireceğimiz trade-offlardan birisi olacaktır.
Blazor Server Side vs Blazor Client Side
Bu konuda detaylı analizler yapmak için erken olduğunu düşünüyorum ancak şu an karşımıza çıkan bir kaç gözle görülür fark var o farklılardan bahsedelim.
Blazor Server Side Hosting Model browser ve server arasında sürekli açık bir connectiona ihtiyaç duymaktadır. Bu ihtiyaçtan kaynaklı, uygulamalar offline olarak çalışmamaktadır. Bununla birlikte bağlantı hızına ve kalitesine bağlı olarak uygulamamızın çalışma hızı etkilenecektir.
Blazor Client Side Hosting Model ise uygulamanın tamamını browser’a gönderdiği için browser tarafından şu an için ortalama en az 1.5MB bir load işlemi gerçekleştirilmektedir. Ben ileride bu değerin çok daha aşağılara düşeceği fikrindeyim. Ayrıca yüklenme süresine bağlı olarak uygulamamızın browser tarafında çalışır hale gelmesi Blazor Server Side Hosting Model’e göre yavaş olacaktır.
WebAssembly ve Blazor ile ilgili gerekli bilgileri edindikten sonra, [HANDS ON] bölümümüze geçebiliriz.
[HANDS ON] Kafka + SignalR + Blazor WebAssembly ile Gerçek Zamanlı .NET Uygulaması

Uygulamamızın mimarisini incelediğimiz zaman, Paranoid Moons olarak adlandırdığım worker servisler olduğunu görüyoruz. Bu worker servislerden birisi Twitter Streaming API üzerinden ilgili anahtar kelimeyi içeren tweetleri gerçek zamanlı olarak okuyor ve “twitter” ismini verdiğim Kafka topic’e produce ediyor. Paranoia Consumers ise Kafka topic üzerinde bulunan mesajları gerçek zamanlı olarak consume ediyor ve HTTP protokolü üzerinden Paranoid Planet olarak adlandırdığım environment içerisinde bulunan Paranoid API’ye POST ediyor. Paranoid API ise hem mongoDB’ye insert işlemini gerçekleştiriyor hem de ilgili mesajı SignalR üzerinden broadcast ediyor. Blazor WebAssembly uygulaması ilgili Hub’a bağlanarak gerçek zamanlı olarak broadcast edilen mesajları Razor Pages üzerinde gerçek zamanlı olarak göstermeye devam ediyor.
Uygulamamızın ne yaptığını kısaca özetledim. Bu konu hakkında herhangi bir sorunuz veya fikriniz olursa yorum olarak iletebilirsiniz.
[HANDS ON]

İlk olarak ilgili dizin (Paranoid.Twitter.Moon) içerisinde,
dotnet new worker
komutunu çalıştırarak worker servisimi oluşturuyorum. Worker hem Twitter Streaming API üzerinden data okurken hem de okumuş olduğu Tweet’i Kafka üzerinde bulunan “twitter” isimli Kafka topic’e gönderiyor.
Local ortamımda 1 replica ve 1 partion’a sahip “twitter” isimli Kafka topic oluşturmak ve bunun ile birlikte local ortamımda Kafka broker’ını ayağa kaldırmak için aşağıda bulunan compose.yml’ını kullanıyorum.
docker-compose up -d

Kafka consumer ve producer ile ilgili detaylı bilgiye ulaşmak için dökümanı inceleyebilirsiniz.
Paylamış olduğum dökümanlarda bulunan yapıları impelemente ederken herhangi bir sorun yaşarsanız yorum olarak bırakabilirsiniz. Bu projeyi çok yakın zamanda GitHub üzerinde paylaşacağım. GitHub hesabımı takip ederek bilgi alabilirsiniz. GitHub’da paylaşmamı beklemeden nasıl bir proje olduğunu merak edenler var ise Twitter üzerinden bana yazarak detaylı bilgi alabilirler.
Gelelim Kafka topic üzerinde bulunan mesajları consume edecek olan Consumer uygulamamıza.
Paranoid.Consumer dizini içerisinde,
dotnet new console
komutunu çalıştırarak consumer uygulamamı oluşturuyorum.

Şu an elimizde Twitter Streaming API üzerinden gelen datayı produce eden ve o datayı okuyan consumer uygulamalarımız var.
Bu kısıma kadar yaptıklarımızı console’a yazdırarak kontrol edelim. Paranoid.Twitter.Moon servisimiz “science” kelimesini içeren tweetleri produce ediyor ve consumer uygulamız okuduğu mesajları console’a yazıyor.

İlk aşamayı tamamladık. 🙌
Paranoid Planet ile devam edelim.

İlk olarak consumer’a gelen mesajı console’a yazdırmak yerine HttpClient ile ilgili end-point’e POST ediyoruz.
Paranoid.Planet.API dizini içerisinde,
dotnet new webapi
komutunu çalıştırarak API uygulamamızı oluşturuyoruz.
POST isteğini karşılayan end-pointimize bakalım.
Temel olarak POST request’i karşılayan bu metot gelen request’i öncelikle MongoDB’ye yazıyor ve daha sonra bu request üzerinde bulunan mesajı SignalR üzerinden broadcast ediyor.
ParanoiaHub sınıfına baktığımız zaman SignalR’ın Hub sıfından inherit ediyor ve interface olarakta IParanoiaHub interface’ini kullanıyor.
IParanoiaHub interface’ini farklı bir dizinde ClassLibrary olarak oluşturacağız. Çünkü bu interface’i ve Hub settings’i ClassLibrary üzerinde tutarak hem API projemizden hem de Blazor projemizden erişibileceğiz.
Paranoid.Planet.Hub.Interfaces dizini içerisinde,
dotnet new classlib
komutunu çalıştırarak ClassLibrary oluşturuyoruz.
public interface IParanoiaHub
{
Task SpreadAsync(string content);
}
Paranoid.Planet.API üzerinde SignalR Hub’ı
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<ParanoiaHub>(“/hubs/paranoia”);
endpoints.MapControllers();
});
/hubs/paranoia adresine maplediğimiz için, ConfigurationSettings’de de HubUrl olarak aynı adresi verdik.
Bu sayede artık HTTP POST end-pointimize herhangi bir request geldiği zaman bu request içerisinde bulunan content’i yani ilgili Tweet’i SignalR ile oluşturduğumuz Hub’a broadcast ediyoruz.
Şu an local ortamımda bu “http://localhost:5001/hubs/paranoia” url’i dinleyen yani oluşturduğumuz Hub’a bağlanan tüm clientlar ilgili mesajlara erişebilecek.

Biz client olarak olarak Blazor projemizi oluşturmaya başlayabiliriz.
Paranoid.UI dizini içerisinde,
dotnet new blazorwasm --hosted --pwa
komutunu çalıştırarak, Blazor Client Side Hosting Model projemizi PWA(Progressive Web Application) destekleyecek şekilde oluşturuyoruz. Ek olarak — hosted yazdığımız için sunucuda çalışan ASP.NET Core üzerinden uygulamamızı sunabiliyoruz. ( ***— hosted, Blazor Server Side Hosting Model anlamı taşımamaktadır. ***)

Oluşturduğumuz projeye baktığımız zaman 3 farklı katmana bölündüğünüzü görüyoruz. Kısaca bahsetmek gerekirse,
Shared projesi altında ortak kullanacağımız objelere yer verebiliriz, bu sayede Server ve Client üzerinde ayrı ayrı kod yazmamız gerekmez. Örneğin modellerimizi bu proje üzerinde tutabiliriz.
Server projesini Client uygulamamızın Back-End’i gibi düşünebiliriz. Server projesi içerisinde database işlemleri, senaryolara göre Authorization, Authentication işlemleri yapılabilir.
Client projesi ise Blazor WebAssembly projesidir. Tamamen browser üzerinde çalışır ve — pwa ile uygulamamızı oluşturduğumuz için Progressive Web Application ihtiyaçlarını karşılamaktadır.
Bu yapıyı günümüzde popüler olan Angular, Vue gibi Single Page Application Frameworklerinin yapısı gibi düşünebiliriz. .NET ekosistemine hakim yazılım geliştiriciler için Client Side ve Server Side çözümleri bir arada sunmaktadır.
( *** — hosted, Blazor Server Side Hosting Model anlamı taşımamaktadır. ***)

Bu kısımda kafamızın karışmaması önemli. Gördüğümiz gibi Client uygulamamızın tamamı browser tarafından yüklenmektedir.
Şu an için senaryomuzda sadece Client projesi ile çalışacağız.
Oluşturduğumuz Paranoid.Planet.Hub.Interfaces , IParanoiaHub interface’ine ve ConnnectionSettings sınıfına sahip bir class library. Bu class library’i Client projemize referans olarak ekliyoruz. Ek olarak SignalR paketini de projemize ekliyoruz.
dotnet add reference ../../Paranoid.Planet.Hub.Interfaces/Paranoid.Planet.Hub.Interfaces.csprojdotnet add package Microsoft.AspNetCore.SignalR.Client --version 3.1.5
Daha sonra Index.razor sayfamız içerisinde SignalR için gerekli implementasyonları yapıyoruz.
Gördüğünüz üzere tamamen .NET ortamında, .NET kütüphanelerini ve C# programlama dilini kullanarak Client uygulamamızı yazmayı başardık.
Şimdi sırasıyla;
Paranoid.Twitter.Moon ( corona sözcüğünü içeren tweetleri getiriyor olacak. )
Paranoid.Consumer
Paranoid.Planet.API
Paranoid.Planet.UI
projelerimizi çalıştırıyoruz.

İlgili sözcük ile alakalı çok fazla tweet atıldığı için chat gibi akan bir görünüm ile karşılaşıyoruz.
[HANDS OFF]
Blazor ile birlikte birbirinden farklı uygulamalar geliştirmemiz mümkün ve .NET 5 ile beraber daha olgun bir hal alacağını söyleyebiliriz. Blazor’ın yetkinlikleri yukarıda gördüğünüz gibi sadece WebAssembly ile sınırlı değil. Biz bu yazı özelinde WebAssembly’i ve Microsoft’un WebAssembly çözümü olan Blazor WebAssembly modelini inceledik ve uyguladık.

Blazor’ın geleceğine bakacak olursak, .NET 5 ile gelecek yaklaşıma uyum sağladığını görebiliriz. (.NET 5 yolculuğu ile ilgili yazım)
✓ Tek bir framework ile birden farklı client uygulaması geliştirebilmek.
✓ Client Side uygulamlarda C# programlama dilini kullanabilmek .
Başlıktan anlayacağımız üzere benim burada gelecek vizyonum WebAssembly’e yönelik olacak. “WEB” ortamında geliştireceğimiz uygulamalara farklı bir bakış açısı getirdiğini ve bu bakış açısını yaygınlaştıracağını düşünüyorum.
Bugüne kadar elde ettiğim tecrübe bir noktada bir dil veya bir framework “performs” üzerine yatırımlarını yapıyorsa o dil, framework veya tool vs. endüstride kendine sağlam bir yer ediniyor.
Ben .NET geliştirme ortamına diğer ortamlara göre daha fazla hakim olduğum için WebAssembly’i anlamak için öncelikle Blazor’ı tercih ettim. Sizlerde .NET geliştirme ortamına alışkansanız Blazor’a bir şans verebilirsiniz. C# bilginizi kullanarak Client-Side ve Server-Side uygulama geliştirme yeteneğine ve güncel bir teknoloji kullanma imkanına sahip olacaksınız. Eğer ben C# programlama dilinden farklı bir programlama dili ile WebAssembly’i deneyimlemek istiyorum diyorsanız, bundan sonraki yazılarımın da konusu olacak Rust programlama dilini araştırabilirsiniz.
Referenslar