Using SQS With JMS for Legacy Applications
In this article, take an in-depth look at the steps to integrate a legacy Java application with SQS through JMS.
Join the DZone community and get the full member experience.
Join For FreeAs part of migrating Java enterprise applications to the cloud, it is beneficial to replace parts of the technology stack with equivalent cloud services. As an example, traditional messaging services are being replaced by Amazon SQS as part of the migration to AWS. Java Messaging Service (JMS) has been a mainstay in the technology stack of Java enterprise applications. The support provided by SQS for the JMS standard helps with an almost seamless technology replacement. In this article, I have described the steps to integrate a legacy Java application with SQS through JMS.
Legacy Java Application
For the purposes of this article, a legacy Java application has the following characteristics:
- Uses the JMS standard to interact with messaging services
- The application is not built using Maven/Gradle or any other package dependency managers as part of Java code build and packaging.
Maven "Uber JAR" Approach
The AWS Java SDK, which contains the Java Messaging Service library for SQS, requires the use of Maven or Gradle to set up the development environment and write code that uses various AWS services. As the legacy application does not use Maven/Gradle, we will use an approach called the "Uber JAR" approach.
In this approach, a Maven project outside the source tree of the legacy application will be created. This Maven project will use the packaging capability of Maven to create an Uber JAR, which is basically a JAR file containing the Java messaging library for SQS and all its direct and transitive dependencies. For example, the SQS Java messaging library depends on the base SQS library which, in turn, has a dependency on multiple Apache libraries (among many other dependencies). All these libraries are packaged into one JAR file. This JAR file can then be added to the classpath of the legacy application and included in its deployment packages.
An example pom file to create the Uber JAR is given below.
<?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.example.aws</groupId>
<artifactId>sqssample</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.shade.plugin.version>3.2.1</maven.shade.plugin.version>
<maven.compiler.plugin.version>3.6.1</maven.compiler.plugin.version>
<exec-maven-plugin.version>1.6.0</exec-maven-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>amazon-sqs-java-messaging-lib</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven.shade.plugin.version}</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<finalName>sqsjmssample</finalName>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Running "mvn package" for this pom will create the Uber JAR file, sqsjmssample.jar. This JAR file should then be added to the classpath of the legacy application just like any other third-party library.
Maven Shading
What happens if the SQS library and the legacy Java application depend on the same open-source library but different versions? This can cause subtle issues during the runtime that affect the functioning of both the application and SQS library. In the pom file above, you will notice that the Maven shade
plugin is being used. As part of creating the Uber JAR, this plugin can rename packages of the dependencies. Assuming that both the SQS library and the Java application depend on different versions of Apache libraries, adding the shading configuration given below will avoid any issues during runtime.
<relocations>
<relocation>
<pattern>org.apache</pattern>
<shadedPattern>software.amazon.awssdk.thirdparty.org.apache</shadedPattern>
</relocation>
</relocations>
This snippet has to be added under the <configuration>
section of the shade
plugin declaration in the pom file. You will notice that Uber JAR built with this configuration has all the Apache libraries moved to the software.amazon.awssdk.thirdparty.org.apache.**
packages. The shade
plugin also repoints any dependencies on the Apache libraries to these new code packages.
Sample Java Code
Now that we have the Uber JAR, let us look at a sample Java code to send a message to a queue destination.
SqsClient sqsClient = SqsClient.builder().region(Region.US_EAST_1).build();
try {
String queueName = "myqueue";
String message = "Test Message";
// Create the connection factory based on the config
SQSConnectionFactory connectionFactory = new SQSConnectionFactory(new ProviderConfiguration(),
SqsClient.builder().region(Region.US_EAST_1).build());
// Create the connection
SQSConnection connection = connectionFactory.createConnection();
// Create the session
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(session.createQueue(queueName));
TextMessage txtmessage = session.createTextMessage(message);
producer.send(txtmessage);
connection.close();
} catch (SqsException e) {
System.err.println(e.awsErrorDetails().errorMessage());
System.exit(1);
} catch (JMSException e) {
e.printStackTrace();
}
Note that apart from the SQSConnectionFactory
and SQSConnection
, the rest of the code uses standard JMS types.
Ideally, the existing messaging service can be replaced by SQS without any code changes to the Java application. Just changing the Application Server configuration for the JMS resources involved (to point to SQS Queues and Connection Factories) should suffice. This approach would require a JCA (Java Connector Architecture)-compliant Resource Adapter implementation for SQS. Even though there are some open-source SQS Resource Adapter implementations, the SQS Java Messaging library does not include one. Of course, using the open-source Resource Adapters carries licensing and support risks.
Design Considerations
- As we saw above with the Maven shading approach, the code packages of open-source libraries can be changed. This could trip up the vulnerability reports produced by OSS (Open Source Software) scanners. So, it is important to set up the Maven project also as another code stream that would go through the same security scans as the Java application codebase.
- The Uber JAR approach discussed above can be used for any AWS service. For example, the Java Application could be modernized to store documents in S3 buckets instead of in the application database. So, it is important to consider the range of AWS services to be used by the application while setting up the required Maven dependencies, to avoid creating numerous Uber JARs with duplicate code.
- The SQS Java Messaging Library does not support Distributed (XA) transactions. If the Java application being migrated requires the messaging provider to support distributed transactions, Amazon MQ (Active MQ) could be a better choice than SQS. Alternatively, the application may have to be refactored to use a pseudo-distributed transaction pattern like SAGA OR to avoid using distributed transactions altogether.
Opinions expressed by DZone contributors are their own.
Comments