.Net Core’da RabbitMQ ve SignalR İle Push Notification

Selamlar Arkadaşlar,

Bu makalede, .Net Core ile cross platform üzerinde RabbitMQ, signalR ve Microservice konularını hep beraber inceleyeceğiz. RabbitMQ hakkında daha detaylı bilgiye buradaki linkden erişebilirsiniz. .Net Core üzerinde RabbitMQ kullanabilmek için öncelikle RabbitMQ.Client kütüphanesine ihtiyaç duyulmaktadır. O zaman gelin bu RabbitMQ için kurulum işlemlerini Macbook üzerinde yapalım:

Makinanızda yok ise önce Homebrew kurulur. Var ise Update edilir:

Bash Profile Güncelle: RabbitMQ ilk kurulduğu zaman “/usr/local/sbin” altına kurulmaktadır. RabbitMQ server’ı kendi profilimiz altında ayağa kaldırmamız için ilgili yolu “.bash_profile” altına tanımlanması gerekmektedir. Bunun için “nano” aşağıdaki gibi kullanılır.

RabbitMQ Server’ın çalıştırılması: “rabbitmq-server” komutu yazıldığı zaman, RabbitMQ ilgili client tarafında ayağa kaldırılmış olunur.

Şimdi gelin .Net Core üzerinde RabbitMQ ile ilk örneğimizi geliştirelim.

Seneryo: Seçilen tahvil, bitcoin gibi yatırım araçlarının fiyatları bir admin ekranında değiştirilecektir. Bu değiştirilen yatırım araçları daha sonra sıra ile işleme alınması amacı ile RabbitMQ’ya, yani bir kuyruğa atılacaktır. Her bir yatarım aracı, kendine özel farklı bir channelda ilgili Queue’da saklanacaktır. Daha sonra her bir channel için farklı .NetCore Console App oluşturup, her bir kanal tek tek dinlenecektir. Son olarak sadece değişen bu yatırım araçlarını satın alan clientlara, SignalR ile geri bildirim yani push notification atılacaktır.

ADMİN :

İşlem adımları:

  • Bir .Net Core Mvc projesi oluşturulur. “dotnet new Mvc -o coreRabbitMQAdmin
  • Yatırım araçlarının bilgilerini tutan “Stoc” model aşağıdaki gibi oluşturulur.

Models/Stoc.cs:

  • Dummy data amaçlı HomeController altında aşağıdaki gibi static bir List of Stoc “stocList” tanımlanmıştır. Amaçdan uzaklaşmamak adına DB işin içine sokulmamıştır.

  • Dummy dataların çekileceği “GetDummyData()” methodu aşağıdaki gibi oluşturulur.

Home/GetDummyData :

  • Index sayfasına dummy data “GetDummyData()” methodu ile çekilerek ilgili data view’a gönderilir.

Home/Index :

Index.cshtml: Aşağıda görüldüğü gibi öncelikle Model olarak =>List of Stoc beklenmektedir.

  • @using(Html.BeginForm(“Push”,”Home”,”FormMethod.Post”))” : Form post işleminde, Home Controller’ın Push() action’ına, değiştirilen Stoc modeli post edilir.
  • <select name=”Name” id=”Name” onchange=”setValue(this.options[this.selectedIndex])”>” : Kağıt Çeşitleri combobox’ı, model’den gelen Stoc Listesi gezilerek doldurulur. Güncelleme amaçlı yeni bir stok seçildiğinde, fiyat bilgisi ilgili text’e “setValue()” function’ı ile doldurulur.
  • <option value=”@stock.Name” id=”@stock.ID” price=”@stock.Value”>@stock.Name</option>” : Stoc ismi, combo’nun her bir elemanı olarak atanır.
  • <input type=”text” name=”Value” id=”Value”>” : Stoc Value yani fiyatı ve ID’si, Form içinde post edileceği için input alanlarda tanımlanır.
  • “setValue()” : Combo listesinde  bir seçim yapıldığı zaman fiyatı, input alana basmak için kullanılan bir function’dır.
    • var price=selectedOption.getAttribute(‘price’) ” : ComboBox’dan seçilen elemanın (option’ın) price alanı alınır.
    • “document.getElementById(“Value”).value=price” : Value id’li input alana, seçilen kağıdın fiyat (price) bilgisi atanır.
    • “var stocID=selectedOption.id” : ComboBox’dan seçilen elemanın (option’ın) ID alanı alınır.
    • “document.getElementById(“ID”).value=stocID ” : “ID” id’li  input alana seçilen kağıdın ID bilgisi atanır.

