https://github.com/openliberty/guide-microprofile-reactive-messaging-acknowledgment
A guide on how to acknowledge messages by using MicroProfile Reactive Messaging.
https://github.com/openliberty/guide-microprofile-reactive-messaging-acknowledgment
Last synced: about 1 year ago
JSON representation
A guide on how to acknowledge messages by using MicroProfile Reactive Messaging.
- Host: GitHub
- URL: https://github.com/openliberty/guide-microprofile-reactive-messaging-acknowledgment
- Owner: OpenLiberty
- License: other
- Created: 2020-03-26T19:52:55.000Z (about 6 years ago)
- Default Branch: prod
- Last Pushed: 2024-05-08T13:47:09.000Z (about 2 years ago)
- Last Synced: 2024-05-08T14:36:32.572Z (about 2 years ago)
- Language: Java
- Homepage: https://openliberty.io/guides/microprofile-reactive-messaging-acknowledgment.html
- Size: 794 KB
- Stars: 2
- Watchers: 5
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.adoc
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
// Copyright (c) 2020, 2025 IBM Corporation and others.
// Licensed under Creative Commons Attribution-NoDerivatives
// 4.0 International (CC BY-ND 4.0)
// https://creativecommons.org/licenses/by-nd/4.0/
//
// Contributors:
// IBM Corporation
//
:projectid: microprofile-reactive-messaging-acknowledgment
:page-layout: guide-multipane
:page-duration: 20 minutes
:page-releasedate: 2020-08-27
:page-majorupdateddate: 2024-04-04
:page-guide-category: microprofile
:page-essential: false
:page-description: Learn how to use MicroProfile Reactive Messaging acknowledgment strategies to acknowledge messages.
:guide-author: Open Liberty
:page-tags: ['microprofile', 'jakarta-ee']
:page-related-guides: ['microprofile-reactive-messaging','microprofile-reactive-messaging-rest','reactive-messaging-sse','reactive-service-testing','microprofile-rest-client-async']
:page-permalink: /guides/{projectid}
:imagesdir: /img/guide/{projectid}
:page-seo-title: Acknowledging messages in Java microservices using MicroProfile Reactive Messaging
:page-seo-description: A getting started tutorial with examples on how to acknowledge messages in asynchronous Java microservices using different MicroProfile Reactive Messaging acknowledgment strategies.
:common-includes: https://raw.githubusercontent.com/OpenLiberty/guides-common/prod
:source-highlighter: prettify
= Acknowledging messages using MicroProfile Reactive Messaging
[.hidden]
NOTE: This repository contains the guide documentation source. To view the guide in published form, view it on the https://openliberty.io/guides/{projectid}.html[Open Liberty website].
Learn how to acknowledge messages by using MicroProfile Reactive Messaging.
== What you'll learn
MicroProfile Reactive Messaging provides a reliable way to handle messages in reactive applications. MicroProfile Reactive Messaging ensures that messages aren't lost by requiring that messages that were delivered to the target server are acknowledged after they are processed. Every message that gets sent out must be acknowledged. This way, any messages that were delivered to the target service but not processed, for example, due to a system failure, can be identified and sent again.
The application in this guide consists of two microservices, `system` and `inventory`. Every 15 seconds, the `system` microservice calculates and publishes events that contain its current average system load. The `inventory` microservice subscribes to that information so that it can keep an updated list of all the systems and their current system loads. You can get the current inventory of systems by accessing the `/systems` REST endpoint. The following diagram depicts the application that is used in this guide:
image::reactive-messaging-system-inventory-rest.png[Reactive system inventory, align="center"]
You will explore the acknowledgment strategies that are available with MicroProfile Reactive Messaging, and you'll implement your own manual acknowledgment strategy. To learn more about how the reactive Java services used in this guide work, check out the https://openliberty.io/guides/microprofile-reactive-messaging.html[Creating reactive Java microservices^] guide.
== Additional prerequisites
You need to have Docker installed. For installation instructions, refer to the official https://docs.docker.com/get-docker/[Docker documentation^]. You will build and run the microservices in Docker containers. An installation of Apache Kafka is provided in another Docker container.
[role='command']
include::{common-includes}/gitclone.adoc[]
== Choosing an acknowledgment strategy
// file 0
start/SystemService.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::start/system/src/main/java/io/openliberty/guides/system/SystemService.java[]
----
Messages must be acknowledged in reactive applications. Messages are either acknowledged explicitly, or messages are acknowledged implicitly by MicroProfile Reactive Messaging. Acknowledgment for incoming messages is controlled by the `@Acknowledgment` annotation in MicroProfile Reactive Messaging. If the `@Acknowledgment` annotation isn't explicitly defined, then the default acknowledgment strategy applies, which depends on the method signature. Only methods that receive incoming messages and are annotated with the `@Incoming` annotation must acknowledge messages. Methods that are annotated only with the `@Outgoing` annotation don't need to acknowledge messages because messages aren't being received and MicroProfile Reactive Messaging requires only that _received_ messages are acknowledged.
Almost all of the methods in this application that require message acknowledgment are assigned the `POST_PROCESSING` strategy by default. If the acknowledgment strategy is set to `POST_PROCESSING`, then MicroProfile Reactive Messaging acknowledges the message based on whether the annotated method emits data:
* If the method emits data, the incoming message is acknowledged after the outgoing message is acknowledged.
* If the method doesn't emit data, the incoming message is acknowledged after the method or processing completes.
It’s important that the methods use the `POST_PROCESSING` strategy because it fulfills the requirement that a message isn't acknowledged until after the message is fully processed. This processing strategy is beneficial in situations where messages must reliably not get lost. When the `POST_PROCESSING` acknowledgment strategy can’t be used, the `MANUAL` strategy can be used to fulfill the same requirement. In situations where message acknowledgment reliability isn't important and losing messages is acceptable, the `PRE_PROCESSING` strategy might be appropriate.
The only method in the guide that doesn't default to the `POST_PROCESSING` strategy is the [hotspot=sendProperty file=0]`sendProperty()` method in the `system` service. The [hotspot=sendProperty file=0]`sendProperty()` method receives property requests from the `inventory` service. For each property request, if the property that's being requested is valid, then the method creates and returns a [hotspot=propertyMessage file=0]`PropertyMessage` object with the value of the property. However, if the [hotspot=null file=0]`propertyName` requested property doesn't exist, the request is ignored and no property response is returned.
A key difference exists between when a property response is returned and when a property response isn't returned. In the case where a property response is returned, the request doesn't finish processing until the response is sent and safely stored by the Kafka broker. Only then is the incoming message acknowledged. However, in the case where the requested property doesn’t exist and a property response isn't returned, the method finishes processing the request message so the message must be acknowledged immediately.
This case where a message either needs to be acknowledged immediately or some time later is one of the situations where the `MANUAL` acknowledgment strategy would be beneficial
== Implementing the MANUAL acknowledgment strategy
// static guide instructions:
ifndef::cloud-hosted[]
Navigate to the `start` directory to begin.
endif::[]
// cloud-hosted instructions
ifdef::cloud-hosted[]
To begin, run the following command to navigate to the ***start*** directory:
```bash
cd /home/project/guide-microprofile-reactive-messaging-acknowledgment/start
```
endif::[]
Update the `SystemService.sendProperty` method to use the `MANUAL` acknowledgment strategy, which fits the method processing requirements better than the default `PRE_PROCESSING` strategy.
[role="code_command hotspot file=0", subs="quotes"]
----
#Replace the `SystemService` class.#
`system/src/main/java/io/openliberty/guides/system/SystemService.java`
----
// file 0
finish/SystemService.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/system/src/main/java/io/openliberty/guides/system/SystemService.java[]
----
The [hotspot=sendProperty file=0]`sendProperty()` method needs to manually acknowledge the incoming messages, so it is annotated with the [hotspot=ackAnnotation file=0]`@Acknowledgment(Acknowledgment.Strategy.MANUAL)` annotation. This annotation sets the method up to expect an incoming message. To meet the requirements of acknowledgment, the method parameter is updated to receive and return a [hotspot=methodSignature file=0]`Message` of type `String`, rather than just a `String`. Then, the `propertyName` is extracted from the `propertyMessage` incoming message using the [hotspot=propertyValue file=0]`getPayload()` method and checked for validity. One of the following outcomes occurs:
* If the [hotspot=invalid file=0]`propertyName` system property isn't valid, the [hotspot=propertyMessageAck file=0]`ack()` method acknowledges the incoming message and returns an empty reactive stream using the [hotspot=emptyReactiveStream file=0]`empty()` method. The processing is complete.
* If the system property is valid, the method creates a [hotspot=returnMessage file=0]`Message` object with the value of the requested system property and sends it to the proper channel. The method acknowledges the incoming message only after the sent message is acknowledged.
== Waiting for a message to be acknowledged
The `inventory` service contains an endpoint that accepts `PUT` requests. When a `PUT` request that contains a system property is made to the `inventory` service, the `inventory` service sends a message to the `system` service. The message from the `inventory` service requests the value of the system property from the system service. Currently, a `200` response code is returned without confirming whether the sent message was acknowledged. Replace the `inventory` service to return a `200` response only after the outgoing message is acknowledged.
[role="code_command hotspot file=0", subs="quotes"]
----
#Replace the `InventoryResource` class.#
`inventory/src/main/java/io/openliberty/guides/inventory/InventoryResource.java`
----
// file 0
InventoryResource.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/inventory/src/main/java/io/openliberty/guides/inventory/InventoryResource.java[]
----
The [hotspot=sendPropertyName file=0]`sendPropertyName()` method is updated to return a [hotspot=SPMHeader file=0]`Message` instead of just a `String`. This return type allows the method to set a callback that runs after the outgoing message is acknowledged. In addition to updating the [hotspot=sendPropertyName file=0]`sendPropertyName()` method, the [hotspot=propertyNameEmitter file=0]`propertyNameEmitter` variable is updated to send a `Message` type.
The [hotspot=updateSystemProperty file=0]`updateSystemProperty()` method now returns a [hotspot=USPHeader file=0]`CompletionStage` object wrapped around a Response type. This return type allows for a response object to be returned after the outgoing message is acknowledged. The outgoing [hotspot=message file=0]`message` is created with the requested property name as the [hotspot=payload file=0]`payload` and an acknowledgment [hotspot=acknowledgeAction file=0]`callback` to execute an action after the message is acknowledged. The method creates a [hotspot=CompletableFuture file=0]`CompletableFuture` variable that returns a [hotspot=returnResult file=0]`200` response code after the variable is completed in the [hotspot=acknowledgeAction file=0]`callback` function.
== Building and running the application
Build the `system` and `inventory` microservices using Maven and then run them in Docker containers.
Start your Docker environment. Dockerfiles are provided for you to use.
To build the application, run the Maven `install` and `package` goals from the command-line session in the `start` directory:
[role='command']
```
mvn -pl models install
mvn package
```
Run the following commands to containerize the microservices:
[role='command']
```
docker build -t system:1.0-SNAPSHOT system/.
docker build -t inventory:1.0-SNAPSHOT inventory/.
```
Next, use the provided script to start the application in Docker containers. The script creates a network for the containers to communicate with each other. It also creates containers for Kafka and the microservices in the project. For simplicity, the script starts one instance of the `system` service.
include::{common-includes}/os-tabs.adoc[]
[.tab_content.windows_section]
--
[role='command']
```
.\scripts\startContainers.bat
```
--
[.tab_content.mac_section]
--
[role='command']
```
./scripts/startContainers.sh
```
--
[.tab_content.linux_section]
--
[role='command']
```
./scripts/startContainers.sh
```
--
== Testing the application
The application might take some time to become available. After the application is up and running, you can access it by making a GET request to the `/systems` endpoint of the `inventory` service.
// Static guide instruction
ifndef::cloud-hosted[]
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. Look for the CPU `systemLoad` property for all the systems:
endif::[]
// Cloud hosted guide instruction
ifdef::cloud-hosted[]
Run the following curl command to confirm that the ***inventory*** microservice is up and running.
```bash
curl -s http://localhost:9085/health | jq
```
When both the liveness and readiness health checks are up, run the following curl command to access the ***inventory*** microservice:
```bash
curl -s http://localhost:9085/inventory/systems | jq
```
Look for the CPU ***systemLoad*** property for all the systems:
endif::[]
[source, role='no_copy']
----
{
"hostname":"30bec2b63a96",
"systemLoad":1.44
}
----
The `system` service sends messages to the `inventory` service every 15 seconds. The `inventory` service processes and acknowledges each incoming message, ensuring that no `system` message is lost.
// Static guide instruction
ifndef::cloud-hosted[]
If you revisit the http://localhost:9085/inventory/systems[^] URL after a while, you notice that the CPU `systemLoad` property for the systems changed.
endif::[]
// Cloud hosted guide instruction
ifdef::cloud-hosted[]
If you run the curl command again after a while, notice that the CPU ***systemLoad*** property for the systems changed.
```bash
curl -s http://localhost:9085/inventory/systems | jq
```
endif::[]
Make a `PUT` request to the `\http://localhost:9085/inventory/data` URL to add the value of a particular system property to the set of existing properties. For example, run the following `curl` command:
include::{common-includes}/os-tabs.adoc[]
[.tab_content.windows_section]
--
If `curl` is unavailable on your computer, use another client such as https://www.getpostman.com/[Postman^], which allows requests to be made with a graphical interface.
--
[.tab_content.mac_section]
--
[role=command]
```
curl -X PUT -d "os.name" http://localhost:9085/inventory/data --header "Content-Type:text/plain"
```
--
[.tab_content.linux_section]
--
[role=command]
```
curl -X PUT -d "os.name" http://localhost:9085/inventory/data --header "Content-Type:text/plain"
```
--
In this example, the `PUT` request with the `os.name` system property in the request body on the `\http://localhost:9085/inventory/data` URL adds the `os.name` system property for your system. The `inventory` service sends a message that contains the requested system property to the `system` service. The `inventory` service then waits until the message is acknowledged before it sends a response back.
You see the following output:
[source, role="no_copy"]
----
Request successful for the os.name property
----
The previous example response is confirmation that the sent request message was acknowledged.
// Static guide instruction
ifndef::cloud-hosted[]
Revisit the http://localhost:9085/inventory/systems[^] URL and see the `os.name` system property value is now
included with the previous values:
endif::[]
// Cloud hosted guide instruction
ifdef::cloud-hosted[]
Run the following curl command again:
```bash
curl -s http://localhost:9085/inventory/systems | jq
```
The ***os.name*** system property value is now included with the previous values:
endif::[]
[source, role='no_copy']
----
{
"hostname":"30bec2b63a96",
"os.name":"Linux",
"systemLoad":1.44
}
----
== Tearing down the environment
Finally, run the following script to stop the application:
include::{common-includes}/os-tabs.adoc[]
[.tab_content.windows_section]
--
[role='command']
```
.\scripts\stopContainers.bat
```
--
[.tab_content.mac_section]
--
[role='command']
```
./scripts/stopContainers.sh
```
--
[.tab_content.linux_section]
--
[role='command']
```
./scripts/stopContainers.sh
```
--
== Great work! You're done!
You developed an application by using MicroProfile Reactive Messaging, Open Liberty, and Kafka.
== Related Links
Learn more about MicroProfile.
https://download.eclipse.org/microprofile/microprofile-reactive-messaging-3.0/microprofile-reactive-messaging-spec.html[View the MicroProfile Reactive Messaging Specification^]
https://download.eclipse.org/microprofile/microprofile-reactive-messaging-3.0/apidocs/[View the MicroProfile Reactive Messaging Javadoc^]
https://openliberty.io/docs/latest/microprofile.html[View the MicroProfile^]
include::{common-includes}/attribution.adoc[subs="attributes"]