RabbitMQ message broker for messenger development. Tips for developers
Recently mobile messenger development technologies have been increasingly gaining popularity. At the moment, there are plenty of mobile messengers, such as Viber, WhatsApp, or Telegram, each having its pros and cons.
Such applications have to be able to support heavy loads and be easily scalable which creates certain challenges for developers. That’s why we recommend integrating ready and reliable messaging platforms in such solutions. Such integration will help to transfer the solution to the scalability problem to the messaging platform side and allow to pay more attention to new functionality development.
One of the platforms that is a good fit for messenger app architecture is message broker RabbitMQ that supports different messaging protocols, such as AMQP, MQTT, STOMP, etc.
RabbitMQ has a large number of client libraries that allow integration with almost any client application platform.
In this article, we’ll write about the implementation of message transfer and processing using RabbitMQ.
Firstly, let’s get acquainted with this message broker’s specific working principle and terms.
RabbitMQ has the following components:
- producer – a client that creates message;
- consumer – a client that receives message;
- queue – an unlimited queue that stores messages;
- exchange – a component that allows to route messages sent to it to different queues.
Generally, the interaction of the components within RabbitMQ is the following: the producer sends a message to the exchange, then the exchange receives the message and routes it to the queues subscribed to it. Depending on the type of exchange messages can be filtered based on a correspondence of keys with which the queue is connected to the exchange and the keys in the message or routed to all interested queues, after which the consumer receives messages from the queue he/she is subscribed to.
The exchange can have one of a few possible types.
Types of exchange:
- fanout – routes messages sent to it to all the connected queues;
- direct – routes messages sent to it to the connected queues according to filtration settings using routeld (can be one word);
- topic – routes messages sent to it to the connected queues according to filtration settings using routeld consisting of a few words that allow achieving more flexible filtering.
This knowledge will be enough for further understanding of the article. Let’s move on to the architecture of the implemented system and thus learn a bit on how to make a messenger.
In general, a messenger developed via RabbitMQ will have the following scheme.
As it’s shown on the scheme, the system consists of the following parts:
- RabbitMQ;
- Backend application;
- Client applications: Android, iOS.
For the architecture to function one needs already set up exchanges and queues created at the first start of the backend application.
Here is the list of principal exchanges and queues:
- “conversation.outgoing” is an exchange, type fanout, which in our case is required for receiving incoming messages from clients and routing them to the queues;
- “conversation.incoming” is an exchange, type topic, for sending already processed messages to exchange of certain users. In our case topic type enables sending only those messages to users that relate to dialogues they participate in;
- “chat-application-messages” is a queue for processing incoming messages by backend application.
After initialization all the used exchanges and queues we connect exchange “conversation.outgoing” with queue “chat-application-messages” and create a backend message processor from queue “chat-application-messages”.
Integration of all parts of the system starts with user registration functionality.
When a user registers on the server using one of the clients (iOS, Android), a backend application creates an exchange of a fanout type in RabbitMQ with a unique generated name and returns this exchange name to the client application in which the user has registered.
From then all the authorized clients will also receive the name of this already existing exchange.
After receiving the exchange name of this user, the clients create a temporary queue in RabbitMQ, which exists only during the connection of the client with the RabbitMQ message broker, and connect it with this exchange.
Creating one unique exchange for each user allows receiving messages from all the clients (iOS, Android) at the same time.
After turning off the Internet on the client or in any other case when the connection with RabbitMQ message broker is lost, a temporary queue will be automatically deleted on the server by RabbitMQ, thus preventing redundant messaging and optimizing our delivery. When a connection is restored, a new queue will be created.
After registration, the user is ready for sending and receiving messages.
Sending messages takes place within dialogues. The system must enable the creation of conversations with both one and several contacts.
When creating a conversation, an exchange of each user connects with the exchange of the backend application “conversation.incoming”, using the conversation’s id as routeId.
A general sequence of message delivery is shown in the picture.
Now let’s clarify the sequence of message delivery a bit. Incoming messages go to “conversation.outgoing” exchange which sends them to “chat-application-messages” queue. The backend application processes all messages sent to “chat-application-messages” queue.
When checking the message, the validity of user token, conversation id and the possibility of this user to send messages to this conversation are checked, the attachment which has to be sent with the message is processed, and the message is stored in the backend application database and then sent to “conversation.incoming” exchange with conversation id as routeId.
“conversation.incoming” exchange will send the message to all exchanges that are signed with the same routeld as the sent message.
Filtration of the sent messages in RabbitMQ allows to conveniently scale the number of contacts receiving messages without increasing the time of messages delivery.
The exchange of a certain user will then transmit the message to all the queues signed to this exchange, that is for all the client applications.
Thus, if you need simple advice on how to develop a messenger, the objective of message delivery with the support of multi-user conversations and lots of clients used at the same time can be solved using RabbitMQ only and writing minimum code for the backend application.
The backend application (Android)
Considering that messengers imply quick communication, the possibility of receiving new messages at any time and in any condition (online/offline) is necessary for the client application.
For ceaseless messages receiving, in our Android app we implemented a background system service that constantly checks the queue we created for new messages.
To implement this functionality on the Android platform, we use the class that implements the standard Android IntentService.
At the start of the service, it creates a connection with the RabbitMQ server and a temporary queue which it connects with the exchange received from the backend application.
connection = ChatApplication.getApp().getRabbitMQConnectionFactory().newConnection();
channel = connection.createChannel();
queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, ChatApp.getApp().getAppConfig().getExchangeName(), “#”);
From this moment the client is ready for receiving new messages. The code of receiving and processing messages is shown below.
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, true, consumer);
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
handleMessage(delivery);
}
A handlemessage method has a code for messages processing, adding them to the local client base and displaying them on the UI.
For messages delivery, Android application creates a new connection with RabbitMQ and sends the message in JSON format to “conversation.outgoing” exchange.
Connection connection = СhatApp.getApp().getRabbitMQConnectionFactory().newConnection();
Channel channel = connection.createChannel();
try {
Map<String, Object> headers = new HashMap<>();
headers.put(ACCESS_TOKEN_HEADER, getPreparedUserToken());
AMQP.BasicProperties properties = (new AMQP.BasicProperties()).builder().replyTo(MessageService.getTempQueueName()).headers(headers).build();
MessageBean message = new MessageBean();
message.setTemporaryId(mTemporaryId);
message.setAttachments(attachments);
message.setType (type.getType());
Gson gson = new Gson();
String messageJson = gson.toJson(message);
channel.basicPublish(OUTGOING_EXCHANGE, routeId, properties, messageJson.getBytes());
Thus, RabbitMQ message broker allows developing effective mobile messengers for iOS and Android alike, completely solving the issue of communication with the application, which allows developers to pay more attention and devote more time to implementation of the business logic for the solution.
Here at Smartym Pro we have many years of experience of developing messenger apps, as well as other mobile apps in general. Don’t be shy, write us a letter if you need help with RabbitMQ or want to create a top messenger or in case you just want to share some of your relevant experience!