How to rewrite synchronous controller to be asynchronous in Play?

后端 未结 2 583
刺人心
刺人心 2021-02-14 08:55

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).

2条回答
  •  北恋
    北恋 (楼主)
    2021-02-14 09:53

    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 index(){
        scala.concurrent.Future user = db.getUser(email); // non-blocking
        scala.concurrent.Future anotherUser = db.getUser(emailTwo); // non-blocking
    
        List> listOfUserFutures = new ArrayList<>();
        listOfUserFutures.add(user);
        listOfUserFutures.add(anotherUser);
        final ExecutionContext dbExecutionContext = Akka.system().dispatchers().lookup("dbExecutionContext");
        scala.concurrent.Future> futureListOfUsers = akka.dispatch.Futures.sequence(listOfUserFutures, dbExecutionContext);  
    
        final ExecutionContext mailExecutionContext = Akka.system().dispatchers().lookup("mailExecutionContext");
        user.andThen(new OnComplete() {
            public void onComplete(Throwable failure, User user) {
                 user.sendEmail(); // call to a webservice, non-blocking.       
            }
        }, mailExecutionContext);
    
        return Promise.wrap(futureListOfUsers.flatMap(new Mapper, Future>() {
            public Future apply(final Iterable users) {
                return Futures.future(new Callable() {
                    public Result call() {              
                        return ok(...);
                    }
                }, Akka.system().dispatcher());
            }
        }, ec));
    }
    

提交回复
热议问题