Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to rewrite synchronous controller to be asynchronous in Play?

I'm using Play framework 2.2 for one of my upcoming web application. I have implemented my controllers in synchronous pattern, with several blocking calls (mainly, database).

For example,

Synchronous version:

public static Result index(){
  User user = db.getUser(email); // blocking
  User anotherUser = db.getUser(emailTwo); // blocking
  ...
  user.sendEmail(); // call to a webservice, blocking.
  return ok();
}

So, while optimising the code, decided to make use of Asynchronous programming support of Play. Gone through the documentation, but the idea is still vague to me, as I'm confused about how to properly convert the above synchronous block of code to Async.

So, I came up with below code:

Asynchronous version:

public static Promise<Result> index(){
  return Promise.promise(
    new Function0<Result>(){
      public Result apply(){
        User user = db.getUser(email); // blocking
        User anotherUser = db.getUser(emailTwo); // blocking
        ...
        user.sendEmail(); // call to a webservice, blocking.
        return ok();
      }
    }
  );
}

So, I just wrapped the entire control logic inside a promise block.

  1. Is my approach correct?
  2. Should I convert each and every blocking request inside the controller, as Asynchronous, or wrapping several blocking calls inside single Async block is enough?
like image 478
Veera Avatar asked Mar 15 '14 08:03

Veera


2 Answers

The play framework is asynchronous by nature and it allows the creation of fully non-blocking code. But in order to be non-blocking - with all its benefits - you can't just wrap your blocking code and expect magic to happen...

In an ideal scenario, your complete application is written in a non-blocking manner. If this is not possible (for whatever reason), you might want to abstract your blocking code in Akka actors or behind async interfaces which return scala.concurrent.Future's. This way you can execute your blocking code (simultaneously) in a dedicated Execution Context, without impacting other actions. After all, having all your actions share the same ExecutionContext means they share the same Thread pool. So an Action that blocks Threads might drastically impact other Actions doing pure CPU while having CPU not fully utilized!

In your case, you probably want to start at the lowest level. It looks like the database calls are blocking so start by refactoring these first. You either need to find an asynchronous driver for whatever database you are using or if there is only a blocking driver available, you should wrap them in a future to execute using a DB-specific execution context (with a ThreadPool that's the same size as the DB ConnectionPool).

Another advantage of abstracting the DB calls behind an async interface is that, if at some point in the future, you switch to a non-blocking driver, you can just change the implementation of your interface without having to change your controllers!

In your re-active controller, you can then handle these futures and work with them (when they complete). You can find more about working with Futures here

Here's a simplified example of your controller method doing non-blocking calls, and then combining the results in your view, while sending an email asynchronous:

public static Promise<Result> index(){
    scala.concurrent.Future<User> user = db.getUser(email); // non-blocking
    scala.concurrent.Future<User> anotherUser = db.getUser(emailTwo); // non-blocking

    List<scala.concurrent.Future<User>> listOfUserFutures = new ArrayList<>();
    listOfUserFutures.add(user);
    listOfUserFutures.add(anotherUser);
    final ExecutionContext dbExecutionContext = Akka.system().dispatchers().lookup("dbExecutionContext");
    scala.concurrent.Future<Iterable<User>> futureListOfUsers = akka.dispatch.Futures.sequence(listOfUserFutures, dbExecutionContext);  

    final ExecutionContext mailExecutionContext = Akka.system().dispatchers().lookup("mailExecutionContext");
    user.andThen(new OnComplete<User>() {
        public void onComplete(Throwable failure, User user) {
             user.sendEmail(); // call to a webservice, non-blocking.       
        }
    }, mailExecutionContext);

    return Promise.wrap(futureListOfUsers.flatMap(new Mapper<Iterable<User>, Future<Result>>() {
        public Future<Result> apply(final Iterable<User> users) {
            return Futures.future(new Callable<Result>() {
                public Result call() {              
                    return ok(...);
                }
            }, Akka.system().dispatcher());
        }
    }, ec));
}
like image 186
stikkos Avatar answered Sep 21 '22 06:09

stikkos


It you don't have anything to not block on then there may not be a reason to make your controller async. Here is a good blog about this from one of the creators of Play: http://sadache.tumblr.com/post/42351000773/async-reactive-nonblocking-threads-futures-executioncont

like image 31
James Ward Avatar answered Sep 21 '22 06:09

James Ward