I’m a C developer who needs to create a basic async REST server (synchronous version works fine) in Java using the Microprofile (Helidon MP) framework. Here is my strategy to do this:
The client should do the POST call and provide JSON objects that the POST endpoint will receive. Then, the POST Endpoint method will call a business logic that should do stuff with the received JSON objects. This logic must be run asynchronously. POST should immediately return 202 Accepted. The client should check for async task completion status using a GET request (simple pooling style).
Should POST return a URI that the GET call will use? How? This GET should also provide the percentage of the task completion if the task is in progress. Finally, if the business logic is done, the GET should return the result.
I have a little previous experience with async Java, but no experience with async in this Microprofile/Java EE/Jakarta or whatever it is. I tried several different approaches (AsyncResponse, CompletitionStatus, etc.) to write this code (async POST Method) but nothing seems to be working. The skeleton of the POST functions looks like this:
@Path("/logic") @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response logic(JsonObject jsonObject){ BusinessLogic bl = new BusinessLogic(); // This should be done asynchronously. //How to do it correctly in Microprofile? bl.doSomethingWithInput(jsonObject); return Response.status(Response.Status.ACCEPTED).build(); //Return ACCEPTED and the task URI??? }
The GET Handler:
@Path("/logicstatus") @GET @@Produces(MediaType.APPLICATION_JSON) public Response logicStatus(URI as query param???) { // Magic here? retrun Response.status(Response.Status.OK).entity(resultJson).build(); }
Thanks a lot. Java reminds me how I love writing device drivers in C :D.
Advertisement
Answer
So, first things first. To run things asynchronously Jakarta EE has the annotation @Asynchronous
. So, create a CDI bean with @RequestScoped
, and put there your business method annotated with @Asynchronous
.
Add @ApplicationScoped
CDI annotation to your JAXRS service so you can inject ypur business bean.
Then, for monitoring the task, you have a couple of possibilities.
If you keep the state in a database you just make the
@GET
method to check the state in the database. But I asume this is not your case.If the first approach doesn’t fit then you have to keep the state of the different inProgress tasks somehow. I would have another
@ApplicationScoped
CDI bean for that. This would contain aConcurrentHashMap
, with aUUID
as the key, and as the value an Object of your own that contains the current state of any specific async job.
The UUID
has to be generated in the @POST
method, and sent as a parameter to the @Asynchronous
, method. This way the @POST
method can return the UUID
, which will be used then in the @GET
to request the state of the task (querying the @ApplicationScoped
bean).
So the application-wide async tasks state holder should be something like this
@ApplocationScoped public class AsyncTasksStateHolder { private Map<UUID, MyCustomState> tasksState = new ConcurrentHashMap<>(); // here go methods to add and get from the map }
Then, the async business logic may be something like:
@RequestScoped public class AsyncTaskExecutor { @Inject AsyncTasksStateHolder asyncTasksStateHolder; @Asynchronous public void doAsyncStuff(UUID uuid, JsonObject jsonObject) { // create the initial state and save it to asyncTasksStateHolder // Do your dirty deeds // Keep updatinf the state in asyncTasksStateHolder } }
And now the JAX-RS part:
@Inject AsyncTasksStateHolder asyncTasksStateHolder @Inject AsyncTaskExecutor asyncTasksExecutor; @Path("/logic") @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.TEXT_PLAIN) public Response logic(JsonObject jsonObject){ UUID uuid = UUID.randomUUID(); asyncTasksExecutor.doAsyncStuff(uuid, jsonObiect); // this returns immediately because of the @Asynchronous return Response .status(Response.Status.OK) .entity(uuid.toString()) .build(); } @Path("/logicstatus/{uuid}") @GET @Produces(MediaType.APPLICATION_JSON) public Response logicStatus(@PathParam("uuid") String uuidAsString) { // And here you just query the asyncTasksStateHolder // with the UUID to retreive the state. }
Some thing to note here:
You will need a mechanism to remove stale state, or the map will grow indefinately.
I used
PLAIN_TEXT
for simplocity, vut feel freento model the@POST
response as you please.Depending on your MicroProfile version you will nees to create the
beans.xml
so CDI gets activated.
And I think that’s all. Any problems or doubts do not hesitate to write a comment.