As we are already aware, there are four types of RabbitMQ message exchanges are available. They can be listed as follows.
- Direct Exachange
- Topic Exchange
- Fanout Exchange
- Header Exchange
In this article, i am expecting to demonstrate about how to use direct exchange in RabbitMQ with Spring Boot. Before moving with the article it is required to have a up and running RabbitMQ server in your development environment.
If not please visit the https://www.rabbitmq.com/download.html and installed the latest version in your development environment.
If you do not wish to install the RabbitMQ Server, you can use a Docker image. Purpose of this article is to demonstrate how the RabbitMQ message broker is effectively used within Spring Boot. I will be composing next article on how to use RabbitMQ Docker image in your development environment. So for the time being, lets move with the installed RabbitMQ server.
Ok. Lets move forward!
What is direct exchange?
In direct exchange, the messages are directly transmitted into the queue(s) who are having the same binding key as the routing key of the message. The binding key of the queue should exactly match the routing key of the message. All the queues that are bound to the direct exchange should have static binding keys (simply this means a constant text). No wildcard binding keys are supported with direct exchange.
This is what we are going to do !
In RabbitMQ server, we need to create a direct exchange and name it as “sales_data_exchange“. After that we should create two queues and need to bind those queues with direct exchange using two binding keys ( customer.order and customer.deliveries).
|queue name||binding key|
Now our RabbitMQ architecture set up is done.
This is how it works!
The Publisher application will publish messages to the RabbitMQ exchange (In our case, direct exchange). Each message should have a routing key and based on the routing key, exchange transmits the message to the related queue(s).
for example, if the message has the routing key as “customer.order” , then it will be directed to orders_queue . This is because routing key is same as the binding key of that queue.
The consumer app, will listen to the “orders_queue” and once the orders_queue will get a message, it will be delivered to the consumer app.
In this example, none of the consumer application is listening to the deliveries_queue. It is just added to show/demonstrate that a direct exchange can have more than one bounded queue.
Lets set up the RabbitMQ Server
Lets look at how the message exchange, queues and binding keys are created in RabbitMQ server for our requirement.
First start the RabbitMQ Server and login to the server. The console can be loaded with following url.
The default login credentials are as follows.
username : guest / password : guest
1. Creating the direct exchange
Click on the exchange tab and add your message exchange. once it is added successfully, it will be listed under exchanges with given name.
You can notice that the type is direct. Therefore this is a direct exchange.
2. Creating two queues (known as orders_queue and deliveries_queue)
Click on the Queues tab and add the queue. once it is added successfully, it will be listed under All Queues.
- adding the orders_queue
- adding the deliveries_queue
3. Bind the orders_queue for the message exchange (sales_data_exchange) that we have already created.
We will be binding only the orders_queue for the sales_data_exchange. (As you are already aware, deliveries_queue was just created for demonstration purpose)
Click on the orders_queue and go to the Binding section. Then bind the queue to the message exchange with your desired binding key.
once the queue is successfully bound to the exchange, it will be shown as follows.
Now we have completed the required RabbitMQ server setup for our article. So Lets look at the project structure and source code. There are two separate spring projects available for representing Producer and Consumer applications.
The fully source code of this article can be found at GitHub
Lets look at the structure of two projects known as Producer and Consumer.
1. Producer Application
Here is the project structure of the Producer application.
1.1 Adding the dependency
The following dependency need to be added in to the pom.xml for enabling Spring AQMP (RabbitMQ support) for the application.
1.2 Developing the message Producer
Here is the most important part of this application and it the the Producer service.
RabbitTemplate is a helper class that can be used to access RabbitMQ server for send ing and receiving messages. It is an extended class of AmqpTemplate and it has the RabbitMQ specific methods for accessing/using AMQP functionalities.
As name implies produce() method produces messages and send to RabbitMQ server. the routing key will be assigned for the each message and the message will be delivered to the given exchange. produce() method is scheduled to be executed for every minute (1000 milliseconds)
convertAndSend() method of RabbitTemplate, will convert the POJO message to the given common format before sending to the RabbitMQ exchange. The message converters will be set in the RabbitTemplate when it is configured (please see the RabbitMqConfig class for further information). Anyhow as the method name stated, it will be converted to the given format and sent to the exchange.
rabbitTemplate.convertAndSend(EXCHANGE, ROUTING_KEY, order);
It is very easy to understand. this says convert the order message using the message converter defined (in the RabbitTemplate) and sent to the defined EXCHANGE with the given ROUTING_KEY.
As i have already mentioned, here we are trying to publish/send java objects (POJO) as the messages for the RabbitMQ exchange. we will be sending the instance of Order class as the RabbitMQ message. The instance of Order class (message) will be send to the “sales_data_exchange” with the binding key “customer.order“.
1.3 RabbitTemplate Configuration
Now we will look at the RabbitMQ configuration class for the application.
You can see how the RabbitTemplate is constructed and configured. RabbitTemplate will use Jackson2JsonMessageConverter for converting the POJO (java objects) into JSON objects. Whenever the RabbitTemplate needs to convert the POJO, it will use this given message converter.
ConnectionFactory will use the RabbitMQ server details declared in the application.properties for making a connection with RabbitMQ server.
1.4 RabbitMQ Server details
Now you need to make sure that you have declared the RabbitMQ server details in the application.properties file of the Producer application. These details are used to make a connection with RabbitMQ server when RabbitTemplate is configured (The ConnectionFactory will use these details for making a connection with RabbiMQ server). if these details are incorrect, the Producer application cannot make a connection with RabbitMQ server for publishing messages.
1.5 Spring Boot Configuration for Producer Application.
Here is the spring boot configuration file for the Publisher application.
You can see that the Producer application has been developed as a command line running application. In addition, we have enable the spring scheduling feature for the project. Therefore annotated @scheduled method(s) will get executed. In our case, the produce() method of the Producer class will get executed as scheduled.
1.6 Build and Run the Producer Application
Now all are set for the Producer application, lets build and run.
Now you can observe that the application is up and running and producer is producing the messages (orders) as scheduled. A series of log records are being printed on the console as follows.
Now it is time to check whether the published messages are received to the RabbitMQ server as expected. Login to the RabbitMQ console and check whether the bound queue has received the published messages. In our case, the orders_queue should have few messages.
Yes! we are done with 37 messages. Now our Publisher application is completed and RabbitMQ server is properly working.
So we will be moving forward with Consumer application.
2. Consumer Application
Here is the project structure of the Consumer application.
The project structure and most of the classes are similar to the Producer application. Therefore i am not going to repeatedly explain those similar classes here again. But i will explain the rest of the classes that are related to the Consumer application.
2.1 Listening for queue(s) with RabbitListener
As you can see that the Consumer class will be a listener class and it will be listening to the orders_queue. If the queue get any message, it will be delivered to the method annotated with @RabbitHandler. You can see that we are expecting to receive the message as a POJO (Java Object). You can see the Order POJO of the Consumer application later on.
2.2 Spring Boot RabbitMQ Configuration
RabbitTemplate can be used to send and receive messages. Therefore we are going to use the RabbitTemplate in this application to receive messages.
if you look at the Consumer class properly, you may have noticed that the messages are received as POJO entities (Java Objects). The related POJO class is the Order class. In oder to convert the message into the related POJO class, it is required to set a message converter for the RabbitTemplate. Therefore the RabbitTemplate can transform the JSON message into the POJO format with the aid of the message converter. But unfortunately, the default class mapper of the Jackson2JsonMessageConverter is not sufficient to map the JSON message into the required POJO form. So we need to override the DefaultClassMapper to provide more meaningful information.
Please look at the following message and its properties.
You can see that headers __TypeID__ contains the package declaration of the Order class of the Producer application.
headers: __TypeId__ : com.springbootdev.samples.producer.model.Order
Therefore the default implementation of the DefaultClassMapper will look at the Order class of the consumer application in the same package. If the Order class is in some other package, it will throw an exception saying that the transformation cannot be done due to class unavailability. But the Consumer application does not want to use the same package declaration as the Producer application. In that case we need to override the DefaultClassMapper to map the actual class for the __TypeId__ headers. Then the message converter can find where are the relevant classes for the __TypeId__ headers.
2.3 Build and Run Consumer application.
The consumer application is also a command line application. Therefore it can be built and run with the same command used for the Producer application.
Now you will see that console logs are being printed on your screen as it gets a new message. In RabbitMQ admin view, you can see that related queue (“orders_queue“) is running and all the message are delivered. (we had 37 messages those were waiting in the queue and now there is no message on the queue)
This is the end of this article and i hope that you get a good idea of how to send and receive messages with RabbitMq. If you need to learn more, it is highly recommended to refer the following reference guide.