1. An overview of the

In this article, we introduce you to Spring Cloud Stream, a framework for building message-driven microservice applications connected by a common messaging broker such as RabbitMQ, Apache Kafka, and so on.

Spring Cloud Stream builds on existing Spring frameworks such as Spring Messaging and Spring Integration. While these frameworks are battle-tested and work very well, the implementation is tightly coupled to the Message Broker used. In addition, it is sometimes difficult to extend certain use cases.

The idea behind Spring Cloud Stream is a very typical Spring Boot concept — in the abstract, let Spring figure out how to implement automatic injection at run time based on configuration and dependency management. This means that you can change Message Broker by changing dependencies and configuration files. You can find the various message brokers that are currently supported here.

This article will use RabbitMQ as message Broker. Before we do that, let’s look at some basic concepts of broker and why you need it in a microservices-oriented architecture.

2. Messages in microservices

In a microservice architecture, we have many small applications that communicate with each other to fulfill requests – one of their main advantages is improved scalability. It is common for a request to be passed from multiple downstream microservices to completion. For example, suppose we have A service-a that internally calls service-b and service-c to complete A request:

Yes, there will be other components, such as Spring Cloud Eureka, Spring Cloud Zuul, etc., but we will focus on the specific issues of this type of architecture.

Suppose for some reason service-B takes more time to respond. Perhaps it is performing I/O operations or long DB transactions, or making further calls to other services that make service-B slower, so that it cannot be more efficient.

Now, we can start more instances of service-b to solve this problem, which would be nice, but service-A is actually very responsive, and it needs to wait for service-B’s response for further processing. This would cause service-a to be unable to receive any more requests, which means we would also have to start multiple instances of service-A.

Another approach to similar situations is to use event-driven microservices architectures. This basically means that service-A does not call service-B or service-C directly over HTTP, but instead publishes requests or events to Message Broker. Service-b and service-c will become subscribers to this event on Message Broker.

This has many advantages over traditional microservices architectures that rely on HTTP calls:

  • Improved scalability and reliability – now we know which services are the real bottlenecks in the overall application.
  • Encourage loose couplingService-ADon’t need to knowService-BandService-C. It just needs to connect tomessage brokerAnd post events. How events are further orchestrated depends on the agent Settings. In this way,Service-AIt can run independently, which is one of the core concepts of microservices.
  • Interacting with legacy systems — Often we can’t move everything into a new technology stack. We still have to use legacy systems, which are slow but reliable.

3. RabbitMQ

The Advanced Message Queuing Protocol (AMQP) is the protocol used by RabbitMQ for messaging. Although RabbitMQ supports several other protocols, AMQP is more popular due to compatibility and the number of features it offers.

3.1 RabbitMQ Architecture Design

So publishers publish messages to RabbitMQ called exchanges. Exchanges receive messages and route them to one or more Queues. The routing algorithm relies on the Exchange type and the Routing key/header(passed with the message). These rules that connect exchanges to Queues are called bindings.

There are four types of bindings:

  • Direct: it is based onrouting key(Routing key) willExchangeThe (switch) type is routed directly to a specificQueues(Queue).
  • Fanout: Routes the message to the bindingExchangeAll in (switch)Queues(Queue).
  • Topic: It is based on full match or partial datarouting key(Routing key) matches that routes the message to (0, 1, or more)Queues(Queue).
  • Headers: It is similar toTopic(Topic) exchange type, but it is baserouting header(Road head) instead ofrouting key(routing key) to be routed.

Source: www.cloudamqp.com/

The entire process of publishing and consuming messages via Exchanges and Queues is done through a Channel.

For more information about routing, visit this link.

3.2 the RabbitMQ set

3.2.1 installation

From here we can download and install binaries based on our operating system.

For this article, however, we’ll use the free cloud installation provided by Cloudamqp.com. Just sign up for the service and log in.

Click Create new instance on the main dashboard:

Then give your instance a name and proceed to the next step:

Then select an available zone:

Finally, to view instance information, click create Instance in the lower right corner:

That’s it. You now have a RabbitMQ instance running in the cloud. For more information about instances, go to your dashboard and click on the newly created instance:

We can see the hosts where we can access the RabbitMQ instance, such as the username and password needed to connect from our project:

We’ll connect to this instance in our Spring application using the AMQP URL, so note it down somewhere.

You can also view the manager console by clicking RabbitMQ Manager in the upper left corner. This will use it to manage your RabbitMQ instance.

The Project configuration

Now that our setup is ready, let’s create our service:

  • Cloud-stream-producer-rabbitmq: Acts as a publisher to push messages toRabbitMQ
  • Cloud-stream-consumer-rabbitmq: Consumer consumption messages

Create a scaffolding project using Spring Initializr. This will be our Producer project and we will use REST endpoints to publish messages.

Select your preferred Spring Boot version, add Web and Cloud Stream dependencies, and generate a Maven project:

