Asynchronous processing
There are common scenarios in enterprise applications that are usually synchronous and blocking, for example, database or remote server calls. In those cases, the call at these methods takes some time to return, usually with a client thread that is blocked waiting for the answer. Let's consider a simple example—a Java EE servlet that queries a database, using a service, and prints the list of the records, in our case a list of authors (yes, I know, I'm a little bit egocentric):
@WebServlet(urlPatterns = "/authors")
public class AuthorServlet extends HttpServlet {
...
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
final List<Author> authors = authorService.getAuthors();
final ServletOutputStream out = resp.getOutputStream();
for (Author author : authors) {
out.println(user.toString());
}
}
}
You must wait for the invocation of the database query, implemented in authorService.getAuthors(), to be completed to get the response. Therefore, the client stays blocked until the request is processed.
Let's try to make the servlet asynchronous:
@WebServlet(urlPatterns = "/authors", asyncSuppported = true)
public class AuthorServlet extends HttpServlet {
...
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
final AsyncContext asyncCtx = req.startAsync();
asyncCtx.start(() -> {
final List<Author> authors = authorService.getAuthors();
printAuthors(authors, asyncCtx.getResponse().getOutputStream());
asyncCtx.complete();
});
}
private void printAuthors (List<Author> authors, ServletOutputStream out) {
for (Author author : authors) {
out.println(author.toString());
}
}
}
With a simple update, we made our servlet asynchronous, freeing our client to perform other operations while waiting to receive the answer to the request that was made.
Well, you could argue that nobody writes servlets nowadays. But, are you sure about that?
In an MSA, the standard communication is based on a RESTful web service that, in the Java EE world, is implemented by JAX-RS—these specifications are built on top of servlet specifications.
Then, we can revisit our simple servlet in a RESTful way:
@Path("/authors")
public class AuthorResource {
...
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getAuthors() {
final List<Author> authors = authorService.getAuthors();
return Response.ok(authors).build();
}
}
Now, we can make it asynchronous:
@Path("/authors")
public class AuthorResource {
...
@GET
@Produces(MediaType.APPLICATION_JSON)
@Asynchronous
public void getAuthors(@Suspended AsyncResponse response) {
final List<Author> authors = authorService.getAuthors();
response.resume(Response.ok(authors).build());
}
}
An annotation, @Asynchronous, and a Context Dependency Injection (CDI) method parameter, @Suspended AsyncResponse response, are used to make it asynchronous. A quick win!
But remember, never wait forever and never couple your application with the issues of other services. Therefore, always set a timeout to abort the invocation in case of an unrecoverable issue.
This is the final version of our easy example:
@Path("/authors")
public class AuthorResource {
...
@GET
@Produces(MediaType.APPLICATION_JSON)
@Asynchronous
public void getAuthors(@Suspended AsyncResponse response) {
response.setTimeout(2, TimeUnit.SECONDS);
response.setTimeoutHandler(resp ->
resp.resume(Response.status(Status.REQUEST_TIMEOUT).build()));
final List<Author> authors = authorService.getAuthors();
response.resume(Response.ok(authors).build());
}
}
This approach is very useful if your application contains a mix of heavy- and soft-loaded queries. In this way, you can obtain a higher throughput and have the opportunity to fine-tune your application server.
We have spoken about how to expose our API asynchronously. But what should you do to obtain the same results in the business logic layer?
We can implement it using EJBs. Some people consider EJBs to be the devil: I disagree.
This negative impression was born due to the complex and poor performance implementations related to version 2.1. The new ones, from version 3.0 to version 3.2, simplify the use and the configuration, and enrich the platform with a great number of new features.
I consider exposing your service using EJB remote as deprecating. You should never communicate via EJB remote, as it is a strong coupling and poor performance way to do this. However, the EJB local can be used outside of a Java EE container because they are only POJO, simplify some core topics such as the handling of thread safety, the concurrency, the transaction life cycle, and so on. I think that an improvement in Jakarta EE that can be implemented in the next feature would be the deletion of EJB remote from the specifications.
Now, let's get back to our business layer implementation.
This is the classic synchronous implementation:
@Stateless
public class AuthorService {
public Author createAuthor(final String name, final String surname) {
final Author author = new Author(UUID.randomUUID(), name, surname);
em.persist(author);
return author;
}
}
Let's make it asynchronous:
@Stateless
public class AuthorService {
@Asynchronous
public Future<Author> createAuthor(final String name, final String surname) {
final Author author = new Author(UUID.randomUUID(), name, surname);
em.persist(author);
return new AsyncResult<>(author);
}
}
This is easy—tell the container to make the invocation asynchronous, using the @Asynchronous annotation, and change the return type from classic POJO to Future<T>, which is implemented by AsyncResult.
After doing that, we can implement our RESTful class to use the new feature:
@Path("/authors")
public class AuthorResource {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
public Response createAuthor(@FormParam("name") final String name, @FormParam("surname")
final String surname) {
final Future<Author> authorFuture = authorService.createAuthor(name, surname);
try {
final Author author = authorFuture.get(2, TimeUnit.SECONDS);
return Response.ok(author).build();
} catch (InterruptedException | ExecutionException | TimeoutException e) {
return Response.serverError().build();
}
}
}
However, we can do this in a better way by using the CompletableFuture class, which was introduced in Java SE 8:
@Stateless
public class AuthorService {
@Asynchronous
public void createAuthor(final String name, final String surname, final
CompletableFuture<Author> promise) {
final Author author = new Author(UUID.randomUUID(), name, surname);
em.persist(author);
promise.complete(author);
}
}
Instead of returning a Future, we made our method void and told the Java EE container to inject a CompletableFuture that will be responsible for notifying the end of the operation.
Now, we can revisit the RESTful implementation:
@Path("/authors")
public class AuthorResource {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
@Asynchronous
public void createAuthor(@FormParam("name") final String name, @FormParam("surname") final
String surname, @Suspended AsyncResponse response) {
CompletableFuture<Author> promise = new CompletableFuture<>();
authorService.createAuthor(name, surname, promise);
promise.thenApply(response::resume);
}
}
I prefer this approach instead of the use of Future. Future is, at the moment, the only solution to implement asynchronicity in Java EE, but it has some limitations compared to CompletableFuture; for example, it is not able to build and use the new lambdas feature.