Event-Driven Microservices with Spring Boot and ActiveMQ

Most communications between microservices is either via HTTP request-response APIs or asynchronous messaging. While these two mechanisms are most commonly used, yet they’re quite different. It is important to know when to use which mechanism.

Event-driven communication is important when propagating changes across several microservices and their related domain models. This means that when changes occur, we need some way to coordinate changes across the different models. This ensures reliable communication as well as loose coupling between microservices.

There are multiple patterns to achieve event-driven architecture. One of the common and popular one is messaging pattern. This is extremely scalable, flexible and guarantee delivery of messages. There are several tools that can be used for messaging pattern such as RabbitMQ, ActiveMQ, Apache Kafka and so on.

Messaging Pattern

In this article, we are going to build microservices using Spring Boot and we will set up ActiveMQ message broker to communicate between microservices asynchronously.

Building Microservices

Let us create two Spring Boot projects ‘activemq-sender’ and ‘activemq-receiver’.  Here is the sample project structure.

Sample project structure

We need to add maven dependency spring-boot-starter-activemq to enable ActiveMQ. Here is a sample pom.xml.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.techshard.activemqsender</groupId>
    <artifactId>activemq-sender</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath />
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-activemq</artifactId>
        </dependency>
    </dependencies>

</project>

Configuring Publisher

In the project activemq-sender, we will first configure a queue. Create a JmsConfig class as follows.

package com.techshard.activemq.configuration;

import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;

import javax.jms.Queue;

@Configuration
public class JmsConfig {

    @Bean
    public Queue queue(){
        return new ActiveMQQueue("test-queue");
    }
}

The above class just declares a bean Queue and our queue name would be test-queue. Note that, queue names can also be read from application properties. This is just an example.

Now, let’s create a REST API which will be used to publish the message to the queue.

package com.techshard.activemq.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.jms.Queue;

@RestController
@RequestMapping("/api")
public class MessageController {

    @Autowired
    private Queue queue;

    @Autowired
    private JmsTemplate jmsTemplate;

    @GetMapping("message/{message}")
    public ResponseEntity<String> publish(@PathVariable("message") final String message){
        jmsTemplate.convertAndSend(queue, message);
        return new ResponseEntity(message, HttpStatus.OK);
    }

}

In the controller, we will inject the bean Queue which we declared before and we will also inject JmsTemplate.

To send or receive messages through JMS, we need to establish a connection to JMS provider and obtain session. JmsTemplate is a helper class which simplifies sending and receiving of messages through JMS and gets rid of boilerplate code.

We have now created a simple API endpoint which will accept string as a parameter and puts it on the queue.

Configuring Consumer

In the project activemq-receiver, create a component class as follows:

package com.techshard.activemq.consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

@Component
@EnableJms
public class MessageConsumer {

    private final Logger logger = LoggerFactory.getLogger(MessageConsumer.class);

    @JmsListener(destination = "test-queue")
    public void listener(String message){
        logger.info("Message received {} ", message);
    }
}

In this class, we have annotated method with @JmsListener and we have passed the queue name test-queue which we configured in the publisher. @JmsListener is used for listening to any messages that are put on the queue test-queue.

Notice that we have annotated class with @EnableJms. As Spring documentation says @EnableJms enables detection of JmsListener annotations on any Spring-managed bean in the container.

The interesting point here is that, Spring Boot detects the methods even without @EnableJms annotation. This issue has been reported on Stackoverflow.

Creating Spring Boot Applications

In both the projects, create an Application class annotated with @SpringBootApplication as below.

package com.techshard.activemq;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

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

Installing ActiveMQ

I have installed ActiveMQ by downloading here. We can also use Spring Boot’s embedded ActiveMQ for testing purpose.  Once you have installed, the ActiveMQ server should be available at http://localhost:8161/admin and we will see the following welcome page.

ActiveMQ Home Page

Configuring ActiveMQ

In both the projects, create application.properties file and add the following properties.

spring.activemq.broker-url=tcp://localhost:61616
spring.activemq.user=admin
spring.activemq.password=admin

ActiveMQ supports multiple protocols to connect to the message broker. In this example, we will be using OpenWire protocol.

That’s it!

Testing ActiveMQ

Before running the applications, make sure to change the server port for one of the projects. The embedded tomcat server runs on the port 8080 by default.

Run both the applications and run the URL http://localhost:8080/api/message/Welcome to ActiveMQ! in  browser or any REST API testing tool.

In the consumer application, we will see the following log in console.

2019-08-06 22:29:57.667  INFO 17608 — [enerContainer-2] c.t.activemq.consumer.MessageConsumer    : Message received Welcome to ActiveMQ!

´What just happened is that the message was put on the queue. The consumer application that was listening to the queue read the message from queue.

In the ActiveMQ dashboard, navigate to Queue tab. We can see the details such as number of consumers to a queue, number is messages pending, queued and dequeued.

In the beginning of this article, I mentioned that message brokers guarantee delivery of messages. Imagine that the consumer service is down, and the message was put on the queue by publisher service.

Stop the application activemq-receiver. Run this URL again http://localhost:8080/api/message/Welcome to ActiveMQ! In browser.

Navigate to ActiveMQ dashboard and notice the queue state.

We can see that one message is pending and enqueued. Start the application activemq-receiver again.

As soon as the application is started, we will the following message in console.

2019-08-06 22:54:32.667  INFO 17608 — [enerContainer-2] c.t.activemq.consumer.MessageConsumer    : Message received Welcome to ActiveMQ!

The number of pending messages is now set to 0 and number of dequeued messages is set to 2. The message broker guarantees delivery of messages.

Conclusion

We just saw a simple example of messaging pattern in this article. Messaging system takes the responsibility of transferring data from one service to another, so the services can focus on what data they need to share but not worry about how to share it.

I hope you enjoyed this article. Feel free to let me know if you have any comments or suggestions.

As always, the complete code can be found on my GitHub repository.

Advertisements

Reader Comments

This site uses Akismet to reduce spam. Learn how your comment data is processed.