Note:

Note the cloud-stream dependency. This also requires binder dependencies such as RabbitMQ and Kafka to work.

Since we will be using RabbitMQ, add the following Maven dependencies:

<dependency>  
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
Copy the code

Alternatively, we can combine the two using spring-cloud-starter-stream-rabbit:

<dependency>  
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
Copy the code

Use the same method to create a consumer project, but only with the Spring-Cloud-starter-stream-rabbit dependency.

4. Create producers

As mentioned earlier, the entire process of passing messages from the publisher to the queue is done through channels. So, let’s create a HelloBinding interface that contains our message mechanism, greetingChannel:

interface HelloBinding {

    @Output("greetingChannel")
    MessageChannel greeting(a);
}
Copy the code

Since this will publish the message, we use the @output annotation. The method name can be whatever we want, and of course we can have multiple channels in an interface.

Now, let’s create a REST that pushes messages to this Channel

@RestController
public class ProducerController {

    private MessageChannel greet;

    public ProducerController(HelloBinding binding) {
        greet = binding.greeting();
    }

    @GetMapping("/greet/{name}")
    public void publish(@PathVariable String name) {
        String greeting = "Hello, " + name + "!";
        Message<String> msg = MessageBuilder.withPayload(greeting)
            .build();
        this.greet.send(msg); }}Copy the code

Above, we created a ProducerController class that has a MessageChannel type property greet. This is initialized in the constructor using the method we declared earlier.

Note: We can do the same thing in a succinct way, but we use different names to give you a clearer idea of how things are connected.

Then, we have a simple REST interface that takes the name of PathVariable and uses MessageBuilder to create a message of type String. Finally, we use the.send() method on the MessageChannel to publish the message.

We will now add the @enableBinding annotation to the main class of the HelloBinding to tell Spring to load.

@EnableBinding(HelloBinding.class)
@SpringBootApplication
public class Application {

    public static void main(String[] args) { SpringApplication.run(Application.class, args); }}Copy the code

Finally, we must tell Spring how to connect to RabbitMQ(via the AMQP URL above) and connect greetingChannel to an available consumer.

Both of these are defined in application.properties:

spring.rabbitmq.addresses=<amqp url>

spring.cloud.stream.bindings.greetingChannel.destination = greetings

server.port=8080
Copy the code

5. Create consumers

Now we need to listen on the greetingChannel we created earlier. Let’s create a binding for it:

public interface HelloBinding {

    String GREETING = "greetingChannel";

    @Input(GREETING)
    SubscribableChannel greeting(a);
}
Copy the code

There are two very obvious differences with producer bindings. Because we are consuming messages, we connect to the greetingChannel using the SubscribableChannel and @Input annotations, where the message data will be pushed.

Now, let’s create methods to process the data:

@EnableBinding(HelloBinding.class)
public class HelloListener {

    @StreamListener(target = HelloBinding.GREETING)
    public void processHelloChannelGreeting(String msg) { System.out.println(msg); }}Copy the code

Here, we created a HelloListener class, add @ StreamListener annotations on processHelloChannelGreeting method. This method takes a string as an argument, which we just printed on the console. We also added @enableBinding to the class to enable HelloBinding.

Again, we use @enableBinding here instead of the main class to tell us how to use it.

Looking at our main class, we don’t have any modifications:

@SpringBootApplication
public class Application {

    public static void main(String[] args) { SpringApplication.run(Application.class, args); }}Copy the code

In the application.properties configuration file, we need to define the same properties as the producer, except for changing the port

spring.rabbitmq.addresses=<amqp url>  
spring.cloud.stream.bindings.greetingChannel.destination=greetings  
server.port=9090
Copy the code

6. All tests

Let’s start producer and consumer services at the same time. First, let’s http://localhost:8080/greet/john to create a message by clicking on the endpoint.

See the message content in the consumer log:

We start another consumer service instance (on another port (9091)) with the following command:

$ mvn spring-boot:run -Dserver.port=9091
Copy the code

Now, when we click on the producer REST endpoint to produce the message, we see that both consumers receive the message:

This may be what we want in some use cases. But what if we only want one consumer to consume one message? To do this, we need to create a consumer group in application.properties. Consumer profile:

spring.cloud.stream.bindings.greetingChannel.group = greetings-group
Copy the code

Now run the two instances of the consumer again on different ports and look again through the producer production message:

This can also be seen in the RabbitMQ manager console:

7. Conclusion

In this article, we explained the main concepts of messaging, its role in microservices, and how to implement it using Spring Cloud Stream. We use RabbitMQ as a message broker, but we can also use other popular brokers, such as Kafka, with configuration and dependencies changes.

As always, the sample code used in this article is available at GitHub for full source code.

Original text: stackabuse.com/spring-clou…

By Dhananjay Singh

Translator: Li Dong