Merhabalar, yeni bir içerikle bu yazıda sizlerle buluşmaya geldim, en azından benim için yeni olduğunu söyleyebilirim 🙂 RabbitMQ’yu 1 ay önce ilk defa sohbet arasında bir arkadaşımdan duymuştum ilgimi çekti araştırayım nedir ne değildir diye bir anda kendimi bir senaryo üzerinde, rabbitmq ile çalışırken buldum daha sonra dedim ki : ” benim gibi bu alanda yeni olup, hem kendini geliştirmek isteyen hemde sırt çantasına yeni birşey eklemek isteyenlere belki faydam olur öğrendiklerimi paylaşayım . ” bu amaç doğrultusunda rabbitmq kullanarak bir spring boot uygulaması yazacağız birlikte.
Senaryomuz şu şekilde bir kuyruğumuz var ve bu kuyruğa her 5 sn’de bir çağrı mesajı göndereceğiz ve kuyruğu dinleyip kuyruğa gelen çağrıları konsolda görüntüleyeceğiz .
Bu yazı sonucunda kazancımız, rabbitmq hakkında basic level ‘da bilgi ve bir projeyle tecrübe sahibi olmak ; kuyruğa nasıl message göndereceğimizi producer(üretici) ve bu kuyruğu dinleyerek gelen messageları nasıl consumer(tüketmek) edip process edeceğimizi, öğreneceğiz ve rabbitmq lifecycle’nı anlamış olacağız.
Projeye başlamadan önce bazı kavramları bilmek ve anlamak gerekiyor. Biraz kavramlardan ve çalıştığımız teknoloji nedir ne değildir bahsedelim.
RabbitMQ Nedir ?
RabbitMQ , Message Broker veya Kuyruk Yöneticisi (queue manager) olarak da bilinen bir message kuyruğu(message-queueing ) yazılımıdır . Kuyruk nedir : bir veri yapısı türüdür. örneğin bir mesaj bir yerlere yazılıyor ve o mesajı yazdığımız sırayla tüketebileceğimiz veya bu mesajların sıralamasını konfigüre edebileceğimiz bir mekanizmadır. Basitçe ifade etmek gerekirse bir yerden aldığı veriyi (Producer) kendine subscribe olan başka bir yere (Consumer) sırayla teslim etmektedir. Platform bağımsızdır.
Protokol Desteği
Advanced Message Queuing Protocol (AMQP) ‘den kısaca bahsedecek olursak aslında bir protokoldür. Kuyruk sistemlerinin bir birleriye konuşabilmeleri için gereken protokolun adıdır. Nasıl ki iki veya daha fazla bilgisayarın birbiriyle haberleşmesi içinTCP/IP protokolu var buda onun gibi. Bu protokolle kuyruk tipi farketmeksizin tüm kuyruklarla konuşabiliriz.
Çalışma Mantığı
Sunucu RabbitMQ sunucusuna bir message gönderir ve sunucu bu mesajı ilgili queue’ya yönlendirir.Burada message direkt olarak queue’ye değil exchange üzerinden iletilir. Yani aslında producer queue’yi bilmemektedir. Message, exchange üzerinden routing key’e göre “queue” ya iletilmektedir. Sonrasında başka bir uygulama bu queue’yi dinler ve FIFO mantığıyla çalışan kuyruktaki bu messageları consume ederek message’ı process eder ve süreci sonlandırır. Message denilince aklınıza sadece Text gelmesin. Her türlü object’i gönderebiliriz. Örneğin bu uygulamada POJO gönderip alacağız.
PEKİ Neden Kullanılır : Bankaların kullandığı EFT vb. işlemlerde, yoğun istek (request) alan e-commerce sistemlerinin olmazsa olmazı haline geldi. Sıraya alınan işlemlerin asenkron bir şekilde yapılması, hem çalışan uygulamanın boş yere bekletilmemesinden hem de sunucu üzerindeki işlem maliyetinin minimuma indirilmesinden dolayı RabbitMQ tercih sebebi olabilir. Ayrıca scalable olmasından dolayı da değişen trafikli yapılarda da tercih edilebilir.
Somut olarak; Postane veya Kargo firması gibi düşünebiliriz.
“Process olarak mektup(nesne) postaneye verilir, postane (kendi iş akışları doğrultusunda) işleme alır ve göndericiye ulaştırır, posta ulaşana kadar siz kendi işlerinizi yapmaya devam edersiniz mektubun gönderilmesini beklemeyiz.”
Bazı kavramlardan bahsedelim:
Producer: Kuyruğa mesaj gönderen veya oluşturan taraftır.
Consumer: Subscribe(abone) olduğu kuyruğa mesaj geldi mi diye dinleyen ve gelen mesajı alıp process edip tüketen taraftır.
Queue: Mesajların RabbitMQ tarafından eklendiği kuyruktur.
Routing Key : Buraya girdiğimiz key’e göre mesajı ilgili Queue’ye yönlendirir.
Exchange: Producer’dan gelen mesajları kuyruğa iletmek üzere kabul eder. Exchange, aldığı mesajla ne yapacağını bilmelidir. Routing key yardımı ile mesajı ilgili kuyruğa iletmektedir.
Exchance Type: Mesajın hangi “queue” ye ne şekilde iletileceğini belirtir. 4 tane exchange tipi bulunmaktadır Direct, Topic, Fanout,Header.
Binding: Exchance ve queue arasındaki bağlantıdır.
Mesajlaşma Mimarileri
point-to-point (Direct Exchange)
Bir mesaj üreticisi tarafından (producer) üretilen mesajın sadece bir tüketiciye (consumer) iletildiği yapıdır. Burada birden fazla tüketici olabilir. Fakat tek bir mesaj sadece tek bir tüketici tarafından işlenir. Direct exchange mesajı gönderdiğiniz key’e göre dağıtıyor bu yüzden mesaj sadece bir kuyruğa gidiyor.
publish-subscribe (Fanout Exchange)
Bir mesaj üreticisi tarafından üretilen mesajın tüm tüketicilere iletildiği yapıdır. Bir event oluştuğunda tüm dinleyenler tetikleniyor. Fanout exchange, gelen mesajı bağlı olan tüm kuyruklara dağıtıyor. Fanout exchange mesajı gönderdiğiniz key’i göz ardı edip, kendisine bağlı olan tüm kuyruklara mesajı iletiyor.
Topic Exchange
Bu exchange tipinde sıradan bir key yapısı kullanamazsınız. Key’inizin belirli özelliklerde olması gerekiyor. orange.mause, orange.cat ve white.mause şeklinde 3 farklı key’de mesaj üretildiğini düşünün. Bu mesajların nasıl dağıtılacağına ayarlarınıza göre karar veriliyor.
Header Exchange
Kontrol etmek istediğiniz bilgileri mesajınızın header’ında tanımlayabiliyorsunuz. Topic’te olduğu gibi hem point-to-point hem de publish-subscribe şeklinde davranabiliyorsunuz.
Temel bilgilerden bahsettiğimize göre projeye start verebiliriz.
Ben geliştirme ortamı olarak Intellij kullanıyorum. Temel sebebi spring-boot projelerini çok rahatlıkla oluşturabilmenizdir. İsterseniz https://start.spring.io adresinden de aynı şekilde bir proje oluşturabilir ve bunu favori geliştirme ortamınıza ekleyebilirsiniz. Build aracı olarak maven kullanacağım.
Öncelikle yeni bir proje create edelim. Proje tipi olarak maven seçiyorum.
Bir sonraki adıma geçtiğimde proje adını belirliyoruz ve finish diyerek bir proje create etmiş oluyoruz.
RabbitMQ kurulum aşamasına girmeyeceğim. Uygulamada hızlıca kullanmak adına RabbitMQ docker image alarak devam edeceğim. Bilgisayarınızda docker kurulu olduğunu varsayarak devam ediyorum. Projemizde resource dizininin içine docker-compose.yml projesi oluşturalım.
1 2 3 4 5 6 7 |
version: '3.1' services: spboot-rabbitmq: image: rabbitmq:3-management ports: - '5672:5672' - '15672:15672' |
Bu compose dosyası, rabbitmq’yu management arayüzü ile beraber bilgisayarımızda ayağa kaldırmamızı sağlıyor. Burada dikkat etmemiz gereken nokta portları docker image’ından dış dünyaya açmalıyız. Bu .yml dosyamızı oluşturduktan sonra terminale geliyoruz. Aşağıdaki komutu girerek bilgisayarımızda rabbitmq’yu 5672 portunda ayağa kaldırmış oluyoruz. Burada rabbitmq arayüzünde username ve password ile giriş yapmamız gerekiyor default da username: guest password: guest olarak giriş yapabilirsiniz.
1 |
D:\Aysenur\Spring-examples\spring-boot-rabbitmq\src\main\resources>docker-compose -f docker-compose.yml up -d |
Komutu çalıştırdıktan sonra konsolda docker ps komutunu girelim ve rabbitmq docker image ayağa kalkmış mı görelim.
Görüldüğü üzere 5671 portunda rabbitmq docker image ayağa kalkmış .
Browser’a gidelim ve localhost:5671 adresinde rabbitmq management arayüzü geliyor mu bakalım. Benim bilgisayarımda docker, ip adresi olarak 192.168.99.100 ip adresini kullandığı için 192.168.99.100:5671 adresinde rabbitmq management arayüzünü kullanabiliyorum. Aşağıdaki gibi bir arayüz karşılamalı.
Şimdiye kadar herşey yolunda . pom.xml dosyasını aşağıdaki gibi düzenleyelim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-boot-rabbitmq</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> </plugin> </plugins> </build> </project> |
Şimdi projemize dönelim ve resource dosyasının içine application.properties dosyasını oluşturalım ve aşağıdaki gibi düzenleyelim.
1 2 3 4 5 6 7 |
spring.rabbitmq.host=192.168.99.100 spring.rabbitmq.port=5672 sr.rabbit.queue.name= aysenur-queue sr.rabbit.routing.name= aysenur-routing sr.rabbit.exchange.name= aysenur-exchange |
src/main/java dizininin altında bir package olusturalım ben com.aysenur.sr şeklinde oluşturdum. Bu packege’in içine RabbitApplication.java classı’nı olusturalım.
RabbitApplication.java
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.aysenur.sr; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class RabbitApplication{ public static void main(String[] args) { SpringApplication.run(RabbitApplication.class, args); } } |
com.aysenur.sr package’nin içine model package olusturalım ve CalledPerson POJO class’ı oluşturalım.
Bu class’da nesneyi serialize edip kuyruğa gönderiyoruz almak istediğimiz zamanda java tarafında anlamlı bir sekilde prosess edebilmek için implemente etmeliyiz. Bunlarla çağrıyı kuyruğa atıcaz kuyruktanda bir baskası alıp process edecek.
CallerPerson.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
package com.aysenur.sr.model; import java.io.Serializable; import java.util.Date; public class CallerPerson implements Serializable { //nesneyi serialize edip kuyruğa gnderiyoruz almak istegigimiz zamanda java tarafında anlamlı bir sekilde prosesses edebilmek için implemente etmeliyiz //bunlarla çağrıyı kuyruga atıcaz kuyruktanda bir baskası alıp process edecek private String callerPersonId; private String callerPerson; private Date createdAt; private Boolean seen; private String message; public String getCallerPerson() { return callerPerson; } public void setCallerPerson(String callerPerson) { this.callerPerson = callerPerson; } public String getcallerPersonId() { return callerPersonId; } public void setcallerPersonId(String callerPersonId) { this.callerPersonId =callerPersonId; } public Date getCreatedAt() { return createdAt; } public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; } public Boolean getSeen() { return seen; } public void setSeen(Boolean seen) { this.seen = seen; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } @Override public String toString() { return "CallerPerson{" + "callerPersonId='" + callerPersonId + '\'' + ", callerPerson='" + callerPerson + '\'' + ", createdAt=" + createdAt + ", seen=" + seen + ", message='" + message + '\'' + '}'; } } |
Kuyrukta message oluşturabilmek için daha öncede söylediğimize göre Producera ihtiyacımız var. Bunun için bir com.aysenur.sr package’nin içine producer package oluşturup içine CalledPersonProducer.java class oluşturalım. içine kuyruğa message gönderebileceğimiz bir metod yazalım. İlerleyen aşamalarda tekrar buraya döneceğiz.
1 2 3 4 5 6 7 |
package com.aysenur.sr.producer; public class CallerPersonProducer { public void sendToQueue(CallerPerson callerPerson) { } } |
kuyruktan gelen mesağları dinleyebileceğimiz bir Listener ‘a ihtiyacımız var. com.aysenur.sr içinde yeni bir package oluşturalım listener adında bunun içine de CallerPersonListener.java sınıfı oluşturalım.
1 2 3 4 5 |
public void handleMessage(CallerPerson callerPerson){ //çağrıyı alıp process eder System.out.println("call received.."); System.out.println(callerPerson.toString()); //aldıgı her msjı ekrana yazack } |
Bir configurasyon sınıfı oluşturalım . Bunu oluşturmamızın amacı bir kuyruk oluştururken kuyruğun adını söylemeliyiz , exchange Type’ını belirtmeliyiz vs. bunun gibi configurasyon bilgilerini burada belirleyeceğiz. En başta da söylemiştik örneğimizde DirectExchange type kullanacağınız.
Burada ilk oluşturmamız gereken bir kuyruk ismi bunu birçok yerde kullanacağımız için bir değişken haline getiriyoruz.Bunları ayarlanabilir,değişebilir yapmak içinde application.properties dosyamızda configurasyonları belirlemeliyiz. Kuyruk ve directExchange’i bir birlerini ilişkilendirmek için de bir binding oluşturmalıyız.
RabbitMqConfiguration.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
package com.aysenur.sr.config; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; @Configuration @EnableScheduling public class RabbitMqConfiguration { @Value("${sr.rabbit.queue.name}")//configuration dosyamızdaki configurationı otamatik bizim bu degiskenimize enject ediyor private String queueName; @Value("${sr.rabbit.routing.name}") private String routingName; @Value("${sr.rabbit.exchange.name}") private String exchangeName; @Autowired private RabbitTemplate rabbitTemplate;//rabbit kuyruguna gidip gelmek icin kullancagız @Bean public Queue queue(){ return new Queue(queueName,true); } @Bean public DirectExchange directExchange(){ return new DirectExchange(exchangeName); } @Bean public Binding binding(final Queue queue, final DirectExchange directExchange){ //constructor Injection return BindingBuilder.bind(queue).to(directExchange).with(routingName); } } |
CallerPersonListener.java sınıfına ve CallerPersonProducer.java geri dönelim ve bu classın başına @Service annotation’ınını ekleyelim ki kendi otomatik ayağa kalksın,bir instance oluştursun ben gerektiğinde @Autowired diyip başka bir yerde kullanabileyim. yada producer sınıfında @Bean annotationı ile de initialize edebilirdik.
CallerPersonListener.java sınıfına geri dönelim ve oluşturduğumuz metodun üstüne @RabbitListener annotation’ınını yazalım . Bu annotation kuyruğa message geldiğinde tetiklenecek. Parametre olarak da hangi kuyruğu dinleyeceğini söyleyeceğiz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package com.aysenur.sr.listener; import com.aysenur.sr.model.CallerPerson; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Service; @Service public class CallerPersonListener { //kuyruktan surekli çağrıları dinliyecek alacak işleyecek @RabbitListener(queues = "aysenur-queue") //message'ı bu kuyruktan dinliyecek public void handleMessage(CallerPerson callerPerson){ //çağrıyı alıp process eder System.out.println("call received.."); System.out.println(callerPerson.toString()); //aldıgı her msjı ekrana yazack } } |
CallerPersonProdecer.java sınıfına geri dönelim ve artık message’ı queue’ye gönderelim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
package com.aysenur.sr.producer; import java.util.Date; import java.util.UUID; import javax.annotation.PostConstruct; import com.aysenur.sr.model.CallerPerson; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @Service public class CallerPersonProducer { //kuyruga message gonderen kisi @Value("${sr.rabbit.routing.name}") private String routingName; @Value("${sr.rabbit.exchange.name}") private String exchangeName; @PostConstruct public void init(){ //prodece edilecek nesne dogru bir sekilde initialize olduktan sonra message'ı gondersin runSomething(); } @Scheduled(fixedDelay = 5000, initialDelay = 5000) public void runSomething() { CallerPerson callerPerson = new CallerPerson(); callerPerson.setCallerPersonId(UUID.randomUUID().toString()); callerPerson.setCreatedAt(new Date()); callerPerson.setMessage("WELCOME TO RABBITMQ"); callerPerson.setSeen(Boolean.FALSE); sendToQueue(callerPerson);//producer, şu message'ımı queue'ye yolla } @Autowired private RabbitTemplate rabbitTemplate; //rabbitmq'nun connection nesnesi gibi bunun üzerinden gidiyoruz sunucuya public void sendToQueue(CallerPerson callerPerson) { System.out.println("CallerPerson Sent ID : " + callerPersonId.getCallerPersonId());// her mssage gonderdiginde gonderdigi messageın id'sini ekrana yazsın rabbitTemplate.convertAndSend(exchangeName, routingName, callerPerson);//istedigimiz routinge message'ımızı atıcak } } |
Burada önemli bir nokta @Scheduled annotation’ını kullanarak kuyruğa bir nevi thread mantığı ile message’ı 5 saniye bekletip tekrar gondermesini sağlayarak en başta senaryomuzda da belirttiğimiz gibi her 5 sn’de bir kuyruğa message gondermeyi @Scheduled(fixedDelay = 5000, initialDelay = 5000) ile sağlamış oluyoruz.
Uygulamayı run ettiğimizde konsolda aşağıdaki gibi queuetan message alındı bilgisi geliyor ve kuyruğa gönderdiğimiz(producer) bilgileri kuyruğu dinleyerek ekrana yazdırıyoruz(consumer).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
2020-05-19 02:45:14.727 INFO 7172 --- [ main] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [192.168.99.100:5672] 2020-05-19 02:45:14.886 INFO 7172 --- [ main] o.s.a.r.c.CachingConnectionFactory : Created new connection: rabbitConnectionFactory#47b2e9e1:0/SimpleConnection@78c1a023 [delegate=amqp://guest@192.168.99.100:5672/, localPort= 64530] 2020-05-19 02:45:15.704 INFO 7172 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler' Message received.. CallerPerson{callerPersonId='13b88124-521f-4cbd-a455-55065d788d5f', callerPerson='AyşenurGökdemir', createdAt=Tue May 19 02:45:14 EET 2020, seen=false, message='WELCOME TO RABBITMQ'} 2020-05-19 02:45:16.533 INFO 7172 --- [ main] com.aysenur.sr.RabbitApplication : Started RabbitApplication in 10.417 seconds (JVM running for 16.418) Caller Person Sent ID : 2c735cb4-04fa-4398-be43-d8cadc1bd559 Message received.. CallerPerson{callerPersonId='2c735cb4-04fa-4398-be43-d8cadc1bd559', callerPerson='AyşenurGökdemir', createdAt=Tue May 19 02:45:21 EET 2020, seen=false, message='WELCOME TO RABBITMQ'} Caller Person Sent ID : 399ee72a-e41b-4d9c-bc0b-72b9a63336a4 Message received.. CallerPerson{callerPersonId='399ee72a-e41b-4d9c-bc0b-72b9a63336a4', callerPerson='AyşenurGökdemir', createdAt=Tue May 19 02:45:26 EET 2020, seen=false, message='WELCOME TO RABBITMQ'} Caller Person Sent ID : 1a7bf1c7-76f4-456a-8ec8-91042ad15096 Message received.. CallerPerson{callerPersonId='1a7bf1c7-76f4-456a-8ec8-91042ad15096', callerPerson='AyşenurGökdemir', createdAt=Tue May 19 02:45:31 EET 2020, seen=false, message='WELCOME TO RABBITMQ'} Caller Person Sent ID : 2db0b798-3d21-4d3b-ab9e-e17a02a86f79 Message received.. CallerPerson{callerPersonId='2db0b798-3d21-4d3b-ab9e-e17a02a86f79', callerPerson='AyşenurGökdemir', createdAt=Tue May 19 02:45:36 EET 2020, seen=false, message='WELCOME TO RABBITMQ'} |
RabbitMQ management arayüzüne baktığımızda aşağıdaki gibi görünmektedir.
QUEUES tabına bakarak belirttiğimiz isimde kuyruğumuzun oluşturulduğunu görmemiz mümkün.
Umarım faydalı bir yazı olmuştur. Herhangi bir aklınıza takılan veya yalnış ifade ettiğimi düşündüğünüz bir yer varsa bildirirseniz yorum kısmına sevinirim.. Olumlu veya olumsuz feedbackleri bekliyorum …
Sevgiyle kalın 🙂
Viel Glück in Ihrem Blog, wie ich weiterhin regelmäßig zu folgen. Hortensia Knox Rossen
Ich habe Ihren Artikel mit Interesse gelesen. Laurice Shurlock Iey
wonderful publish, very informative. I ponder why the other specialists of this sector
don’t realize this. You should proceed your writing. I am sure, you’ve a
great readers’ base already!
I’m glad you like it. thank u 🙂
I’m extremely pleased to discover this website. I wanted to thank you for ones time just for this fantastic read!! I absolutely enjoyed every part of it and i also have you bookmarked to see new stuff in your site.
thank u very much 🙂
Definitely, what a great blog and revealing posts, I definitely will bookmark your site. Best Regards!
thank u 🙂
I got what you intend,bookmarked, very decent website.
thank u 🙂
I reckon something truly special in this website.
thank u 🙂
Eline emeğine sağlık
teşekkürler 🙂
teşşekür ederiz 15 yıllık devloperım ben bile okudum:)
Elinize sağlık çok güzel yazı olmuş. Spring boot kafka entegrasyonu ile yazı paylaşabilir misiniz.
Merhaba yazıyı beğenmenize sevindim. talebinizi todo list’ime aldım en kısa zamanda paylaşım gerçekleştireceğim.
Çok güzel olmuş eline sağlık
Hi there, You’ve done a fantastic job. I will definitely digg
it and personally recommend to my friends. I’m sure they will be benefited from this website.
Hi, how we can override queue name, exchange and binding properties if we want to use docker-compose file.
hi, you can edit queue name vs. from application.properties file.
It’s going to be end of mine day, but before end I am reading this
impressive piece of writing to improve my experience.
My website 구글상위노출
It is the best time to make some plans for the future and it is time to be happy.
I have read this post and if I could I wish to suggest you some interesting things or
advice. Maybe you can write next articles referring to this
article. I desire to read even more things about it!
Also visit my web page :: 바카라검증사이트