Home/Push() : Değiştirilen kayıt Push() action’ına gönderilerek, kuyruğa eklenmesi sağlanır. Bu arada değişen Item, “UpdateDummyList()” methodu ile var olan tanımlı global Static stocList‘den de değiştirilir. Queue olarak, bu makalenin de asıl konusu olan RabbitMQ kullanılmıştır. Tüm işlemler tamamlandıktan sonra, “Index” sayfasına tekrardan geri dönülür.

Home/UpdateDummyList(): Güncellenen yatırım aracı, ID’si ile ilgili static stocList’den bulunup, eski değeri ile değiştirilir. Bu işlem static bir listede yapıldığı için tüm clientlar bundan etikenmektedir.

RabbitMQPost : Aşağıda görüldüğü gibi Constructor’da kuyruğa atılacak Stoc modeli data değişkenine atanmaktadır.

Not: Projeye, RabbitMQ Client kütüphanesi aşağıdaki gibi eklenmektedir.

  • ConnectionFactory :RabbitMQ hostuna bağlanmak için kullanılır. Bulunulan sunucudaki host name (localhost) ve eğer tanımlanmış ise credentials (password) girilir.
  • CreateModel() : Bu method ile RabbitMQ üzerinde yeni bir channel yaratılır. Bu channel aşağıda görüldüğü gibi her bir yeni eklenen stoc için yaratılmaktadır. Bu şekilde açılan channel yani session ile, yeni bir queue oluşturulup, değiştirilen stok bilgisi kendisine özel açılan bu channel üzerinden gönderilmektedir.
  • QueueDeclare() : Bu method ile oluşturulacak olan queue‘nin ismi tanımlanır. Bu uygulamada ilgili “queue” ismi, seçilen Stoc ismi ile aynı olucak şekilde tanımlanmıştır. “durable” ile datanın in-memory mi yoksa fiziksel olarak mı saklanacağı belirlenir. “exclusive” parametresi ile diğer connectionlar ile kullanılmasına izin verilir. Eğer queue deleted olarak işaretlenmiş ise ve tüm consumerlar bunu kullanmayı bitirmiş ise ya da son consumer iptal edilmiş veya channel kapanmış ise silme işlemi gerçekleştirilemez. İşte bu gibi durumlarda “autoDelete” ile  delete method’u normal olarak çalıştırılır. Böylece ilgili queueler silinir. “arguments” belirlenen exchanges ile alakalı tanımlanacak parametrelerdir. Bu uygulamada exchange tanımlanmamıştır.
  • Güncellenen “Stoc” modeli, JsonConvert ile Serialize edilip string bir ifadeye dönüştürülür. Daha sonra “byte[ ]” dizisine çevrilip post edilecek “body“ parametresine atanır.
  • BasicPublish() : Bu methodda “exchange”, aslında mesajın alınıp bir veya daha fazla queue’ya konmasını sağlar. routingKey ile konucak olan kuyruğun adı tanımlanır.
  • Ve son olarak değiştirilen Stoc’un, kuyruğa konduğuna dair bir bildiri console’a yazdırılır.

Örnek değişen yatırım araçlarının, RabbitMQ’ya bildirim amacı ile atılması.

CLIENT :

Şimdi sıra geldi yukarıda tanımlanan yatırım araçlarından birinin bir client tarafından alınıp, monitör ekranında izlenmesine :

İşlem adımlar:

  • Yeni bir .Net Core Mvc Client projesi oluşturulur. “dotnet new Mvc -o coreRabbitMQClient
  • Yatırım araçlarının bilgilerini tutan “Stoc” model, aşağıda görüldüğü gibi aynı Admin ekranında olduğu gibi oluşturulur.

Models/Stoc.cs:

Seçilecek olan yatırım araçları, örnek amaçlı aşağıda görüldüğü gibi static olarak tanımlanır.

Index.cshtml:

Home/Index : Static olarak tanımlı List<Stoc> view model, seçilmek amacı ile Index sayfasına View Model olarak gönderilir.

Index.cshtml : Client’ın, örnek amaçlı ürün satın aldığı ekrandır.

  • Sayfada view model olarak “List<Stoc>” kullanılmıştır.
  • “@using(Html.BeginForm(“Detail”,”Home”,”FormMethod.Post”))” : Kaydetme işlemi yapıldığında “Detail()” action’ına seçilen “Stoc” datası Post edilir.
  • “@foreach(var stoc in Model)” : Stoc listesi gezilerek, radio button olarak ekrana alınabilecek yatırım araçları listelenir.

Detail.cshtml:

