The Story
Imagine we're building a simple e-commerce application. When a customer places an order, it's not instantly whisked away by elves. Instead, the order details – a message filled with product information, shipping address, and payment details – gets sent to a queue managed by a message broker.
Meanwhile, our order processing system sits like a hungry rabbit, constantly checking the queue for new messages. Once it grabs an order message, it springs into action: verifying payment, notifying the warehouse, and sending updates to the customer. All without the two systems ever needing to directly talk to each other!
This decoupling is the superpower of message brokers. Applications don't need to know the specifics of each other's internal workings. They simply send and receive messages, leaving the orchestration to the broker. This makes systems more flexible, scalable, and resilient.
Let's delve deeper into this rabbit hole, using RabbitMQ as our trusty guide.
The Technology
RabbitMQ is a popular open-source message broker, and it's a great starting point to understand the magic behind these event-driven systems.
- It is used worldwide at small startups and large enterprises.
- It is lightweight and easy to deploy on premises and in the cloud.
- It can be deployed in distributed and federated configurations to meet high-scale, high-availability requirements.
- It runs on many operating systems and cloud environments, and provides a wide range of developer tools for most popular languages.
The Concepts
- Message: It is the fundamental unit of communication in RabbitMQ. It contains the data being sent from the producer to the consumer. It is like a post carrying a message.
- Producer: A producer is an application or component that sends messages to RabbitMQ. It is like a person sending the post.
- Consumer: A consumer is an application or component that receives and processes messages from RabbitMQ. It is a person receiving the post.
- Queue: A queue is a buffer that stores messages until they are consumed. Messages are placed in queues by producers and retrieved by consumers. It is a postbox that stores messages of a person.
- Exchange: An exchange is a routing mechanism that receives messages from producers and routes them to queues. It is like a post office.
- Routing Key: A routing key is a property of a message that is used by exchanges to determine which queues should receive the message. This is like a mailing address for a post.
The Tutorial
Generate prototype from WeDAA
Use below Architecture as reference and generate a project from WeDAA
All the code mentioned in the blog will be generated by WeDAA. It can be further extended as necessary.
RabbitMQ Configuration
RabbitMQConfigOrdersToInventory class in orders service registers Queue, Exchange, Binding and Message Converters are as beans for auto-configuration in Spring AMPQ.
@Configuration
public class RabbitMQConfigOrdersToInventory {
public static final String QUEUE = "OrdersToInventory_message_queue";
public static final String EXCHANGE = "OrdersToInventory_message_exchange";
public static final String ROUTING_KEY = "OrdersToInventory_message_routingKey";
@Bean
public Queue queueOrdersToInventory() {
return new Queue(QUEUE);
}
@Bean
public TopicExchange exchangeOrdersToInventory() {
return new TopicExchange(EXCHANGE);
}
@Bean
public Binding bindingOrdersToInventory() {
return BindingBuilder.bind(this.queueOrdersToInventory()).to(this.exchangeOrdersToInventory()).with(ROUTING_KEY);
}
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
@Bean
public AmqpTemplate template(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(messageConverter());
return template;
}
}
Message Producer
RabbitMQProducerOrdersToInventory class in orders service sends a message to the exchange every 15 seconds.
@Scheduled(cron = "0/15 * * * * *")
public void publishMessage() {
RabbitMessageModel message = new RabbitMessageModel();
message.setMessage("Publishing this message from orders with key: " + RabbitMQConfigOrdersToInventory.QUEUE);
message.setDateTime(new Date());
template.convertAndSend(RabbitMQConfigOrdersToInventory.EXCHANGE, RabbitMQConfigOrdersToInventory.ROUTING_KEY, message);
logger.info("Message Published Successfully");
}
Message Consumer
RabbitMQConsumerOrdersToInventory in the inventory service starts receiving the messages as the messages are sent by the Producer.
msgs, err := channel.Consume(
queueName,
"",
true,
false,
false,
false,
nil,
)
forever := make(chan bool)
go func() {
for d := range msgs {
logger.Infof("Received Message: %s\n", d.Body)
}
}()
<-forever
The execution
-
Bootup the RabbitMQ server
WeDAA provides dockerfile for starting RabbitMQ server quickly. It can be found in both inventory and orders service.
RabbitMQ server can be started using below command from orders service.
docker compose -f src/main/docker/rabbitmq.yml up --wait
RabbitMQ's management console can be accessed on http://localhost:15672/
Default username: guest, password: guest
-
Start the orders service
In the sample architecture, orders service acts as producer. Start the service using the following command
./mvnw
Once the service is started, it can be seen from the logs that messages are sent periodically.
2024-01-15T20:35:00.015+05:30 INFO 55955 --- [rs-scheduling-1] .o.c.r.RabbitMQProducerOrdersToInventory : Message Published Successfully
2024-01-15T20:35:15.008+05:30 INFO 55955 --- [rs-scheduling-1] .o.c.r.RabbitMQProducerOrdersToInventory : Message Published Successfully
2024-01-15T20:35:30.003+05:30 INFO 55955 --- [rs-scheduling-1] .o.c.r.RabbitMQProducerOrdersToInventory : Message Published Successfully
2024-01-15T20:35:45.002+05:30 INFO 55955 --- [rs-scheduling-1] .o.c.r.RabbitMQProducerOrdersToInventory : Message Published Successfully -
Start the inventory service
In the sample architecture, inventory service acts as consumer.
Build and start the service using the following commands
go mod tidy
go run .Once started, inventory service starts consuming the messages sent by orders service.
2024-01-15 20:41:33 file=rabbitmq/RabbitMQConsumerOrdersToInventory.go:51 level=info Received Message: {"id":1,"message":"Publishing this message from orders with key: OrdersToInventory_message_queue","dateTime":1705331085013}
2024-01-15 20:41:33 file=rabbitmq/RabbitMQConsumerOrdersToInventory.go:51 level=info Received Message: {"id":2,"message":"Publishing this message from orders with key: OrdersToInventory_message_queue","dateTime":1705331100012}
2024-01-15 20:41:33 file=rabbitmq/RabbitMQConsumerOrdersToInventory.go:51 level=info Received Message: {"id":3,"message":"Publishing this message from orders with key: OrdersToInventory_message_queue","dateTime":1705331115005}
2024-01-15 20:41:33 file=rabbitmq/RabbitMQConsumerOrdersToInventory.go:51 level=info Received Message: {"id":4,"message":"Publishing this message from orders with key: OrdersToInventory_message_queue","dateTime":1705331130001} -
Track activity on RabbitMQ management console
The Conclusion
This blog gives a head start on making use of RabbitMQ to orchestrate your event-driven microservice application architectures.
Learn more from: https://www.rabbitmq.com/getstarted.html