MassTransit ZeroToHero — 1

Ahmet Murat Gençay
9 min readNov 10, 2020

MassTransit hakkında örnek uygulamalar ve anlatımlarda bulunacağım bu yazı serisinde en temel kavramlardan ileri seviye konulara kadar örneklere yer vereceğim. Tüm örnekleri github’da bulabilirsiniz.

Önceki yazımda RabbitMQ ile Queue yapılarına giriş yapmıştık bu yazımda ise konuyu bir aşama daha ilerletip, mesaj brokerlar’da ihtiyaç duyduğumuz Exception Handling, Test, Activity, Saga gibi yapıları kendi bünyesinde barındıran MassTransite giriş yapacağız.

MassTransit

MassTransit

MassTransit Nedir?

MassTransit, .NET için mesaj tabanlı, loosely-coupled (gevşek bağlı) asenkron iletişimden yararlanan uygulamalar ve hizmetler oluşturmayı kolaylaştıran ücretsiz, açık kaynaklı bir dağıtılmış uygulama framework’üdür.

Ne zaman kullanılır?

Son zamanlarda geliştirilen hemen her uygulamada mikro servis mimarisi, bunların haberleşmesi, queue mekanizmaları ile çalışan mesajlaşma temelli yapılar vb. ihtiyaçlar giderek artmaktadır. Her uygulamada belirli teknolojilere zaman harcayıp bunlar ile uzun araştırmalar ve altyapılar oluşturulmak gerekmektedir. Proje ihtiyaçlarına göre bu teknolojilerde de sürekli değişiklik yapmak zorunda kaldığımız durumlar ortaya çıkmaktadır. Bugün RabbitMQ ile başladığınız bir uygulamayı daha sonra ActiveMQ’ya geçirmeniz gerekmekte, bu teknoloji geçişleri de bize zaman kaybı ve ön görülemez planlama problemleri çıkartmaktadır.

İşte bu noktada en temel olarak MassTransit bize, ORM sistemlerinin veritabanı teknolojileri üzerinde sağlamış olduğu kolaylığı, mesaj tabanlı iletişim araçları için sağlamaktadır. EntityFramework ile çalışırken, kullandığınız veritabanı teknolojisinin PostgreSql veya MSSQL olduğunu umursamadan geliştirme yapabilirsiniz.

Benzer şekilde MassTransit RabbitMQ, ActiveMQ, Azure Service Bus, Amazon SQS ve InMemory gibi mesajlaşma teknolojilerini destekler. Bu teknolojiler arası geçiş yalnızca basit bir konfigürasyon ayarı ile gerçekleştirilebilir. Bunun yanında MassTransit mesajlaşma teknolojilerindeki Saga, Activity gibi yapıları da destekler ve Test için bize kolaylık sağlayacak kütüphanelere sahiptir.

Mesaj Transfer Yapıları

MassTransit’ de ilk olarak InMemory transfer ve RabbitMQ transfer örneklerinden bahsedeceğim.

InMemory Transfer

Herhangi bir mesaj broker kurulmasını veya çalıştırılmasını gerektirmediğinden test için harika bir araçtır. Aynı zamanda çok hızlıdır. Ancak kalıcı değildir ve bus durdurulursa veya işlem sona ererse mesajlar kaybolur. Bu nedenle, genellikle production ortamı için akıllıca bir seçenek değildir.

InMemory transfer, yalnızca tek bir process içinde kullanılmak üzere tasarlanmıştır. Birden fazla process arasında iletişim kurmak için kullanılamaz (aynı makinede olsalar bile).

InMemory Message Transfer Example

InMemory Message Transfer örneğinde, Console’da çalışan bir uygulama kullanıcının girdiği mesajı bus üzerinden publish eder, receive endpointi ile alınan mesaj yine console’a yazar.

InMemory Message Example

InMemory Message Transfer Example Code

InMemory Message Example Code

RabbitMQ

RabbitMQ yaygın olarak kullanılan open source bir mesaj broker uygulamasıdır. Queue(Kuyruk) yapısı ile çalışır. Küçük ölçekli uygulamalardan kurumsal uygulamalara kadar birçok kullanım alanı mevcuttur. Cloud ve on-premise ortamlarda deploy edilebilir ve çoklu mesajlaşma protokollerini destekler. Distributed çalışma imkanı sağlar ve işletim sistemi bağımsız çalışır. Daha fazlası için RabbitMQ ile ilgili yazımdan faydalanabilirsiniz.

MassTransit, birçok gelişmiş özellik ve yetenek dahil olmak üzere RabbitMQ’yu tam olarak destekler.

RabbitMQ Message Transfer Example

RabbitMQ Message Transfer örneğinde, çalışma mantığı bir önceki örneğimizle aynı olan yalnızca transfer yapısı olarak RabbitMQ kullanan bir uygulama oluşturduk.

Bir mesaj broker kullanmamız bize, mesaj hattının uygulamamızın dışında yönetilmesi imkanı sağlar. Diğer bir deyişle gönderdiğimiz bir mesaj işleme alınmadan uygulama sonlansa dahi RabbitMQ kendi sisteminde bu mesajı saklayacak ve uygulama tekrar ayağa kalktığında mesaj işletilecektir.

RabbitMQ Message Transfer Example

RabbitMQ Message Transfer Example Code

RabbitMQ Message Transfer Example Code

Yukarıda, çalışma mantığı aynı olan yalnızca mesaj transfer yöntemleri farklı olan iki örnek yaptık. MassTransit’in bize sağlamış olduğu en önemli özelliklerden birisi de çok kolay bir şekilde transfer yapılarımızı değiştirebiliyor olmamızdır.

Bus oluşturma esnasındaki fark
Konfigürasyon esnasındaki fark

Gördüğünüz üzere yalnızca “Bus Factory” kısmı ve “RabbitMQ” ya özel konfigürasyon kısmında farklılık var. Bu yapı bize mesaj brokerlarından soyutlanmış bir sistem oluşturmamız için kolaylık sağlamaktadır.

Mediator

İlk iki başlıkta MassTransit üzerinden mesajlaşma yapılarını kurguladık ve basit anlamda örnekler gerçekleştirdik. Yalnız gerçek uygulamalarda yapılar daha karmaşık ve dağıtık bir haldedir. Tüm işlemleri tek bir sınıf içerisinde toplamaktansa her sınıfın kendi işini yaptığı single-responsiblity’e uygun yapılar kurulmalıdır. Ayrıca mesaj yapılarında bazen gönderdiğimiz mesajların sonuçları üzerinde işlem yapmak isteriz. İşte bu noktada MassTransit bize Mediator yaklaşımı ile ihtiyaçlarımızı karşılayacak yapılar sunar.

Mediator Design Pattern

Mediator Pattern

Nesne yönelimli programlamada, programlar genellikle birçok sınıftan oluşur. İş mantığı ve hesaplama bu sınıflar arasında dağıtılır. Bununla birlikte, özellikle bakım ve / veya yeniden düzenleme sırasında bir programa daha fazla sınıf eklendikçe, bu sınıflar arasındaki iletişim sorunu daha karmaşık hale gelebilir. Bu durum, programın okunmasını ve sürdürülmesini zorlaştırır. Ayrıca, herhangi bir değişiklik diğer birkaç sınıftaki kodu etkileyebileceğinden programı değiştirmek zor olabilir.

Mediator pattern ile, nesneler arasındaki iletişim bir aracı nesne içinde yönetilir. Nesneler artık birbirleriyle doğrudan iletişim kurmaz, bunun yerine arabulucu aracılığıyla iletişim kurar. Bu durum, iletişim kuran nesneler arasındaki bağımlılıkları azaltır.

MassTransit Mediator Desing Pattern

MassTransit, publisherlar, consumerlar ve sagalar için tam destek sağlayan bir mediator uygulaması içerir. MassTransit Mediator, proses içi ve bellek içi olarak çalışır. Maksimum performans için, mesajlar serileştirilmek yerine referans olarak aktarılır ve kontrol akışları doğrudan Publish / Send metotları ile consumer’a gönderilir.

Mediator Periodic Table Example