Home/Detail: Alınan yatırım aracının, monitor edildiği ekrandır.

  • “Stoc stoc=stocList.FirstOrDefault(s=>s.ID==ID)” Seçilen yatırım aracına ait data, ID bilgisi ile linq kullanılarak çekilir. Daha sonra dönen Stock resut, Detail view’a model olarak gönderilir.

Detail.cshtml: Bu sayfa, satın alınan yatırım aracının  real time olarak izlendiği  monitor ekranıdır.

  • @model Stoc : View model olarak sayfa üzerinde Stoc kullanılmıştır.
  • “<script src=”~/scripts/signalr-client-1.0.0-alpha2-final.min.js”></script>” :.Net Core üzerinde ilgili signalR kütüphanesi eklenir. SignalR, makalenin devamında detaylıca incelenecektir. .Net Core üzerinde SignalR hakkında ön bir bilgi edinmek isterseniz, buradaki makaleye bir göz atabilirsiniz.
    • İlgili signalR script kütüphanesi “npm install @aspnet/signalr-client –save”  komutu ile indirilebilir.
    • npm install @aspnet/signalr-client” komutu ile ilgili kütüphane, golbal’e de indirilebilir. İlgili dosya globalden, yandaki klasörden erişilebilir==>”/usr/local/lib/node_modules/@aspnet/signalr-client/dist/browser/signalr-client-1.0.0-alpha2-final.min.js“. Bu script dosyasının projede “wwwroot” altında “scripts” klasörü altına kopyalanması gerekmektedir.
  • “let connection = new signalR.HubConnection(‘/stochub’)” : Makalenin devamında, signalR sınıfı olarak “StocHub: Hub” sınıfı oluşturulacaktır. İşte bu yaratılacak sınıfa client tarafından “HubConnection” ile erişilir.
  • “connection.on(‘SetConnectionId’, data => {” : Server Side taraftan, ilgili client side tarafa tetiklenecek olan “SetConnection()” function’ı tanımlanmıştır. Burada “data”  ===> client’ın SignalR sınıfında, yani server side tarafında “OnConnectedAsync()” methodunda çekilen ConnectionID değeridir.
    • “var result=connection.invoke(‘ConnectGroup’,’@Model.Name’,data);” : Seçilen Stoc ismi server side SignalR : Hub sınıfındaki “ConnectGroup()” methoduna parametre olarak gönderilmiştir.
  • “connection.on(‘ChangeStocValue’, data => {” : İlerde seçilen ürünün fiyatı değiştiği zaman, son güncel fiyatın client’a real time bildirilmesi amacı ile server side taraftan, client side tarafa tetiklenen “ChangeStocValue()” function’ıdır. Bu şekilde son güncel fiyat, server side tarafından alınıp, client üzerinde bulunan “stocValue” “<span>” html elementine basılır.

Önemli Not: Index sayfasından seçilen bir yatırım aracı, sessionda saklanıp, burada yani Detail sayfasında server side “OnConnectedAsync()” methodunda direk kullanılabilirdi. Ama bu örnekde session’dan kaçınılıp önce 1-) data ==> “Detail.cshtml” sayfaya View Model olarak gönderilmiş daha sonra 2-) “Detail.cshtml” sayfadan ==> İlerde yazacağımız Server Side taraftaki “SetConnectionId()” methoduna “Stoc.Name” olarak aktarılmıştır. Bu server side ve client side arasındaki trafik, örnek amaçlı yukarıdaki çizimde detaylıca gösterilmektedir.

Ödev: Yüksek trafikli bir sitede, ilgili datayı Session’a mı koymak daha performanslı yoksa client side ve server side taraflar arasında birden fazla trafik oluşturmak mı?

SignalR :

Bu projede kullanılma amacı, fiyatı değişen yatırım araçlarının güncellenen son fiyatının, bu ürünü alan clientlara bildirilmesidir. Bunun için realt time websocket teknolojisi olan SignalR kullanılmıştır. 

Kurulum: 

  • Oluşan Mvc projede “coreRabbitMQClient.csproj” dosyası aşağıdaki gibi güncellenir. ==>”<PackageReference Include=”Microsoft.AspNetCore.SignalR” Version=”1.0.0-alpha2-final” />” dosyası eklenir. Kaydetme işleminden sonra VS Code’un sorduğu Restore işlemine izin verilir. Ya da “dotnet restore” komutu ile ilgili dosyalar yüklenir.
  • <Folder Include=”wwwroot\scripts\” />” : Aşağıda görülen bu ekleme ile “.cshtml” sayfa üzerinde eklenecek script dosyalarına kısa yol verilmiş olunur.

coreRabbitMQClient.csproj: SignalR için gerekli kütüphane eklenmiş olunur.

Startup.cs: İlgili SignalR kodları aşağıdaki gibi Startup.cs’e eklenir. Böylece signalR Routing tanımlaması yapılmış olunur.

  • ConfigureServices()” methodunda ==> “services.AddSignalR()” ile ilgili signalR services tanımlaması yapılır.
  • “routes.MapHub<StocHub>(“stochub”)” : Mvc routing işlemlerinde “stochub” key’i ile “StocHub” sınıfına yönlenilir.

StocHub(class):

  • “OnConnectedAsync()” : Bu StocHub signalR sınıfında ilk önce connect olan client’ın connectionID’si o an bağlanan client’a gönderilir:
    • “Context.ConnectionId” : O an bağlanan client’ın connectionID’sidir.
    • “Clients.Client(Context.ConnectionId)” : Connect olan client’a connectionID’si ile erişilir.
    • “InvokeAsync(“SetConnectionId”, Context.ConnectionId)” : Client Side tarafta “SetConnectionId()” function’ı tetiklenerek parametre olarak connect olan client’ın ConnectionID’si gönderilir.
  • “ConnectGroup()” : Connect olan client’ın, seçtiği yatırım aracının adı ile bir guruba atanması sağlanmıştır.
    • “await Groups.AddAsync(connectionID,stocName)” : Asenkron olarak connect olan ilgili connectionID’li client’ın, seçilen stockName adındaki guruba asenkron olarak eklenmesi sağlanır.
    • “return $”{connectionID} is added {stocName}”” :  Geriye, connect olan client’ın connectionID’si ve atandığı gurubun ismi dönülür.
  • “PushNotify()” : Herhangi bir yatırım aracının fiyatı değiştiği zaman, bunu satın alan clientlara real time bildirilmesini sağlanır.
    • “return Clients.Group(stocData.Name).InvokeAsync(“ChangeStocValue”, stocData)” :
      • Clientların atandığı gurup ismi, satın alınan yatırım aracı ile aynı olduğu için “Clients.Group(stocData.Name)” komutu ile bu guruba dahil olan yani bu ürünü alam tüm clientlara erişilir.
      • “InvokeAsync(“ChangeStocValue”, stocData)” : Değişen stok bilgisi, o guruba ait clientların (yukarıda Detail.cshtml sayfada anlatılan client side tarafdaki) “ChangeStocValue()” function’ına parametre olarak gönderilip tetiklenmesi sağlanır.

Service :

Amaç: RabbitMQ’ye atılmış Stoc datasını, bağlı olduğu channel üzerinden dinlenip, data gelindiği zaman ilgili queue’den çekilmesi ve daha sonra bu yatırım aracını almış clientlara, son güncel verinin gönderilmesidir.

Kurulum :

  • Yeni bir .Net Core Console projesi oluşturulur. “dotnet new console -o coreRabbitMQService
  • Yatırım araçlarının bilgilerini tutan “Stoc” model, aşağıda görüldüğü gibi aynı Admin ekranında olduğu gibi tanımlanır.

Models/Stoc.cs:

  • Oluşan Mvc projede “coreRabbitMQService.csproj” dosyası aşağıdaki gibi güncellenir. ==>”<PackageReference Include=”Microsoft.AspNetCore.SignalR.Client” Version=”1.0.0-alpha2-final” />” dosyası eklenir. Kaydetme işleminden sonra VS Code’un sorduğu Restore işlemine izin verilir. Ya da “dotnet restore” komutu ile ilgili signalR kütüphanesi yüklenir.

coreRabbitMQService.csproj:

  • Projeye RabbitMQ Client kütüphanesi aşağıdaki gibi eklenir.

Program.cs : Localdeki RabbitMQ kuyruğu, tanımlanan channel’a göre dinlenip, yeni gelen Stoc datası aynı guruptaki Clientlara SignalR kullanılarak push edilir.

  • “static HubConnection connectionSignalR” : Main() methodunun tepesinde global olarak static HubConnection değişkeni tanımlanır.
  • “var factory = new ConnectionFactory() { HostName = “localhost” }” : Localdeki RabbitMQ Queue’ya bağlanılır.
  • “using (var connection = factory.CreateConnection())” : Connection açılır.
  • “using (var channel = connection.CreateModel())” : İlgili kanal açılır.
  • ” Connect().Wait()” : Methodunda yeni bir HubConnection asenkron olarak Main() methodunun dışında tanımlanır. Çünkü Main methodu şu an için asenkron çalışmayı desteklememektedir.
  • “public static async Task Connect()” : Connect methodunda asenkron olarak signalR StocHub sınıfına bağlanılır.
    • “var connectionSignalR = new HubConnectionBuilder()”: SignalR StocHub sınıfına asenkron olarak bağlanmak için kullanılan bir methoddur.
      • “.WithUrl(“http://localhost:1453/stochub”)” : SignalR sınıfı dünyanın başka bir yerinde de olabilir. Bu nedenle erişim IP’si tanımlanır.
    • “await connectionSignalR.StartAsync()” : SignalR sınıfına asenkron bağlanılır. Bunun nedeni, ilgili sınıfa bağlanılma süresinin tam olarak bilinmemesi ve bundan dolayı çalışan sistemin boşuna bekletilmek istenmemesidir.
    • Not: Dikkat edilir ise StartAsync() asenkron bir methoddur.  Ama Halen .Net Core’da Program sınıfında “Main()” methodu asenkron yapıyı desteklememektedir. Daha önceki .Net Core versiyonunda  “async” desteklense de bu özellik şu an için tekrardan kaldırılmıştır. Bu nedenlerden dolayı signalR sınıfa bağlanma işlemi, main methodu dışında asenkron olarak tanımlanmış ve Main() methodu içinde çağrılmıştır.
  • “channel.QueueDeclare(queue: “Bitcoin”” : RabbitMQ’da dinlecek channel’ın “Bitcoin” olduğu tanımlanır.
  • “var consumer = new EventingBasicConsumer(channel)”  RabbitMQ’da dinleyici, ya da alıcı da diyebileceğimiz tanımlama yapılır.
  • “consumer.Received += (model, ea) =>” : RabbitMQ’da yeni bir “Stoc” paketi yakalandığında, Received event’i tetiklenir. Ve bu eventin altındaki kodlar çalıştırılır.
    • “var body = ea.Body” : Yakalanan Stoc modeli body değişkenine atanır.
    • “var data = Encoding.UTF8.GetString(body)” : Stoc model string’e çevrilir.
    • “Stoc stoc = JsonConvert.DeserializeObject<Stoc>(data)” : string’e çevrilen data NewtosSoft kütüphanesi ile “Stoc” modele deserialize edilir.
    • “connectionSignalR.InvokeAsync(“PushNotify”, stoc)” : Bu Stoc modele çevrilen data, SignalR “StocHub/PushNotify()” methodu tetiklenerek parametre olarak gönderilir. Böylece güncellenen yatırım aracı, kendisini takip eden clientlara push notification olarak gönderilir.
    • “channel.BasicConsume()” : Methodu ile ilgili Queue’den mesajları çekme işlemine başlanır. Burada autoAckparametresi true olarak atanır ise, ilgili mesaj alındıktan sonra Queue’den otomatik olarak silinir.
    • “Console.ReadLine()” : Methodu ile bu satırda kod bekletilip, RabbitMQ dinlenmeye devam edilir. Ve yeni bir paket yakalandığında “Received” tekrardan tetiklenip, yukarıda yapılan işlemlere baştan başlanır.

Yukarıda yazılan console application sadece “Bitcoin” channel’ı dinlemek için yapılmıştır. Diğer channellar için de, tek tek konsol applicationlar yazılmalıdır.(Bu örnekteki diğer channelları dinleyen servisleri, Soruce Code’da bulabilirsiniz) Bu tabi çok fazla yükün olduğu durumlarda, performans amaçlı gidilen bir dağıtık mimarili microservis çözümüdür. İstenir ise tüm channellar için tek bir Console App de yazılabilirdi. Sonuçta gelen “Stoc.Name“‘e göre dinlenecek channel’ın, değişken olarak tanımlanması yeterlidir. İstenir ise, her bir yeni tanımlanacak yatırım ürünü için oluşturulan channel’i dinleyen yeni console applicationlar, otomatik olarak yaratılıp devreye alınabilir. Bu da size ödev olsun :) Unutulmamalıdır ki ne kadar çok MicroServices, o kadar zor Monitor ve Yönetilebilme demektir.

Geldik bir makalenin daha sonuna. Başlangıç videosunda da bahsettiğim gibi, bu senaryoyu gerçek hayatta birçok sorunu çözmek için uyarlıyabilirsiniz. Her şeyden önemlisi .Net Core 2.0, hayal ettiğim tüm Apilere tam destek vermekte ve hepsi macOS High Sierra’da canavar gibi çalışmaktadır. Yeni bir makalede görüşmek üzere hoşçakalın.

Source Code: https://github.com/borakasmer/CoreRabbitMQ

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir