.NET’te NServiceBus Nedir? Nasıl Kullanılır?
Bu içeriğimizde Microsoft platformlarında çalışan bir mesajlaşma altyapısı olan NServiceBus üzerine odaklanacak ve Asp.NET Core mimarisinde nasıl yapılandırılabildiğini pratiksel olarak inceliyor olacağız.
NServiceBus Nedir?
NServiceBus, giriş satırında da belirtildiği gibi bir mesajlaşma altyapısı sağlayan yazılım framework’üdür. Bu framework ile temel olarak distributed sistemler arasında iletişimi sağlamak amaçlanmaktadır. Kurumsal uygulamalar için güvenilir, ölçeklenebilir ve gerçek zamanlı(realtime) mesajlaşma imkanı tanıyan ve birçok farklı kaynaktan gelen mesajları alabilen ve böylece birçok endüstri standardını destekleyen bir araçtır. Bu sayede, farklı uygulamalar arasında entegrasyonu kolaylaştırmakta ve veri uyumsuzluklarını da giderebilmektedir.
NServiceBus’ın önemli bir özelliği, mesaj üzerine dayalı bir asenkron iletişim modeli kullanmasıdır. Uygulamalar arasındaki iletişim, geleneksel bir client-server ilişkisi yerine, bir mesaj alıcısı(handler) tarafından işlenen mesajların gönderilmesi prensibine dayanmaktadır. Böylece, mesaj iletildikten sonra işlenene kadar bekletilebilmekte ve hatta yeniden denenebilmektedir. Bu sayede, sistemler arasındaki bağımlılıklar törpülenmekte ve daha esnek ve ölçeklenebilir bir mimari sağlanmış olunmaktadır.
Uzun lafın kısası NServiceBus’ın bazı özelliklerini listelememiz gerekirse eğer;
- Asenkron Mesajlaşma
İşlemler arasında mesajlar kullanılarak asenkron iletişim sağlamaktadır. Bu da, sistemlerin daha hızlı ve esnek olmasına olanak tanımaktadır. - Güvenilirlik
Mesajların iletilmesini ve işlenmesini sağlamak için bir dizi güvenilirlik özelliği içermektedir. Mesajlar kaybolduğunda veya hata durumlarında tekrar iletilmesi gibi senaryoları ele alabilmektedir. - Ölçeklenebilirlik
Büyük ve karmaşık sistemlerde ölçeklenebilirlik ihtiyaçlarını karşılamak üzere tasarlanmıştır. - Uygulama Entegrasyonu
Farklı uygulamalar ve bileşenler arasında etkili bir şekilde iletişim kurabilmek için çeşitli entegrasyon mekanizmalarını desteklemektedir. - Monitoring ve Yönetim
Çeşitli araçlar ve yönetim arabirimleri aracılığıyla uygulamaların izlenmesini ve yönetilmesini sağlamaktadır.
NServiceBus’ın Kullanım Alanları Nelerdir?
NServiceBus, genellikle büyük ve karmaşık sistemlerde, özellikle mikroservis mimarisiyle tasarlanmış projelerde tercih edilmektedir diyebiliriz. Bunların dışında yaygın olarak kullanılabileceğini söyleyebileceğimiz alanlar şunlar olabilir;
- Mikroservis Mimarisi
NServiceBus, farklı servisler arasında asenkron ve güvenilir iletişimi kolaylaştırabildiği ve sistemlerin modüler ve ölçeklenebilir olmasını sağlayabildiği için mikroservis mimarisiyle tasarlanmış projelerde altyapı sağlamaya elverişlidir. - İş Süreç Yönetimi
NServiceBus, distributed iş süreçlerini yönetmek için de kullanılabilir. - Event Log & Monitoring
Sistemdeki event’leri izlemek ve logları yönetmek amacıyla da tercih edilebilir. - Uygulama Entegrasyonu
Farklı uygulamalar ve bileşenler arasında güvenilir iletişim kurmak ve sistem içindeki farklı parçalar arasında entegrasyonu sağlamak üzere kullanılabilir. - Distributed Sistemler
Büyük ölçekli ve dağıtık sistemlerde kullanılarak, sistemler arasındaki iletişimi yönetmek ve dağıtık iş yüklerini etkili bir şekilde dağıtmak için kullanılabilir. - Message-Base Integration
NServiceBus, temel olarak mesajlar aracılığıyla iletişim kurmayı destekleyecek şekilde tasarlanmasından dolayı sistemlerin daha gevşek bağlı, esnek ve ölçeklenebilir olmasını sağlayabileceğinden dolayı message-base integration süreçlerinde tercih edilebilir.
NServiceBus & MassTransit Farkı
Kâh NServiceBus olsun kâh MassTransit olsun her ikisi de .NET platformunda kullanılabilen, distributed sistemlerde asenkron mesajlaşma sağlamaya yönelik framework’lerdir. Her ikisi de benzer temel prensipleri paylaşıyor olsa da, bazı farklılıklar da bulunmaktadır. İşte NServiceBus ve MassTransit arasındaki bazı temel farklar şunlardır;
- Lisans Modeli
- NServiceBus, açık kaynak değildir ve kullanıcılar için belli bir ücret talep etmektedir.
- MassTransit ise tamamen açık kaynak ve ücretsizdir.
- Topluluk ve Destek
- NServiceBus, ticari bir ürün olduğu için müşterilere özel bir destek sunmaktadır.
- MassTransit, open source olduğu için desteğini topluluktan almaktadır.
- Kapsamlılık ve Karmaşıklık
- NServiceBus, genellikle daha fazla özellik ve kapsamlı bir yapı sunmaktadır. O yüzden düşük öğrenme eğrisine sahiptir.
- MassTransit ise basit ve kullanımı kolay bir API sunmaktadır. Dolayısıyla başlangıçta daha hızlı bir şekilde öğrenilebilmektedir.
Görüldüğü üzere her ikisinin de kendine ait avantaj ve dezavantajları mevcuttur. Hal böyleyken seçim; kişisel tercihlere, projenin ihtiyaç ve ölçeklendirme beklentilerine göre değişkenlik gösterecektir.
NServiceBus’ın Temel Kavramları
NServiceBus’ın çalışma sürecindeki davranışları belli başlı kavramlara karşılık gelen yapılar üzerinden şekillenmektedir. Bu kavramları öncelikle öğrenmek, yapılan işteki düşünceye hakimiyet açısından bizlere fayda sağlayacaktır.
- Endpoint Kavramı
NServiceBus’ta endpoint kavramı, distributed sistem olarak tasarlanmış uygulamanın belirli bir işlevini yerine getiren her bir servisine karşılık terminolojik olarak kullandığımız bir terimdir. Misal olarak; bir e-ticaret sistemindeki siparişten, müşteri işlerinden ve ödeme işlemlerinden sorumlu olan her bir bileşen uygulamanın bir endpoint’i olarak düşünülebilir. Yani anlayacağınız bizler, NServiceBus framework’ü ile yaptığımız çalışmalarda servis’lere servis değil de endpoint diyoruz :)Bir uygulamadaki endpoint’lerin her biri mesaj alıp gönderebilmekte ve bu sayede diğer endpoint’ler ile iletişim kurabilmektedir. Ve bu işlemi yaparken endpoint isimlerinden istifade edilmektedir. Yine örnek vermemiz gerekirse eğer; sipariş bileşeni sadece müşteri bileşenine bir mesaj gönderecekse bunu müşteri servisinin endpoint adı her ne ise sadece onu belirterek rahatlıkla gerçekleştirebilecektir. - Message Kavramı
Uygulamadaki farklı bileşenler/endpoint’ler arasında iletişim kurmak için kullanılan bilgi taşıyıcılarına mesaj denmektedir. Mesajlar, genellikle bir eylemi temsil etmekte ve bir endpoint’ten diğerine iletilmektedirler.NServiceBus’ta mesajları ‘Command’ ve ‘Event’ olmak üzere iki ana kategoriye ayırabiliriz.- Command
Bir eylemi gerçekleştirmek amacıyla (genellikle) hedef servise gönderilerek yapılacak işlemin başlatılmasını sağlayacak olan komut mahiyetindeki mesaj türüdür.public class PlaceOrderCommand : ICommand { public Guid OrderId { get; set; } public string ProductName { get; set; } public int Quantity { get; set; } // Diğer gerekli özellikler... }
NServiceBus’ta bir mesajın ‘Command’ olabilmesi için
ICommand
interface’inden implement edilmesi gerekmektedir. - Event
Bir olayın gerçekleştiğini bildirmek amacıyla (genellikle) o olayla ilgili/ilişkili işlem yapacak olan birden fazla servise(ki bu servisler abone/subscribe olarak nitelendirilir) haber gönderip işlem yapılmasını sağlayacak olan durum mahiyetindeki mesaj türüdür.public class OrderPlacedEvent : IEvent { public Guid OrderId { get; set; } public string ProductName { get; set; } public int Quantity { get; set; } // Diğer gerekli özellikler... }
NServiceBus’ta bir mesajın ‘Event’ olabilmesi için
IEvent
interface’inden implement edilmesi gerekmektedir.
ICommand
veIEvent
interface’leri genel bir mesaj türünü belirtmek için NServiceBus tarafından bizlere sunulan, ancak ihtiyaç noktalarında kendimize göre custom interface’ler ile özelleştirebileceğimiz arayüzlerdir. Bu şekilde bir ayrımın, mikroservis mimarilerinde ve distributed sistemlerde yaygın olarak kullanıldığını göreceksiniz. Çünkü bu yaklaşım, bir servisin iç durumunu ve davranışını daha açık bir şekilde ifade etmeye ve servisler arası etkileşimi daha iyi yönetmeye yardımcı olmaktadır.Velhasıl kelam anlayacağınız, çalışma süreçlerinde bu mesaj türlerinden hangisinin seçileceği mesajların taşıdığı bilgi türü ve gönderim amacına göre belirlenmektedir.
- Command
- Handler Kavramı
Bir endpoint tarafından yayınlanmış olan mesajın farklı bir endpoint ya da endpoint’ler tarafından işlenmesini sağlayacak olan kod parçacığıdır. Yani MassTransit’te ki ‘Consumer’ kavramının muadilidir diyebiliriz. Her endpoint, istediği mesaj türünü işleyebilecek bir veya birden fazla handler barındırabilmektedir. - Transport Kavramı
Endpoint’ler arası mesajların fiziksel olarak nasıl taşınacağını belirten bir alt sistemdir. Misal olarak; RabbitMQ, Azure Service Bus veya MSMQ gibi teknolojiler NServiceBus’da transport olarak nitelendirilmektedirler.
.NET Core’da NServiceBus’ın Kullanımı
Şimdi NServiceBus’ın kullanımını pratiksel olarak incelemeye başlayabiliriz. Bunun için Console Application ve Asp.NET Core Web API ortamları üzerinden örneklendirme yapmayı uygun görüyorum. İlk olarak Console Application ortamında incelemeye başlayalım.
Console Application Yapılandırması
Console Application’da NServiceBus’ı örneklendirebilmek için öncelikle ‘ServiceA’, ‘ServiceB’ ve ‘ServiceC’ isminde üç adet uygulama oluşturalım. Bunlardan; ‘ServiceA’yı mesaj yayınlayacak, ‘ServiceB’ ve ‘ServiceC’yi yayınlanmış olan mesajları tüketecek konumda düşünebiliriz.
Servisleri oluşturduktan sonra ilk iş olarak tüm servislere aşağıdaki kütüphaneleri yükleyelim;
Şimdi ilk olarak ‘ServiceA’ya odaklı bir şekilde geliştirmede bulunalım.
using Shared; using Shared.Messages; EndpointConfiguration endpointConfiguration = new(ServiceRegistryInformations.ServiceAEndpointName); var rabbitMQTransport = endpointConfiguration.UseTransport<RabbitMQTransport>(); rabbitMQTransport.ConnectionString("..."); rabbitMQTransport.UseConventionalRoutingTopology(QueueType.Quorum); endpointConfiguration.EnableInstallers(); IEndpointInstance endpointInstance = await Endpoint.Start(endpointConfiguration); while (true) { Console.WriteLine("Mesajı yazınız..."); #region Hedef Endpoint'e Mesaj Gönderme ExampleMessage message = new() { Message = Console.ReadLine() }; await endpointInstance.Send(ServiceRegistryInformations.ServiceCEndpointName, message); #endregion #region Tüm Endpoint'lere Event Yayınlama ExampleEvent @event = new() { Message = "...Event content..." }; await endpointInstance.Publish(@event); #endregion Console.WriteLine("Devam mı? D/H"); if (Console.ReadKey().Key == ConsoleKey.H) break; } await endpointInstance.Stop(); Console.Read();
Yukarıdaki kod bloğunu incelersek eğer; 4. satırda EndpointConfiguration
nesnesinin üretildiğini görüyoruz. Bu nesne bir endpoint’in(yani bu servisin) nasıl davranış sergileyeceği ve çeşitli özelliklerinin neler olacağı şeklinde türlü yapılandırmaları sağlamaktadır. İlgili nesnenin constructor’ına verilen metinsel değer bu servisin yani endpoint’in adını ifade etmektedir.5. satırda ise UseTransport
metodu ile bu endpoint’in transport ayarları yapılandırılmaktadır. Burada görüldüğü üzere RabbitMQTransport
sınıfı kullanılarak RabbitMQ message broker’ına göre bir transport ayarlamasında bulunulmaktadır.
7. satırda ise UseConventionalRoutingTopology
metodu ile RabbitMQ taşıma yöntemi yapılandırılmakta ve kullanılacak routing topolojisi belirlenmektedir. Buradaki QueueType.Quorum
ifadesi, RabbitMQ’da kullanılan bir özelliktir ve RabbitMQ 3.8.0 ve üzeri sürümlerde tanıtılmıştır. Bu özellik, bir kuyruğun ‘quorum’ modda çalışmasını ifade etmektedir. Quorum modu, RabbitMQ kuyruklarının yüksek güvenilirlik ve dayanıklılık sağlamak için geliştirilmiş bir modudur.
9. satırda ise EnableInstallers
metodu ile uygulamanın çalışma zamanında mesaj taşıma alt yapısı otomatik olarak yüklenmekte ve yapılandırılmaktadır. Bu yapılandırma sayesinde NServiceBus, belirli bir mesaj tipini karşılayabilmek ve işleyebilmek için gerekli kuyruk oluşturma işleminin altyapısını sağlamaya yardımcı olmakta ve işlem bittikten sonra kuyruğun temizlenmesini sağlamaktadır. Unutulmamalıdır ki, EnableInstallers
metodu, geliştirme ve test süreçlerinde süreci basitleştirse de production ortamında genellikle devre dışı bırakılmaktadır. Çünkü production ortamında altyapıyı manuel olarak yapılandırmak daha doğdurudur!
11. satırda ise Endpoint.Start
metodu ile ilgili endpoint başlatılmakta ve bu endpoint üzerinden mesaj alışverişini gerçekleştirebilir hale getirmektedir.
13 ile 31. satır aralığında ise mesaj yayımı gerçekleştirilmektedir. 17 ile 20. satır aralığında hedef endpoint olan ‘ServiceC’ye, 22 ile 25. satır aralığında ise tüm servislere mesaj gönderilmektedir.
Şimdi ‘ServiceA’yı geliştirdiğimize göre ‘ServiceB’ ve ‘ServiceC’ye odaklanabiliriz.
‘ServiceB’;
using Shared; EndpointConfiguration endpointConfiguration = new(ServiceRegistryInformations.ServiceBEndpointName); var rabbitMQTransport = endpointConfiguration.UseTransport<RabbitMQTransport>(); rabbitMQTransport.ConnectionString("..."); rabbitMQTransport.UseConventionalRoutingTopology(QueueType.Quorum); endpointConfiguration.EnableInstallers(); IEndpointInstance endpointInstance = await Endpoint.Start(endpointConfiguration); //await endpointInstance.Stop(); Console.Read();
‘ServiceC’;
using Shared; EndpointConfiguration endpointConfiguration = new(ServiceRegistryInformations.ServiceCEndpointName); var rabbitMQTransport = endpointConfiguration.UseTransport<RabbitMQTransport>(); rabbitMQTransport.ConnectionString("..."); rabbitMQTransport.UseConventionalRoutingTopology(QueueType.Quorum); endpointConfiguration.EnableInstallers(); IEndpointInstance endpointInstance = await Endpoint.Start(endpointConfiguration); //await endpointInstance.Stop(); Console.Read();
Görüldüğü üzere her üç serviste özünde aynı yapılandırmaya sahiptir. Sadece ‘ServiceA’da gönderilen mesajların ‘ServiceB’ ve ‘ServiceC’de işlenmeleri gerekmektedir. Bunun için her iki endpoint’te handler yapılanmaları oluşturmamız gerekmektedir.
public class ExampleEventHandler : IHandleMessages<ExampleEvent> { public Task Handle(ExampleEvent message, IMessageHandlerContext context) { Console.WriteLine($"{message.Message}"); return Task.CompletedTask; } }
public class ExampleMessageHandler : IHandleMessages<ExampleMessage> { public Task Handle(ExampleMessage message, IMessageHandlerContext context) { Console.WriteLine($"Received message : {message.Message}"); return Task.CompletedTask; } }
Bu iki sınıfı her iki projede oluşturalım. -Hocam, bu handler sınıflarını ilgili projelerin yapılandırmalarında bildirmeyecek miyiz? şeklinde sorunuzu duyar gibiyim… Hayır! NServiceBus kütüphanesinde handler sınıfları, ilgili proje içerisinde assembly’de reflection ile taranmakta ve handler sınıfı hangi mesajı generic olarak ifade ediyorsa, o türden bir mesaj yayınlandığı taktirde ilgili handler sınıfı tetiklenmektedir. Bu yüzden handler’ları ilgili projeler içerisinde sadece oluşturmak yeterli ve kafidir.
Ayrıca örnekte kullandığımız command ve event mesaj türlerinin içerikleri de aşağıdaki gibi olacaktır. Tabi bu mesaj tanımlarının ‘Shared’ isimli class library’de olması gerekmektedir.
public class ExampleEvent : IEvent { public string Message { get; set; } }
public class ExampleMessage : ICommand { public string Message { get; set; } }
Şimdi uygulamayı bu vaziyette derleyip ayağa kaldırırsak eğer aşağıdaki gibi çalışma sergilediğini gözlemliyor olacağız.Görüldüğü üzere ‘ServiceA’dan yazılan mesaj direkt ‘ServiceC’ye gönderilmekte ve ayrıca her iki servise de bir event yayınlanmaktadır. Anlayacağınız NServiceBus sayesinde servisler arasında mesaj gönderme sürecinde ayrımı yukarıdaki çalışmada yaptığımız davranışta olduğu gibi endpoint üzerinden rahatlıkla sağlayabilmekte ve temiz bir çalışma sergileyebilmekteyiz.
Console Application ile çalışmamızı gerçekleştirdiğimize göre sıra Asp.NET Core Web API ortamında NServiceBus’ı incelemeye gelmiştir.
Asp.NET Core Web API Yapılandırması
Burada ki incelemeyi de ‘WebAPIA’ ve ‘WebAPIB’ isminde oluşturduğumuz iki API servisi üzerinden yürüteceğiz. İlk olarak bu servislere gerekli kütüphanelerin yüklenmesi gerekmektedir. Yalnız Asp.NET Core mimarisinde çalıştığımızdan dolayı NServiceBus kütüphanesinden ziyade, mimarinin HostBuilder üzerindeki entegrasyonunu sağlamak ve Asp.NET Core uygulamalarındaki NServiceBus kullanımını kolaylaştırmak için tasarlanmış olan NServiceBus.Extensions.Hosting kütüphanesini kullanıyor olacağız. Bu sebeple iki uygulamaya da adı geçen kütüphaneyle birlikte NServiceBus.RabbitMQ kütüphanesini yükleyelim.
Şimdi ilk olarak mesaj gönderici konumunda olacak olan ‘WebAPIA’ servisinin ‘Program.cs’ dosyasını ele alalım;
using NServiceBus; using Shared; using Shared.Messages; var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Host.UseNServiceBus(context => { EndpointConfiguration endpointConfiguration = new(ServiceRegistryInformations.WebAPIAEndpointName); var rabbitMQTransport = endpointConfiguration.UseTransport<RabbitMQTransport>(); rabbitMQTransport.ConnectionString(builder.Configuration.GetConnectionString("RabbitMQ")); rabbitMQTransport.UseConventionalRoutingTopology(QueueType.Quorum); endpointConfiguration.EnableInstallers(); return endpointConfiguration; }); var app = builder.Build(); app.UseSwagger(); app.UseSwaggerUI(); app.MapPost("/send-message", async (IMessageSession messageSession) => { ExampleMessage exampleMessage = new() { Message = "Example message" }; await messageSession.Send(ServiceRegistryInformations.WebAPIBEndpointName, exampleMessage); return TypedResults.Ok(); }); app.Run();
Görüldüğü üzere 10 ile 19. satır aralığında Asp.NET Core mimarisine uygun NServiceBus yapılandırmasında bulunuyoruz. Burada kullandığımız tüm sınıfları içeriğimizin önceki satırlarında izah ettiğimizden dolayı tekrara düşmemek için geçiyorum.
Geliştirdiğimiz bu yapılanmayı test etmek için 26 ile 32. satır aralığında oluşturduğumuz minimal api’a da dikkatinizi çekerim.
Aynı şekilde yayınlanmış mesajları tüketecek konumdaki ‘WebAPIB’ servisimizi de geliştirmemiz gerekirse eğer;
using NServiceBus; using Shared; var builder = WebApplication.CreateBuilder(args); builder.Host.UseNServiceBus(context => { EndpointConfiguration endpointConfiguration = new(ServiceRegistryInformations.WebAPIBEndpointName); var rabbitMQTransport = endpointConfiguration.UseTransport<RabbitMQTransport>(); rabbitMQTransport.ConnectionString(builder.Configuration.GetConnectionString("RabbitMQ")); rabbitMQTransport.UseConventionalRoutingTopology(QueueType.Quorum); endpointConfiguration.EnableInstallers(); return endpointConfiguration; }); var app = builder.Build(); app.Run();
şeklinde yapılandırılması kâfidir. Tabi bu yapılandırmanın dışında ayriyeten ExampleMessage
‘ı tüketecek olan handler sınıfını da oluşturmamız gerekmektedir.
public class ExampleMessageHandler : IHandleMessages<ExampleMessage> { public Task Handle(ExampleMessage message, IMessageHandlerContext context) { Console.WriteLine($"Received message : {message.Message}"); return Task.CompletedTask; } }
İşte bu kadar 🙂 Yapılan bu çalışmalar neticesinde uygulamaları derleyip çalıştırdığımızda aşağıdaki ekran görüntüsünde olduğu gibi neticeyle karşılıyor olacağız.
Nihai olarak;
Farklı service bus yapılanmalarını görmek, gözlemlemek her daim ihtiyaç noktalarında farklı bir alternatif değerlendirmesine imkan tanıyacak ve böylece geliştirdiğimiz yazılımlar senaryolarına göre daha verimli bir çalışma gerçekleştirebiliyor olacaktır. Ben deniz, NServiceBus’ın yukarıdaki satırlarda anlattığımız şeklindeki endpoint mantığıyla çalışmasının, biz yazılımcılar açısından geliştirme süreçlerinde oldukça pratik ve etkili bir davranış sağladığını düşünüyorum ve bu mantığı, kod sadeliği ve yönetimi açısından önemli bir katkı olarak değerlendiriyorum.
Sizlere de nacizane tavsiyem, NServiceBus’ın gücünü daha tereffuatlı bir şekilde test etmek istiyorsanız makalenin sonundaki github adresinde paylaşmış olduğum bu çalışmaların hepsini indirip, hem Web API hem de console uygulamalarını kümülatif bir şekilde çalıştırıp, kendi aralarında iletişim kurmalarını deneyimleyerek gözlemde bulunabilirsiniz.
İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…
Not : Örnek çalışmaya aşağıdaki github adresinden erişebilirsiniz.
https://github.com/gncyyldz/NServiceBus.Example