Producing and consuming messages in Java microservices

Note
This repository contains the guide documentation source. To view the guide in published form, view it on the Open Liberty website.

Learn how to produce and consume messages in Java microservices by using Jakarta Messaging with Liberty Messaging Server, Liberty Messaging Server Client, and IBM MQ.

What you’ll learn

You will learn how to communicate between Java services by using Jakarta Messaging with Liberty Messaging Server, Liberty Messaging Server Client, and IBM MQ. You will use a Jakarta Messaging service to handle the asynchronous messages that are sent and received between the microservices as streams of events.

You’ll also explore the configuration and use of Liberty Messaging Server, along with examples of message production and consumption with Liberty Messaging Server Client. Additionally, you will discover how to use IBM MQ for Jakarta Messaging.

In this guide, you will use Jakarta Messaging APIs to build the application and implement a messaging solution that enables communication between different parts of Java microservices. Jakarta Messaging makes it easy to write and configure your application to send, receive, and process the events efficiently.

What is Jakarta Messaging?

Jakarta Messaging provides an easy way to asynchronously send, receive, and process messages that are received as continuous streams of events. By integrating Jakarta Messaging with Open Liberty, you can easily configure and manage message producers and consumers within your Java microservices. You simply use the Jakarta Messaging API to annotate methods in your application beans, and Open Liberty handles the communication infrastructure, ensuring messages are reliably exchanged. This integration allows your services to connect easily with external messaging systems, such as IBM MQ.

The application in this guide consists of two microservices, system and inventory. Every 15 seconds, the system microservice computes and publishes an event that contains the recent CPU load usage. The inventory microservice subscribes to that information so that it can keep an updated list of all the systems and their current system loads. The current inventory of systems is available from the /systems REST endpoint. You’ll create the system and inventory microservices by using the Jakarta Messaging service.

Application architecture where system and inventory services use the Jakarta Messaging to communicate.

Additional prerequisites

If you’re focusing on sections other than the optional Using IBM MQ section, you can move forward without running Docker and skip this section.

You need to install Docker if it is not already installed. For installation instructions, refer to the official Docker documentation. You will build and run the application in Docker containers.

Make sure to start your Docker daemon before you proceed.

Try what you’ll build

The finish directory in the root of this guide contains the finished application. Give it a try before you proceed.

To try out the application, first go to the finish directory and run the following Maven goal to build and install the models module.

cd finish
mvn -pl models clean install

Start the inventory service by running the following command:

mvn -pl inventory liberty:run

Next, open another command-line session, navigate to the finish directory, and start the system service by using the following command:

mvn -pl system liberty:run

After you see the following message, your Liberty instances are ready:

The defaultServer server is ready to run a smarter planet.

Visit the http://localhost:9085/health URL to confirm that the inventory microservice is up and running.

When both the liveness and readiness health checks are up, go to the http://localhost:9085/inventory/systems URL to access the inventory microservice. You see the systemLoad property for all the systems:

{
   "hostname": <your hostname>,
   "systemLoad": 6.037155240703536E-9
}

You can revisit the http://localhost:9085/inventory/systems URL after a while, and you will notice the systemLoad property for the systems changed.

You can also use curl command to retrieve the hostname and systemLoad information from the inventory/systems server endpoint in another command line session:

curl http://localhost:9085/inventory/systems

After you are finished checking out the application, stop the Liberty instance by pressing CTRL+C in each command-line session where you ran Liberty. Alternatively, you can run the liberty:stop goal from the finish directory in another shell session:

mvn -pl inventory liberty:stop
mvn -pl system liberty:stop

Creating the consumer in the inventory microservice

Navigate to the start directory to begin.

When you run Open Liberty in dev mode, dev mode listens for file changes and automatically recompiles and deploys your updates whenever you save a new change.

Run the following goal to start the inventory service in dev mode:

mvn -pl inventory liberty:dev

After you see the following message, your Liberty instance is ready in dev mode:

**************************************************************
*    Liberty is running in dev mode.

Dev mode holds your command-line session to listen for file changes. Open another command-line session to continue, or open the project in your editor.

The inventory microservice records in its inventory the recent system load information that it received from potentially multiple instances of the system service.

Create the InventoryQueueListener class.
inventory/src/main/java/io/openliberty/guides/inventory/InventoryQueueListener.java

InventoryQueueListener.java

link:finish/inventory/src/main/java/io/openliberty/guides/inventory/InventoryQueueListener.java[role=include]

The inventory microservice receives the messages from the system microservice. Implement the InventoryQueueListener class with the MessageListener interface and annotate with @MessageDriven for monitoring the jms/InventoryQueue queue. Override the onMessage() method that processes the incoming messages, updates the inventory by using the InventoryManager bean, and logs the action. Use the SystemLoad.fromJson() method to convert the JSON message string to the SystemLoad object.

Enable the inventory service with an embedded messaging server to monitor the message queue.

Replace the inventory's server.xml configuration file.
inventory/src/main/liberty/config/server.xml

