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 run tasks parallelly or asynchronously in Java microservices by using Jakarta Concurrency APIs.
Jakarta Concurrency is a Java API specification of Jakarta EE that provides a framework for developing concurrent and asynchronous Java applications. The Jakarta Concurrency APIs provide concurrency utilities to make developers easier writing thread-safe and scalable applications to execute tasks parallelly or asynchronously and coordinate the completion of multiple asynchronous tasks in a more efficient manner. To learn more about Jakarta Concurrency, see the Jakarta Concurrency specification.
The application in this guide consists of two microservices, system
and inventory
. The system
microservice provides GET REST APIs to retreive the Java system properties, heap size, memory usage, and system load. The inventory
microservice provides different REST APIs to register systems, update their memory usage and system load, and query their information from the inventory.
You’ll learn how to run tasks in parallel by implementing the POST /api/inventory/system/{hostname}
endpoint that registers a system to the inventory, create asynchronous task by the PUT /api/inventory/systems/memoryUsed
endpoint that updates the memory usage of the systems in the inventory, and synchronize asynchronous tasks by the PUT /api/inventory/systems/systemLoad
endpoint that updates the system load of the systems.
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, navigate to the finish
directory and run the following command to deploy the system
microservice to Open Liberty:
mvn -pl system liberty:run
Open another command-line session. Navigate to the finish
directory and run the following command to deploy the inventory
service to Open Liberty:
mvn -pl inventory liberty:run
When you see the following message in both command-line sessions, both your services are ready.
The defaultServer server is ready to run a smarter planet.
Visit the http://localhost:9081/openapi/ui URL to try out the REST endpoints of the inventory
microservice.
First, make a POST request to the /api/inventory/system/{hostname}
endpoint. To make this request, expand the POST endpoint on the UI, click the Try it out
button, provide localhost
for the hostname
parameter, then click the Execute
button. The POST request registers the localhost
system to the inventory.
Visit the http://localhost:9081/api/inventory/systems URL to see the inventory. You will see an output similar to the following:
[ { "heapSize": 8589934592, "hostname": "localhost", "javaVersion": "17.0.12", "memoryUsage": 0, "osName": "Mac OS X", "systemLoad": 0 } ]
Repeat the above POST endpoint for the hostname
parameter with the value of 127.0.0.1
and your local system hostname.
Then, make a PUT request to the /api/inventory/systems/memoryUsed
endpoint with 5
seconds for the after
parameter and a PUT request to the /api/inventory/systems/systemLoad
endpoint with 5
seconds for the after
parameter. After 5 seconds, visit the http://localhost:9081/api/inventory/systems URL again to see that the memoryUsage
and systemLoad
values are updated to non-zero. If you interest, try out the other endpoints.
After you are finished checking out the application, stop the Liberty instance by pressing CTRL+C
in the command-line session where you ran the inventory
services. Alternatively, you can run the liberty:stop
goal from the finish
directory in another command-line session:
mvn -pl inventory liberty:stop
Let the system
microservice running. In the following sections, you’ll implement the inventory
microservice to learn how to create parallel or asynchronous tasks.
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
microservice in dev mode:
mvn -pl inventory liberty:dev
When 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.
server.xml
link:finish/inventory/src/main/liberty/config/server.xml[role=include]
The Jakarta Concurrency feature has already been enabled for you in the Liberty server.xml
configuration file.
In this section, you will learn how to implement a task that calls the system
microservice in parallel to get different system data.
InventoryAsyncTask.java
link:finish/inventory/src/main/java/io/openliberty/guides/inventory/InventoryAsyncTask.java[role=include]
Create theInventoryAsyncTask.java
file.inventory/src/main/java/io/openliberty/guides/inventory/InventoryAsyncTask.java
This bean provides different utilities for the inventory
service. Annotating the managedExecutor
field with the @Resource
annotation, an instance of the ManagedScheduledExecutorService
requested resource will be injected when the bean is initialized by Liberty runtime.
The getClientData()
method uses the managedExecutor
to submit multiple tasks in parallel. Each task uses the getSystemClient()
method to get the REST client for the given system hostname, and use the client to retieve the os.name
property, java.version
property, and the heap size
of the system.
The submit()
tasks run in parallel and return Future<>
object immediatedly for the result of asynchronous task. Use the get()
method to wait the task completion, and then retrieves the result.
Implement the POST /api/inventory/system/{hostname}
endpoint of the inventory
microservice to register a system to the inventory.
InventoryResource.java
link:finish/inventory/src/main/java/io/openliberty/guides/inventory/InventoryResource.java[role=include]
Replace theInventoryResource.java
file.inventory/src/main/java/io/openliberty/guides/inventory/InventoryResource.java
Inject the InventoryAsyncTask
bean into the InventoryResource
class. Create the POST /system/{hostname}
endpoint that uses the bean’s getClientData()
method to collect the properties from the system
microservice and addes the system into the inventory.
You started the Open Liberty in dev mode at the beginning of the this section, so all the changes were automatically picked up.
Visit the http://localhost:9081/openapi/ui URL and make a POST request to the /api/inventory/system/{hostname}
endpoint with the value of localhost
. Then, visit the http://localhost:9081/api/inventory/systems URL to see the inventory. You will see an output similar to the following:
[ { "heapSize": 8589934592, "hostname": "localhost", "javaVersion": "17.0.12", "memoryUsage": 0, "osName": "Mac OS X", "systemLoad": 0 } ]
Repeat the above POST endpoint for the hostname
parameter with the value of 127.0.0.1
and your local system hostname.
In this section, you will learn how to implement an asynchronous task to update the memory usage of the systems in the inventory.
InventoryAsyncTask.java
link:finish/inventory/src/main/java/io/openliberty/guides/inventory/InventoryAsyncTask.java[role=include]
Replace theInventoryAsyncTask.java
file.inventory/src/main/java/io/openliberty/guides/inventory/InventoryAsyncTask.java
The updateSystemsMemoryUsed()
method uses the managedExecutor
to schedule multiple tasks that will be delayed with the given after
seconds and executed in parallel. Each task calls the client’s getMemoryUsed()
method to retieve the memory usage, and calls the system’s setMemoryUsed()
method to calculate the memory usage.
Annotate the updateSystemsMemoryUsed()
method with the @Asynchronous
annotation to make it running asynchronously.
InventoryResource.java
link:finish/inventory/src/main/java/io/openliberty/guides/inventory/InventoryResource.java[role=include]
Replace theInventoryResource.java
file.inventory/src/main/java/io/openliberty/guides/inventory/InventoryResource.java
Create the PUT /systems/memoryUsed
endpoint that uses the task
bean’s updateSystemsMemoryUsed()
method to update the memory usage of all systems in the inventory and returns immediately.
Visit the http://localhost:9081/openapi/ui URL and make a PUT request to the /api/inventory/systems/memoryUsed
endpoint with 5
seconds for the after
parameter. After 5 seconds, visit the http://localhost:9081/api/inventory/systems URL to see that the memoryUsage
values are updated to non-zero.
In this section, you will learn how to synchronize asynchronous tasks to update the system load of the systems in the inventory.
InventoryAsyncTask.java
link:finish/inventory/src/main/java/io/openliberty/guides/inventory/InventoryAsyncTask.java[role=include]
Replace theInventoryAsyncTask.java
file.inventory/src/main/java/io/openliberty/guides/inventory/InventoryAsyncTask.java
The getSystemLoad()
method calls the client’s getSystemLoad()
method to retieve the system load, and returns the system load in CompletableFuture<Double>
type by using the Asynchronous.Result.complete()
method.
Annotate the getSystemLoad()
method with the @Asynchronous
annotation to make it running asynchronously.
InventoryResource.java
link:finish/inventory/src/main/java/io/openliberty/guides/inventory/InventoryResource.java[role=include]
Replace theInventoryResource.java
file.inventory/src/main/java/io/openliberty/guides/inventory/InventoryResource.java
Create the PUT /systems/systemLoad
endpoint that multiply calls the task
bean’s getSystemLoad()
method to retrieve the system load of all systems in the inventory. When the calls return, the thenAcceptAsync()
method processes the returned data with the CompletableFuture<Double>
type and calls the system’s setSystemLoad()
method to store the system load. Exceptions are handled in a callback that is provided to the exceptionally()
method, which behaves like a catch block.
A CountDownLatch
object is used to track asynchronous requests. The countDown()
method is called whenever a request is complete. When the CountDownLatch
is at zero, it indicates that all asynchronous requests are complete. By using the await()
method of the CountDownLatch
, the program waits for all the asynchronous requests to be complete. When all asynchronous requests are complete, the program resumes execution with all required data processed.
Visit the http://localhost:9081/openapi/ui URL and make a PUT request to the /api/inventory/systems/systemLoad
endpoint with 5
seconds for the after
parameter. The request will take 5 seconds to complete. Visit the http://localhost:9081/api/inventory/systems URL to see that the systemLoad
values are updated to non-zero.
Although you can test your application manually, automated tests ensure consistent code quality by triggering a failure whenever a code change introduces a defect. In this section, you’ll create integration tests for the inventory
microservice.
Create a RESTful client interface for the inventory
microservice.
InventoryResourceClient.java
link:finish/inventory/src/test/java/it/io/openliberty/guides/inventory/InventoryResourceClient.java[role=include]
Create theInventoryResourceClient.java
file.inventory/src/test/java/it/io/openliberty/guides/inventory/InventoryResourceClient.java
This interface declares listContents()
, getSystem()
, addSystemClient()
, updateMemoryUsed()
, updateSystemLoad()
, removeSystem()
, and resetSystems()
methods for accessing each of the endpoints that are set up to access the inventory
microservice.
InventoryEndpointIT.java
link:finish/inventory/src/test/java/it/io/openliberty/guides/inventory/InventoryEndpointIT.java[role=include]
Create theInventoryEndpointIT.java
file.inventory/src/test/java/it/io/openliberty/guides/inventory/InventoryEndpointIT.java
The testAddSystems()
tests the POST /inventory/system/{hostname}
endpoint and confirms that three systems are added to the inventory.
The testUpdateMemoryUsed()
tests the PUT /inventory/systems/memoryUsed
endpoint and confirms that the memory usage of each system is greater than 0.0.
The testUpdateSystemLoad()
tests the PUT /inventory/systems/systemLoad
endpoint and confirms that the system load of each system is greater than 0.0.
The testResetSystems()
tests the PUT /inventory/systems/reset
endpoint and confirms that the memory usage and the system load of each system are 0.0.
The testRemoveSystem()
tests the DELETE /inventory/systems/{hostname}
endpoint and confirms that two systems remain in the inventory.
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 the inventory
microservice.
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: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 11.62 s -- in it.io.openliberty.guides.inventory.InventoryEndpointIT
Results:
Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
When you are done checking out the services, exit dev mode by pressing CTRL+C
in the command-line sessions where you ran the system
and inventory
microservices.
You just developed tasks that run parallelly or asynchronously in a Java microservice by using Jakarta Concurrency APIs in Open Liberty.