Event-Driven Microservices With Spring Boot and ActiveMQ
A guide to using Spring Boot and ActiveMQ for event-driven microservices.
Join the DZone community and get the full member experience.
Join For FreeMost communications between microservices are 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 patterns is the messaging pattern. It is extremely scalable, flexible, and guarantees 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 the 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.
<?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: 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 the JMS provider and obtain a session. JmsTemplate
is a helper class that simplifies sending and receiving of messages through JMS and gets rid of boilerplate code.
We have now created a simple API endpoint that will accept a string as a parameter and puts it in 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 an 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 an 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 purposes. Once you have installed, the ActiveMQ server should be available at http://localhost:8161/admin and we will see the following welcome 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 the 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 the queue.
In the ActiveMQ dashboard, navigate to the Queue tab. We can see the details such as a number of consumers to a queue, the number is messages pending, queued and dequeued.
At 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 the 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 zero and the number of dequeued messages is set to two. The message broker guarantees the delivery of messages.
Conclusion
We just saw a simple example of a messaging pattern in this article. The 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.
Published at DZone with permission of Swathi Prasad, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments