10
votes

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?
2
why did the title get edited??? I think the original title 'How to properly implement Async programming in Play framework?' was better since it isn't just about changing the controller but about implementing a complete non-blocking application flow. Just re-writing a synchronous controller will not make it asynchronous!stikkos
Is there any way that user.sendEmail() could be made non-blocking?James Ward
depends on what you use to send email, if you use for example Amazon Simple Email Service, you could use the AWS Java SDK which supports asynchronous emails. Or you can use the JavaMail API as described on docs.oracle.com/javaee/6/tutorial/doc/gkiez.html Do you think the client should be blocked until the email is send?stikkos

2 Answers

7
votes

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));
}
2
votes

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