inventory/server.xml

link:finish/inventory/src/main/liberty/config/server.xml[role=include]

The messagingServer feature enables a Liberty runtime to host an embedded messaging server to manage messaging destinations. The messagingClient feature enables applications to connect to a Liberty messaging server and access the messaging destinations hosted on that server through the Jakarta Messaging API that is enabled by the messaging feature.

Add the wasJmsEndpoint element to configure the Liberty runtime to monitor and manage incoming JMS connections from any hosts. Set up the messagingEngine configuration to ensure that the Liberty runtime can manage incoming message queues more effectively, assigning a reliable and persistent destination for the InventoryQueue. Configure a jmsConnectionFactory element to use the InventoryCM and set properties for the JMS implementation. Define a jmsQueue element for the inventory Jakarta Messaging service and a jmsActivationSpec element to configure properties, including the destination queue reference and maximum concurrency.

To learn more about configuration for the jmsQueue element and jmsConnectionFactory element, see the JMS Queue and JMS Connection Factory documentation.

Creating the message producer in the system service

Open another command-line session, navigate to the start directory, and run the following goal to start the system service in dev mode:

mvn -pl system liberty:dev

After you see the following message, your Liberty instance is ready in dev mode:

**************************************************************
*    Liberty is running in dev mode.

The system microservice is the producer of the messages that are published to the Jakarta Messaging service as a stream of events. Every 15 seconds, the system microservice publishes an event that contains its calculation of the recent CPU usage for the last minute.

Create the SystemService class.
system/src/main/java/io/openliberty/guides/system/SystemService.java

SystemService.java

link:finish/system/src/main/java/io/openliberty/guides/system/SystemService.java[role=include]

The SystemService class contains the sendSystemLoad() method that calculates the recent system load, creates a SystemLoad object, and publishes the object as a message to the jms/InventoryQueue queue running in the Jakarta Messaging service by using the send() API. The @Schedule annotation on the sendSystemLoad() method sets the frequency at which the system service publishes the calculation to the event stream, ensuring it runs every 15 seconds.

Enable the system service to access the message queue.

Replace the system's server.xml configuration file.
system/src/main/liberty/config/server.xml

system/server.xml

link:finish/system/src/main/liberty/config/server.xml[role=include]

The messaging and messagingClient features enable the Liberty runtime to provide the required messaging services. Add a connectionManager element to handle connections for the messaging server running on the inventory service. Define the jmsConnectionFactory element to use the InventoryCM and set up the required remoteServerAddress properties. Use the jmsQueue element to define the inventory queue.

In your dev mode console for the system microservice, type r and press enter/return key to restart the Liberty instance. After you see the following message, your Liberty instance is ready in dev mode:

**************************************************************
*    Liberty is running in dev mode.

You can find the inventory and system services at the following URLs:

You can also use curl command to retrieve the hostname and systemLoad information from the inventory/systems server endpoint in another command line session:

curl http://localhost:9085/inventory/systems

Testing the inventory application

While you can test your application manually, you should rely on automated tests because they trigger a failure whenever a code change introduces a defect. Because the application is a RESTful web service application, you can use JUnit and the RESTful web service Client API to write tests. In testing the functionality of the application, the scopes and dependencies are being tested.

Create the InventoryEndpointIT class.
inventory/src/test/java/it/io/openliberty/guides/inventory/InventoryEndpointIT.java

InventoryEndpointIT.java

link:finish/inventory/src/test/java/it/io/openliberty/guides/inventory/InventoryEndpointIT.java[role=include]

See the following descriptions of the test cases:

  • testGetSystems() verifies that the hostname and the system load for each system in the inventory are not empty.

  • testGetSystemsWithHost() verifies that the hostname and system load returned by the system service match the ones stored in the inventory service and ensures they are not empty.

  • testUnknownHost() verifies that an unknown host or a host that does not expose their JVM system properties is correctly handled as an error.

Running the tests

Because you started Open Liberty in dev mode, you can run the tests by pressing the enter/return key from the command-line session where you started dev mode for the inventory service.

If the tests pass, you see a similar output to the following example:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.inventory.InventoryEndpointIT
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.325 sec - in it.io.openliberty.guides.inventory.InventoryEndpointIT

Results :

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0

When you are done checking out the service, stop the Liberty instance by pressing CTRL+C in each command-line session where you ran the system and inventory services.

Using IBM MQ - Optional

The application has been built and tested. In this section, you’ll learn how to configure Liberty to use IBM MQ container as the messaging server.

Start IBM MQ by running the following command on the command-line session:

docker pull icr.io/ibm-messaging/mq:latest

docker volume create qm1data

docker run ^
--env LICENSE=accept ^
--env MQ_QMGR_NAME=QM1 ^
--volume qm1data:/mnt/mqm ^
--publish 1414:1414 --publish 9443:9443 ^
--detach ^
--env MQ_APP_PASSWORD=passw0rd ^
--env MQ_ADMIN_PASSWORD=passw0rd ^
--rm ^
--platform linux/amd64 ^
--name QM1 ^
icr.io/ibm-messaging/mq:latest