Mediator Periodic Table örneğinde, console üzerinden Atom Numarası(ElementId) ile istekte bulunulur ve MassTransit Mediator yapısı bu mesajı işleyerek Periodic Table Consumer’a iletir. Periodic Table Consumer kendisine gelen istekteki ElementId bilgisine göre Periodic Table Servisine sorgu atar ve sonucu console’a yazar.

Mediator Periodic Table Example Schema
Mediator Periodic Table Example

Mediator Periodic Table Example Code

Element Contracts Code
Periodic Table Console App Code
Periodic Table Consumer Code

Bu örnekte , consumer’ı ayrı bir sınıfa alarak soyutladık, bu sayede uygulama içerisinde göndermek istediğimiz her mesaj için bağımsız consumer’lar oluşturabilir ve her birinin kendi görevinden sorumlu olduğu yapılar kurabiliriz.

RabbitMQ

Mediator yapısı bizlere, consumerları sistemden soyutlama imkanı sunar. Her mesaj için farklı bir consumer oluşturabiliriz ve mesajın iletilmesi durumunda gerekli işlemleri ilgili consumer’ın içerisinde yönetebiliriz.

Bunun yanında mediator yapısı tek bir process içerisinde çalışır. Diğer bir deyişle mikro servis mimarisine uygun değildir. Bu tarz ihtiyaçlar için bir mesaj broker yapısı kullanmalıyız.

Bu noktada devreye RabbitMQ girer. Bu örneğimizde RabbitMQ Consumer App ve RabbitMQ Publisher App diye iki uygulama oluşturacağız ve her iki uygulama da konfigürasyon esnasında RabbitMQ ile çalışacak şekilde ayarlayacağız. Geri kalan her şeyi MassTransit bizim için yönetecektir.

RabbitMQ Periodic Table Example Schema
RabbitMQ Periodic Table Example
RabbitMQ Periodic Table Consumer Code
RabbitMQ Periodic Table Publisher Code

Exceptions

Şu ana kadar yaptığımız örneklerde best-case üzerinden çalıştık. Peki istisnai durumlar oluştuğunda ne yapabiliriz. ElementId sayısal bir alan olmasına karşın, metin içeren ifadeler barındırırsa sistem nasıl davranır ve bunu nasıl yönetebiliriz. İşte tüm bu gibi durumlar için ExceptionHandling mekanizması gerekmektedir.

MassTransit, uygulamanızın exceptionlardan kurtulmasına ve bunlarla başa çıkmasına yardımcı olacak bir dizi özellik sağlar.

  1. Retry Configuration, Exception oluşması durumunda mesajın tekrar gönderilmesi için konfigürasyon ayarları;
    None: Tekrar yok.
    Immediate: Hemen bir daha dene.
    Interval: Sabit bir süre sonra tekrar dene.
    Intervals: Farklı süreler bekle ve dene.
    Exponential: Üstel artan aralıklar ile dene.
    Incremental: Sabit artan aralıklar ile dene.
  2. Exception Filters, bazı durumlarda oluşan tüm exceptionlar için işlem yapmak istemeyebiliriz. Bu durumlarda Ignore ve Handle metodları yardımı ile ayarlamalarımızı yapabiliriz.
  3. Redelivery, sistemde oluşan hatalar her zaman kısa süreli problemlerden meydana gelmez. Uzak sunucu, SQL serverin çökmesi gibi durumlarda retry süremizi daha uzun vermek isteyebiliriz. Bu gibi durumlarda message scheduler kullanmamız gerekir. Scheduler yapısıyla daha uzun aralıklı süreler ile retry mekanizması kurabiliriz.
  4. Outbox, uzun süren hata durumlarında message scheduler’a alternatif olarak InMemoryOutbox kullanılabilir. Bu yapıda mesaj consume edilene kadar Outbox (Giden Kutusu) mesajı kendi bünyesinde barındırır ve uzun süren aralıklar ile retry imkanı sağlar.
  5. Faults, belirli bir mesaj için oluşturulan consumer’larda fırlatılan exceptionları yönetmek amacıyla ilgili consumer’a özel fault-consumer oluşturulabilir. Bu fault-consumer da exception’ı yakalayıp gerekli işlemleri gerçekleştirebiliriz.

