Saturday, July 1, 2017

JavaEE 7: Asynchronous RESTful API with Concurrency Utilities

Different web server creates a different number of (limited) HTTP I/O threads.  Some server itself implements a single thread I/O and supports asynchronous HTTP requests.  In real life, HTTP requests may take some time to execute their logic: for examples, sending email, batch process, and/or complex DB operations.  During this long process, it will be inefficient to hold the (limited) http I/O threads until the process ends.  We should try to release the I/O thread and let it handles other http requests.

Although Java SE5 provides an API support for concurrency via the java.util.concurrent package, there were no specific API that allowed enterprise developers to use concurrency utilities in a safe manner within the JavaEE container managed thread pools.

JavaEE 7 containers are now required to provide 4 types of managed objects that implement these interfaces. Applications look up managed objects via JNDI lookup or resource injection using @Resource.
  • ManagedExecutorService
  • ManagedScheduledExecutorService
  • ManagedThreadFactory
  • ContextService
On this post, I will use a ManagedExecutorService to create a Asynchronous HTTP request endpoint. This is an AccountService I used on an earlier post, but I added 1 second thread sleep for a demonstration purpose.

@Logging
@Stateless
public class AccountService {
   @PersistenceContext(name="demoDB")
   private EntityManager em;
   
   public Account updateBalance(Long id, Integer amountChange){
      Account acct = em.find(Account.class, id);
      try {
         Thread.sleep(1000);
      } catch (InterruptedException e) {
      }
      
      acct.setBalance(acct.getBalance() + amountChange);
      //Updating the updateTime field is omitted on purpose 
      return acct;
   }
}

Synchronous API
Again, this is the same web service I used on a previous post.

@Logging
@Path("/account")
public class AccountRestEnd {
   @Inject
   private Logger logger;
   
   @Inject
   private AccountService accountService;
   
   @POST
   @Path("/balance")
   @Produces(MediaType.APPLICATION_JSON)
   public Response changeBalance(@QueryParam("id") Long acctId,
         @DefaultValue("0") @QueryParam("amount") Integer amount){
      Account acct = null;
      try{
         acct = accountService.updateBalance(acctId, amount);
      }catch(Exception ex){
         return Response.status(Response.Status.CONFLICT).build();
      }
      Response rs = Response.ok(acct).build();
      logger.info("Finished a balance endpoint synchronously.");
      return rs;
   }
}

By triggering a POST request http://localhost:8080/demo/account/balance?id=1&amount=1 , we see this logs, which is created by the interceptor Logging explained on this post.  Every method call is started and finished in the order and the log shows that all methods are executed with a http-nio-8080-exec-2 thread.
[http-nio-8080-exec-2] demo.interceptor.LoggingInterceptor.logMethod  Entering demo.rest.endpoint.AccountRestEnd - changeBalance
[http-nio-8080-exec-2] demo.interceptor.LoggingInterceptor.logMethod  Entering demo.rest.service.AccountService - updateBalance
[http-nio-8080-exec-2] demo.interceptor.LoggingInterceptor.logMethod  Exiting  demo.rest.service.AccountService - updateBalance Execution Time: 1004ms
[http-nio-8080-exec-2] demo.rest.endpoint.AccountRestEnd.changeBalance Finished a balance endpoint synchronously.
[http-nio-8080-exec-2] demo.interceptor.LoggingInterceptor.logMethod  Exiting  demo.rest.endpoint.AccountRestEnd - changeBalance Execution Time: 1010ms

Asynchronous API
This Asynchronous API uses the ManagedExecutorService along with a AsyncResponse.  The Asynchronous method needs to be a void type method and the 'execute' method creates an asynchronous process.  To resume the http response, the 'resume' method of the AsyncResponse object.
@Logging
@Path("/accountasync")
public class AccountAsyncRestEnd {
   @Inject
   private Logger logger;
   
   @Inject
   private AccountService accountService;
   
   @Resource
   ManagedExecutorService mes;
   
   @POST
   @Path("/balance")
   public void changeBalanceAsync(@Suspended AsyncResponse ar, 
         @QueryParam("id") Long acctId,
         @DefaultValue("0") @QueryParam("amount") Integer amount){
      
      mes.execute(new Runnable() {
         @Override
         public void run() {
            Account acct = null;
            try{
               acct = accountService.updateBalance(acctId, amount);
            }catch(Exception ex){
               ar.resume(Response.status(Response.Status.CONFLICT).build());
            }
            Response rs = Response.ok(acct)
                  .encoding(MediaType.APPLICATION_JSON).build();
            ar.resume(rs);
         }
      });

      logger.info("Finished a balance endpoint Asynchronously.");
   }
}

Let's trigger a POST request http://localhost:8080/demo/accountasync/balance?id=1&amount=1 and see the log again.

[http-nio-8080-exec-38] demo.interceptor.LoggingInterceptor.logMethod  Entering demo.rest.endpoint.AccountAsyncRestEnd - changeBalanceAsync
[http-nio-8080-exec-38] demo.rest.endpoint.AccountAsyncRestEnd.changeBalanceAsync Finished a balance endpoint Asynchronously.
[http-nio-8080-exec-38] demo.interceptor.LoggingInterceptor.logMethod  Exiting  demo.rest.endpoint.AccountAsyncRestEnd - changeBalanceAsync Execution Time: 0ms
[managed-thread-4] demo.interceptor.LoggingInterceptor.logMethod  Entering demo.rest.service.AccountService - updateBalance
[managed-thread-4] demo.interceptor.LoggingInterceptor.logMethod  Exiting  demo.rest.service.AccountService - updateBalance Execution Time: 1005ms

The RESTful API endpoint is executed with a http-nio-8080-exec-38 thread and the changeBalanceAsync method is completed immediately.
Exiting  demo.rest.endpoint.AccountAsyncRestEnd - changeBalanceAsync Execution Time: 0ms
The business login in the updateBalance method is executed with a separate thread 'manaed-thread-4' and the result is returned 1005 ms later in this example.

No comments:

Post a Comment

Java 9: Flow - Reactive Programming

Programming world has always been changed fast enough and many programming / design paradigms have been introduced such as object oriented p...