Translate: progressivecoder.com/microservic…
Communication between microservices is the backbone of distributed systems. In general, developers try to avoid worrying about increased complexity entirely. However, the combination of Spring Cloud Stream and RabbitMQ makes it relatively easy to handle communication between microservices.
Spring Cloud Stream is also part of the Spring Cloud project team. It integrates easily with a variety of message brokers with minimal configuration. Here is a high-level view of the overall pattern.
Spring Cloud Stream helps exchange messages between two applications or microservices. It works seamlessly with Message brokers such as Rabbit MQ and Kafka.
In this article, we will use Spring Boot and Spring Cloud Stream to implement communication between microservices. Here is our advanced plan.
- Step 1 – Create the RabbitMQ server using the Docker
- Step 2 – Create a Spring Boot application using Spring Cloud Stream to listen for messages. We call this application a “subscriber application.”
- Step 3 – Publish messages to RabbitMQ so that our subscriber application can listen to them.
- Step 4 – Create another Spring Boot application using Spring Cloud Stream to publish messages to Rabbit MQ. We call this application publisher-Application.
- Step 5 – Publish messages using the publisher application, which will be consumed by the subscriber application.
So let’s start the process.
Step 1 – Create the RabbitMQ server using the Docker
To demonstrate Spring Cloud Stream, we will use RabbitMQ as a message broker. However, Spring Cloud Stream can easily be used with other messaging brokers as well as Kafka or Amazon Kinesis. Therefore, you can use them well. For simplicity, we will use RabbitMQ.
RabbitMQ can be easily set up on your machine. One way is to follow the official download guide and get RabbitMQ for the operating system of your choice.
However, another easy way to get RabbitMQ is through Docker. If you are not familiar with Docker, you can follow my detailed article to learn the basics of Docker. The official Docker image for RabbitMQ is available from this link.
You can easily start the RabbitMQ server using the following command.
docker run -d --hostname kubernetes.docker.internal --name backend-rabbit -p 15672:15672 -p 5672:5672 rabbitmq:3-management
Copy the code
This command extracts the RabbitMQ image from the Docker hub and starts the server on port 5672. We also exposed the RabbitMQ management console on port 15672.
RabbitMQ stores data based on node names that are host names by default. In this case, we provide the host name as backend-rabbit. We can also check the RabbitMQ server logs using the following command.
docker logs backend-rabbit
Copy the code
However, we have also exposed the administrative console for the RabbitMQ server. Basically, it provides a graphical user interface to manage RabbitMQ. You can access it at http://localhost:15672.
If you see the login page above, the login page on your machineRabbitMQThe installation is successful. Now, we can go to step 2.
Step 2 – Create the subscriber application
In this step, we can use Spring Boot and Spring Cloud Stream to create an application to listen for messages. If you are not familiar with Spring Boot, see the detailed guide to Spring Boot Microservices.
Here’s our basic plan for now.
Basically, we’ll start by building a subscriber application that connects to the RabbitMQ server. We will publish messages directly through the RabbitMQ Web console. And our Spring Boot application will have something called Sink that will help us process incoming messages. In the next section, we’ll look at what a receiver is.
Select dependencies
We can use start.spring. IO to quickly boot the application. The important things to note are the following dependencies for Spring Cloud Stream and RabbitMQ. Together, these two dependencies form the basis of our integration. At this point, you can also choose Kafka or another option (instead of RabbitMQ) of your choice.
You can generate the project and then view the POM.xml file (in your IDE of choice), which mentions these dependencies.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-test-support</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Copy the code
Add a message handler
Even at this point, our Spring Boot application can run. But it doesn’t do anything.
As per our plan, we want the application to listen for messages published on RabbitMQ. To do this, we need to add a listener that can handle incoming messages.
Here’s how we’re going to do it.
@SpringBootApplication
@EnableBinding(Sink.class)
public class SubscriberApplication {
public static void main(String[] args) {
SpringApplication.run(SubscriberApplication.class, args);
}
@StreamListener(Sink.INPUT)
public void handleMessage(Message message){
System.out.println("Received Message is: " + message);
}
public static class Message{
private String message;
public String getMessage(a) {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString(a) {
return "Message{" +
"message='" + message + '\' ' +
'} '; }}}Copy the code
Important points to note here are:
We have enabled the Sink binding by using the @enableBinding annotation. This step signals the infrastructure to create the necessary bindings to the messaging middleware. In other words, it will create target items such as queues, topics, and so on. In addition, we added a handler method. This method is used to receive incoming messages of Message type. This is one of the most powerful features of Spring Cloud Stream. The framework attempts to automatically convert incoming messages to type Message.
In this way, we have basically completed the minimum setup required for our application.
Step 3 – Publish messages through RabbitMQ
We are ready to test our subscriber application and see it in action.
You can simply start the application with the following command:
clean package spring-boot:run
Copy the code
The application will automatically attempt to connect to the RabbitMQ server at http://localhost:5672. You should see something similar in the application startup log.
The 2019-06-25 17:35:22. 8115-304 the INFO [main] O.S.A.R.C.C achingConnectionFactory: Created new connection: rabbitConnectionFactory# 34688 e58:0 / SimpleConnection @ 1 a981cf0 [delegate = it: / / [email protected]:5672 /, localPort = 52000]The 2019-06-25 17:35:22. 8115-355 the INFO [main] O.S.I.M onitor. IntegrationMBeanExporter: Registering MessageChannel input. Anonymous. KWrQWmqZSYKPjOBkx4DotA. Errors 17:35:22 2019-06-25. 8115-432 the INFO [main] o.s.c.stream.binder.BinderErrorChannel : Channel'application.input.anonymous.KWrQWmqZSYKPjOBkx4DotA.errors'From 1 the subscriber (s). The 2019-06-25 17:35:22. 8115-432 the INFO [main] O.S.C.S tream. Binder. BinderErrorChannel: Channel'application.input.anonymous.KWrQWmqZSYKPjOBkx4DotA.errors'From the subscriber (s). The 2019-06-25 17:35:22. 8115-454 the INFO [main] O.S.I.A.I.A mqpInboundChannelAdapter: Started the inbound. Input. Anonymous. KWrQWmqZSYKPjOBkx4DotA 17:35:22 2019-06-25. 8115-464 the INFO [main] c.p.d.s.SubscriberApplication : Started SubscriberApplicationin3.52 seconds (JVM is runningfor 18.305)
Copy the code
To test whether our application can listen to messages, we can publish messages through the RabbitMQ administrative console.
To do this, you can log into the console at http://localhost:15672 using the default userID /password as guest/guest.
At this point, you should see a list of exchanges as shown below. Notice the last exchange in the list, called input. This is basically created automatically when we start the application.
At this point, you should see a list of exchanges as shown below. Notice the last exchange in the list, called input. This is basically created automatically when we start the application.
Also, create a queue under the input exchange where we can publish messages.
After you click Publish, you will see the following message printed in the subscriber application log.
The 2019-06-25 17:35:22. 8115-454 the INFO [the main] O.S.I.A.I.A mqpInboundChannelAdapter: Started the inbound. Input. Anonymous. KWrQWmqZSYKPjOBkx4DotA 17:35:22 2019-06-25. 8115-464 the INFO [main] c.p.d.s.SubscriberApplication : Started SubscriberApplicationin3.52 seconds (JVM is runningfor18.305) Received Message is: Message{Message ='Hello World'}
Copy the code
This completes the subscriber portion of the application. Now, we can move on to the next step.
Step 4 – Create the publisher application
In this step we will create a publisher application whose endpoint is the endpoint that publishes messages to the RabbitMQ server.
Here is our high-level plan.
As you can see, we now have a publisher application instead of a Web console. In the publisher application, we have something called a Source. Consider it the opposite of the Sink interface we saw earlier in the subscriber application. We’ll cover the Source interface in detail in this section.
Select dependencies
To quickly boot the application, we can use Spring Initializr as we would for the subscriber application. This time, we’ll include an additional dependency called the Spring Web Starter.
You can click Build Project to get the ZIP file on your local computer.
Add message publishing logic
To publish messages using Spring Cloud Stream and RabbitMQ, we modify the main class of the publisher application as follows.
@SpringBootApplication
public class PublisherApplication {
public static void main(String[] args) { SpringApplication.run(PublisherApplication.class, args); }}@RestController
@EnableBinding(Source.class)
class MessagePublisher{
@Autowired
private Source source;
@GetMapping(value = "/api/publish")
public void sendMessage(a){
Message message = new Message("Hello World from Publisher"); source.output().send(MessageBuilder.withPayload(message).build()); }}class Message{
String message;
public Message(String message) {
this.message = message;
}
public String getMessage(a) {
return message;
}
public void setMessage(String message) {
this.message = message; }}Copy the code
The important things to note here are:
- We create a MessagePublisher class and annotate it as @RestController.
- In addition, we annotate the controller class with @enableBinding. However, instead of binding it to Sink (as we did in the subscriber), bind this class to source.class. Basically, Source and Sink are the binding interfaces provided by Spring Cloud Stream.
- We automatically associate an instance of the Source class and use it in the/API/publish call to publish the Message object to RabbitMQ.
- We also define the Message class to create new messages.
You need to perform another important setting in the application.properties file. We need to define the output binding as follows.
spring.cloud.stream.bindings.output.destination=input
spring.cloud.stream.default.contentType=application/json
Copy the code
The following figure helps you understand what this property means.
Basically, the Source interface exposes an Output channel that we bind to the input switch. Publishers and subscribers must have this relationship to exchange messages.
The 2019-06-26 17:18:50. 5138-366 the INFO [the main] O.S.I.A.I.A mqpInboundChannelAdapter: Started the inbound. Input. Anonymous. Z0h4KjScTG2guIvw1KxRxQ 17:18:50 2019-06-26. 5138-374 the INFO [main] c.p.d.s.SubscriberApplication : Started SubscriberApplicationin2.939 seconds (JVM is runningfor11.335) Received Message is: Message{Message ='Hello World from Publisher'}
Copy the code
Step 5 – Publish messages using the publisher application
Now we can start the publisher application as well. After the application is started, we need to trigger the endpoint http://localhost:8080/api/publish.
Nothing is displayed as a response in the browser or client. However, if you access the subscriber application’s logs, we will be able to see the printed messages.
In addition, you will see a green spike in the RabbitMQ console for the input queue. This shows the transfer of the message to the input queue on which the subscriber application is listening.
conclusion
Thus, we successfully developed a small application using Spring Cloud Stream and RabbitMQ to publish and subscribe messages.