Start IBM MQ by running the following command on the command-line session:

docker pull icr.io/ibm-messaging/mq:latest

docker volume create qm1data

docker run \
--env LICENSE=accept \
--env MQ_QMGR_NAME=QM1 \
--volume qm1data:/mnt/mqm \
--publish 1414:1414 --publish 9443:9443 \
--detach \
--env MQ_APP_PASSWORD=passw0rd \
--env MQ_ADMIN_PASSWORD=passw0rd \
--rm \
--platform linux/amd64 \
--name QM1 \
icr.io/ibm-messaging/mq:latest

If you’re an Intel-based Mac user, start IBM MQ by running the following command on the command-line session:

docker pull icr.io/ibm-messaging/mq:latest

docker volume create qm1data

docker run \
--env LICENSE=accept \
--env MQ_QMGR_NAME=QM1 \
--volume qm1data:/mnt/mqm \
--publish 1414:1414 --publish 9443:9443 \
--detach \
--env MQ_APP_PASSWORD=passw0rd \
--env MQ_ADMIN_PASSWORD=passw0rd \
--rm \
--platform linux/amd64 \
--name QM1 \
icr.io/ibm-messaging/mq:latest

If you’re an ARM-based Mac user, check out the How to build Mac IBM MQ container image blog in the IBM TechXchange Community website for building IBM MQ container image.

Navigate to an empty directory for building the IBM MQ Docker container image and run the following commands:

git clone https://github.com/ibm-messaging/mq-container.git -b 9.4.0.0-r3
cd mq-container
make build-devserver COMMAND=docker

After building the container image, you can find the image version:

docker images | grep mq

If it builds successfully, you will see an image similar to the ibm-mqadvanced-server-dev:9.4.0.0-arm64. Now, you can start IBM MQ by running the following command on the command-line session:

docker volume create qm1data

docker run \
--env LICENSE=accept \
--env MQ_QMGR_NAME=QM1 \
--volume docker:/mnt/mqm \
--publish 1414:1414 --publish 9443:9443 \
--detach \
--env MQ_APP_PASSWORD=passw0rd \
--env MQ_ADMIN_PASSWORD=passw0rd \
--name QM1 ibm-mqadvanced-server-dev:9.4.0.0-arm64

If the IBM MQ container runs successfully, you can access the https://localhost:9443/ibmmq/console URL.

Replace the pom.xml file of the inventory service.
inventory/pom.xml

inventory/pom.xml

link:ibmmq/inventory/pom.xml[role=include]

Add the liberty.var.ibmmq-* properties for the IBM MQ container. You can change to different values when you deploy the application on production environment without modifying the Liberty server.xml configuration file.

Replace the server.xml file of the inventory service.
inventory/src/main/liberty/config/server.xml

inventory/server.xml

link:ibmmq/inventory/src/main/liberty/config/server.xml[role=include]

Refine the jmsQueue and jmsActivationSpec configurations with the variables for IBM MQ settings. Add the resourceAdapter element to define the RAR file that provides the IBM MQ classes for Java and JMS. Note that the messagingEngine and jmsConnectionFactory configurations are removed from the configuration because they are no longer being required.

Replace the pom.xml file of the system service.
system/pom.xml

system/pom.xml

link:ibmmq/system/pom.xml[role=include]

Add the liberty.var.ibmmq-* properties for the IBM MQ container as you did for the inventory service previously.

Replace the server.xml file of the system service.
system/src/main/liberty/config/server.xml

system/server.xml

link:ibmmq/system/src/main/liberty/config/server.xml[role=include]

Replace the properties.wasJms configuration by the properties.wmqjmsra configuration. All property values are defined in the pom.xml file that you replaced. Also, modify the jmsQueue property to set the baseQueueName value with the ${ibmmq-inventory-queue-name} variable. Add the resourceAdapter element like you did for the inventory service.

Start the inventory service by running the following command in dev mode:

mvn -pl inventory liberty:dev

Next, open another command-line session, navigate to the start directory, and start the system service by using the following command:

mvn -pl system liberty:dev

After you see the following message, your Liberty instances are ready in dev mode:

The defaultServer server is ready to run a smarter planet.

You can access the inventory service by the http://localhost:9085/inventory/systems URL.

In the command shell where inventory dev mode is running, press enter/return to run the tests. If the tests pass, you’ll see output that is similar to the following example:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.inventory.InventoryEndpointIT
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.325 sec - in it.io.openliberty.guides.inventory.InventoryEndpointIT

Results :

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0

After you are finished checking out the application, stop the Liberty instances by pressing CTRL+C in the command-line sessions where you ran the system and inventory services.

Run the following commands to stop the running IBM MQ container and clean up the qm1data volume:

docker stop QM1
docker rm QM1
docker volume remove qm1data

Great work! You’re done!

You just developed a Java cloud-native application that uses Jakarta Messaging to produce and consume messages in Open Liberty.