.Net Core ve SignalR ile Online Bir Dükkanda Gerçek Zamanlı Ürün Düzenleme

Selamlar,

Sunset on top of Lao Zhai mountain at the bank of Li River, Xingping, Guangxi, China. © James Bian/National Geographic Traveler Photo Contest

Bu makalede, online bir game shop’un ürün bilgisini gerçek zamanlı güncellemeyi ve bunu daha performanslı nasıl yapabileceğimizi tartışacağız. Backend WebApi servisi için, .Net 5.0 ve FrontEnd uygulaması için de Angular 12 kullanacağız.

Backend:

Gelin öncelikle WebApi projemizi, Visual Studio 2019 ile aşağıdaki gibi oluşturalım.

Aşağıda görüldüğü gibi, GamesController oluşturulmuş ve random 10 oyun içinden 5 tanesi, ilgili client’a gönderilmiştir. Ayrıca, herbir client için signalR socket ile oluşacak connectionID ve ilgili client’a gönderilen 5 oyun, static bir dictionary’ye key-value şeklinde saklanmıştır. connectionID, herbir client’ın signalR Hub sınıfına bağlandığı zaman otomatik aldığı unique bir keydir. Ve bu örnekde, client’a özel bir key olarak kullanılmaktadır.

  • GamesDB“: Herbir client’ın saklanacağı, static dictionary listesi.
  • Games“: Herbir client’a gönderilen 5’li oyun listesi.
  • GameList“: Test amaçlı DB rolunde olan, oyun havuzunun bulunduğu string dizi. Herbir client için rastgele seçilen beş oyun, bu liste içinden alınmaktadır.
  • Get()“: Client, signalR Hub’a connect olduğu zaman aldığı connectionID ile bu method çağrılır, ve havuzdan rastgele 5 oyun alınır.
  • Games.ForEach(g => g.ImgPath = g.Name + “.jpg”)“: Seçilen geriye dönülecek beş oyun tek tek gezilerek, ImgPathleri Name’e göre düzenlenir.
  • GamesDB.Add(connectionID, Games)“: İlgili client’ın connectionID’sine göre seçilen oyunlar, GamesDB dictionary’e key-value olarak atanır.

GamesController:

Models/Games: Tanımlanan Games model, aşağıdaki gibidir.

SignalR Hub Sınıfı:

GamesHub(): Client’ın, connect ve disconnect olduğunda çeşitli işlemlerin yapıldığı ayrıca istenen bir ürünün güncellendiği Hub sınıfıdır.

  • OnConnectedAsync(): Bir client connect olduğu zamani aldığı connectionID, kendisine gönderilir.
  • OnDisconnectedAsync(): Bir client disconnect olduğu zaman, kendisi kayıtlı olduğu static GamesDB listesinden çıkarılır.
  • ClearProduct(): Güncelenen oyun datasını, tüm clientlara asenkron gönderen methoddur.

Dependency Injection ile signalR Hub:

IHubGameDispatcher.cs: Dependency injection için ilgili sınıfa ait bir interface’in yaratılması gerekmektedir. Yaratılan signalR Hub sınıfı, ChangeGame() adında ilgili oyunun güncellenmesini sağlayacak, asenkron bir methoda sahiptir.

HubGameDispatcher.cs:  Bu sınıf, backend’e Controller katmanında dependency injection ile projeye dahil edilip, ilgili signalR Hub sınıfına erişimi sağlar. Bu sınıf üzerinden ChangeGame() methodu çağrılıp, GamesHub sınıfı üzerindeki ChangeGame() methodu asenkron olarak çağrılmıştır. Constructorın’da, ilgili IHubContext<GamesHub> sınıfını dependency Injection ile alınmıştır.

GamesController/UpdateGame(): Aşağıda görüldüğü gibi, GamesController constructor’ında “IHubGameDispatcher” almakta ve böylece signalR GamesHub sınıfına erişilebilmektedir. UpdateGame() post methodu ile

  • “foreach (var gamesList in GamesDB)”: Tüm kullanıcıların oyun Listesi gezilmiş.
  • “var updateGame = gamesList.Value.Where(g => g.Name == game.Name).FirstOrDefault()” : İlgili liste içinde, güncellenecek oyun bulunmuş.
  • “gamesList.Value.Remove(updateGame)” : İlgili liste içinden oyun çıkarılmış.
  • “gamesList.Value.Add(game)” : Güncellenen oyunun son hali, tekrar listeye eklenmiş.
  • “GamesDB[gamesList.Key] = gamesList.Value”: Son olarak güncel liste GameDB dictionary içinde, ilgili client’ın key’i bulunup değiştirilmiştir.
  • if (isChange) await this._dispatcher.ChangeGame(game)“: Gerçekden değişen bir liste var ise, tüm clientların “ChangeGame()” function’ı signalR ile gerçek zamanlı değişen oyun parametresi ile tetiklenmiştir.

Startup.cs (ConfigureServices): .Net 5.0 tarafındaki tüm tanımlamaların yapıldığı sayfadır.

  • “services.AddCors(o => o.AddPolicy(“MyPolicy”, builder =>”: Client side taraftan, server side tarafdaki bir methodun tetiklenebilmesi için, güvenlik anlamında Cors’un açılması gerekmektedir.
  • “services.AddSingleton<IHubGameDispatcher, HubGameDispatcher>()”: IHubGameDispatcher’ın Constructor’da Dependency Injection ile dahil edilebilmesi için bağlı olduğu sınıfın tanımlanması gerekmektedir.
  • “services.AddSignalR()”: SignalR servis projeye bu şekilde eklenir.
    • “options.PayloadSerializerOptions.PropertyNamingPolicy = null”: Tanımlaması ile signalR sınıfından ile ilgili clientlara gönderilen modellerin propertylerinin, büyük küçük harfe göre değişmemesi sağlanır.

Startup.cs (Configure): SignalR sınıfının routing tanımlamasının yapıldığı, swagger’ın dosya yolunun gösterildiği, Cors için ilgili policy’nin seçildiği ve Authorization tanımlamasının yapıldığı methoddur.

  • “app.UseCors(“MyPolicy”)”: Cors için yukarıda tanımlanan policy, buradan eklenir.
  • “endpoints.MapHub<GamesHub>(“/gameHub”)”: Routing amaçlı, Client-Side’dan Server-Side’a gelinirken “/gameHub” şeklinde request çekilir ise, GamesHub signalR sınıfına yönlenilmesi sağlanmıştır.

Frontend:

Aşağıdaki komut ile, Games adında Angular projesi ayağa kaldırılır.

models/game.ts: Game model, aşağıdaki gibi oluşturulur.

app.component.ts: Sayfa yüklendiği zaman, ilgili oyun datasının servisden çekilip ekrana basıldığı ve bir oyun bilgisinin değiştiği zaman, client-side tarafda bu değişen oyun datasını real-time gösteren typescript kodları, aşağıdaki gibidir.

  • “modelGames: Array<Game>”:  Sayfaya yüklenecek tüm oyunların toplandığı dizidir.
  • “_hubConnection: HubConnection”: SignalR Games Hub sınıfına bağlanacak, connectiondır.
  • “_connectionId: string”: Client’ın signalR hub sınıfına bağlandığı zaman aldığı, unique connectionID’dir.
  • “signalRServiceIp: string = “http://localhost:42213/gameHub””: Server side tarafda, bağlanılacak Hub sınıfının yoludur.
  • Aşağıdaki kodda, ngOnInit() ile sayfa yüklendikten sonra, server-side tarafdaki GamesHub sınıfına, WebSocket ile bağlanılır.
  • Aşağıda görüldüğü gibi GamesHub sınıfına bağlanıldıktan sonra, client-side’da gösterilen bilgilendirme mesajıdır.
  • Aşağıda görüldüğü gibi, Server-Side tarafda, client GamesHub sınıfına connect olduktan sonra, alınan unique connectionID, server-side taraftan client-side tarafdaki “GetConnectionID()” function’ı trigger edilerek gönderilir. İlgili connectionID alındıktan sonra, bu sefer de client-side’dan server-side taraftaki GamesController sınıfına ait “Get” methodu çağrılarak, gelen oyun listesi başta tanımlanan modelGames[] değişkenine atanır.
Aşağıda görüldüğü gibi, server-side tarafta herhangi bir oyunun bilgisi değiştiği zaman, real-time olarak tüm clientların client-side tarafdaki “ChangeGame()” methodu tetiklenir. Eğer değişen oyun, ilgili client’ın “modelGames[]” dizisinde var ise, bulunup çıkarılır ve yenisi ilgili diziye tekrardan atılır. Böylece, değişen oyun bilgisi güncellenmiş olunur.

service/gameService.ts: Aşağıda görüldüğü gibi webapi servisinden, ilgili oyun listesinin çekilmesi için GameService yazılmıştır. GetGames() methodu ile ilgili “/games” servisine request çekilip, “Game[ ]” dizisi şeklinde result model alınır ve ekrana basılır.

app.component.html: Yukarıda görüldüğü gibi, servisden çekilen oyunların listelendiği html sayfadır. Servisden dönen “Games[ ]” result, modelGames dizisine doldurulmaktadır. İlgili dizi gezilerek oyunun resmi ve fiyat bilgisi ekrana basılır.

app.module.ts: İlgili kütüphanelerin tanımlandığı sayfadır.

Performans:

  • Dikkat edilir ise, her client için random 5 oyun seçilmektedir. Ama fiyatı değişen oyun bilgisi, tüm clientlara gönderilmektedir. Bunun yerine, herbir client’a özel oluşturulan connectionID ile “GamesDB”‘ye eklenen oyunlar tek tek incelenip, değişen oyun o client’da var ise, o zaman push notify gönderilmelidir. Kısaca, sadece o oyuna sahip clientlara notify gönderilmelidir.
  • Diğer bir durum, anlık kullanıcı sayısı eğer belli bir sayının üstüne çıkar ise, SignalR Hub sınıfı için Azure SignalR Services’inden faydalanılabilir.
  • Üçüncü bir konu, sisteme bağlı binlerce oyun olduğunu farz edelim. İşte bu durumda GamesDB static dictionary listesi yerine client’a ait oyunları Redis’de tutmak çok daha performanslı olacaktır. Key olarak Client’a ait unique connectionID ve value olarak random seçilen 5 oyun listesi olacaktır.
  • Son olarak, oyunun bilgilerinin değiştiğini duyuran trigger servisi, doğrudan DB’ye ya da bir başka microservices’e bağlanabilir. Bu durumda test , monitoring ve devops apayrı bir önem kazanmaya başlamaktadır.

Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.

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

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

3 Cevaplar

  1. Lütfullah Kabalak dedi ki:

    abi ellerine sağlık harika ve çok faydalı bir anlatım olmuş 👏🏼👏🏼👏🏼

  2. Mehmet dedi ki:

    Hocam oyun fiyatları manidar :D

  3. eroğlu recep dedi ki:

    Merhaba hocam,
    İnternette sizden başka signalr bu kadar çok farklı patformda blog yazan olmadı.Bence signalr geliştirme takımının başında olmalısınız.
    Emeğinize sağlık.

Bir cevap yazın

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