RabbitMQ Periodic Table Exception Handling Example

Örnek için kullanım senaryoları;

  1. ElementId: 11 gönderirsek sonuç Na-Sodium(ElementAccepted) olacaktır.
  2. ElementId: 130 gönderirsek sonuç Element Not Found!(ElementRejected) olacaktır.
  3. ElementId: Na gönderirsek sonuç Na-Sodium(ElementAccepted) olacaktır.
  4. ElementId: Mm gönderirsek sonuç Element Not Found!(ElementRejected) olacaktır.

Hazırlık:

  1. İlk olarak Periodic Table Consumer’ımızın iki sonuç döndüğü yapıyı kuruyoruz. Bununla gelen ElementId parametresine karşılık bir element var ise ElementAccepted yok ise ElementRejected sonucunu döneceğiz.
  2. İkinci olarak, gelen ElementId alanı sayısal bir alan değilse exception throw edeceğiz.
  3. Daha sonra Periodic Table Consumer’ımız için Periodic Table Fault Consumer oluşturacağız. Fault Consumer sayesinde oluşan exceptionları yakalayabileceğiz.
  4. Exception konfigurasyonunda, hata durumunda 3 kez hemen deneme (Retry(Immediate(3))) yapısı kuracağız ve fault durumundaki queue’yu oluşturacağız.
RabbitMQ Periodic Table Exception Example
PeriodicTableConsumerApp Code
ElementContracts Code
PeriodicTablePublisherApp Code
PeriodicTableConsumer Code
PeriodicTableFaultConsumer Code

Testing

MassTransit, tamamen bellekte çalışan ancak gerçek bir mesaj aracısına yakın davranan test araçları içerir. Bu test araçları, test edilmesi karmaşık olan asenkron işlemler için bize kolaylık sağlar.

Harness

Test harness, bus yapılandırmasını içerisinde barındırır. InMemory harness, kullanımı kolaydır, herhangi bir harici bağımlılık olmadan tamamen bellek içinde çalışır. Gönderilen, yayınlanan ve tüketilen mesajlar, serialization, dispatch, deserialization, consumer instantiation, ve asynchronous execution dahil olmak üzere tüm bir yaşam döngüsünden geçer. Bu da gerçeğe yakın simüle edilebilir bir test ortamı sağlar.

NUnit Test Examples

NUnit Test Examples

Fine Code Coverage

Bu tool sayesinde geliştirilen kodun ne kadarlık kısmının test edildiğini gözlemleyebiliriz. Free ve kullanımı basit bir araçtır.

Fine Code Coverage

Test Structure

Test örneğimize ilk olarak test dosya yapısından bahsederek başlamak istiyorum. Test dosyamız üç kısımdan oluşmakta;

  1. Inits, Bu kısma çalıştırılacak her test metodundan önce ve sonrasında yapılacak işlemleri koyacağız. Örneğimizde harness ve consumerların oluşturulduğu yer burası olacak.
  2. Utils, genellikle test metodlarının içeriği uzun olur ve ve gereğinden fazla kod tekrarı barındırır. Bu duruma engel olmak için sık kullandığımız kontrol metodlarını utils altına ekleyeceğiz ve test metotlarında kullanacağız.
  3. Tests, Gerçek test senaryolarını oluşturduğumuz ve test kodlarımızın olduğu bölümdür.
Test Structure

Test Inits

Her test metodundan önce SetUp ile harness ve consumerları oluşturuyoruz. TearDown’da ise test metodu tamamlandığında harness’ı durduruyoruz.

Test Inits

Test Utils

Test metodlarımızda kullanmak üzere kod tekrarının önüne geçmek için kontrol metodlarımızı koyduğumuz bölüm.

  1. IsConsumerWorked
    harness, consumerHarness ve faultConsumerHarness’ın ilgili mesajları consume ettiğini kontrol ettiğimiz metottur.
  2. IsEventsPublished
    harness’ın ilgili mesajları publish ettiğini kontrol ettiğimiz metottur.
  3. IsElementRejectedEquals
    ElementRejected nesnesinin gönderilen değerler ile eşit olduğunu kontrol ettiğimiz metottur.
  4. IsElementAcceptedEquals
    ElementAccepted nesnesinin gönderilen değerler ile eşit olduğunu kontrol ettiğimiz metottur.
Test Utils

Test Cases and Test Structure

Aşağıdaki görselde 8 adet test senaryomuz ve test yapısı gösterilmektedir.

Test Cases and Test Structure

Should_publish_the_element_accepted_event; ElementId:11 olarak gönderdiğimizde, herhangi bir fault oluşmadan consumer’ın ElementAccepted mesajı döndüğü ve publisher’ın da bu mesajı aldığını test eden metot.

Should_publish_the_element_accepted_event

Should_publish_the_element_rejected_event; ElementId:130 olarak gönderdiğimizde, herhangi bir fault oluşmadan consumer’ın ElementRejected mesajı döndüğü ve publisher’ın da bu mesajı aldığını test eden metot.

Should_publish_the_element_rejected_event

Should_publish_the_element_accepted_event_after_fault; ElementId:Na olarak gönderdiğimizde, bir fault oluştuktan sonra fault-consumer’ın ElementAccepted mesajı döndüğü ve publisher’ın da bu mesajı aldığını test eden metot.

Should_publish_the_element_accepted_event_after_fault

Should_publish_the_element_rejected_event_after_fault; ElementId:Mm olarak gönderdiğimizde, bir fault oluştuktan sonra fault-consumer’ın ElementRejected mesajı döndüğü ve publisher’ın da bu mesajı aldığını test eden metot.

Should_publish_the_element_rejected_event_after_fault

Should_Element_Not_Found_return_when_send_130; ElementId:130 olarak gönderdiğimizde, herhangi bir fault oluşmadan consumer’ın ElementRejected mesajı döndüğü ve publisher’ın da bu mesajı aldığını, alınan mesaj içerisinde “130-Element Not Found!” olduğunu test eden metot.

Should_Element_Not_Found_return_when_send_130

Should_Na_return_when_send_11; ElementId:11 olarak gönderdiğimizde, herhangi bir fault oluşmadan consumer’ın ElementAccepted mesajı döndüğü ve publisher’ın da bu mesajı aldığını, alınan mesaj içerisinde “11-Na-Sodium” olduğunu test eden metot.

Should_Na_return_when_send_11

Should_Sodium_return_when_send_Na; ElementId:Na olarak gönderdiğimizde, bir fault oluştuktan sonra fault-consumer’ın ElementAccepted mesajı döndüğü ve publisher’ın da bu mesajı aldığını, alınan mesaj içerisinde “11-Na-Sodium” olduğunu test eden metot.

Should_Sodium_return_when_send_Na

Should_Element_Not_Found_return_when_send_Mm; ElementId:Mm olarak gönderdiğimizde, bir fault oluştuktan sonra fault-consumer’ın ElementRejected mesajı döndüğü ve publisher’ın da bu mesajı aldığını, alınan mesaj içerisinde “Mm-Element Not Found!” olduğunu test eden metot.

Should_Element_Not_Found_return_when_send_Mm

Sonuç

MassTransit bizlere, queue yapıları ile çalışırken büyük kolaylıklar sağlamakta, exception handling, in-memory transfer, mediator pattern ve test harness gibi bir çok yardımcı tool sayesinde kapsamlı bir framework oluşturmaktadır.

Sonraki yazımda Messages, Consumers, Producers, Requests, Containers konularının ayrıntılarından ve geniş kapsamla Saga yapısından bahsedeceğim.

Referanslar

  1. MassTransit
  2. Asynchronous programming with async and await
  3. RabbitMQ
  4. ActiveMQ
  5. ORM
  6. EntityFramework
  7. PostgreSql
  8. MSSQL
  9. Saga
  10. Activity
  11. MassTransit InMemory Tansfer
  12. Message Broker
  13. MassTransit RabbitMQ
  14. Periodic Table
  15. Mediator Pattern
  16. MassTransit Mediator
  17. MassTransit Exceptions
  18. MassTransit Testing
  19. NUnit
  20. Fine Code Coverage

--

--