Embracing non-blocking APIs

In the first section of this chapter, I claimed the superiority of the evented execution model over the threaded execution model, in the context of web servers. That being said, to be fair, the threaded model has an advantage over the evented model: it is simpler to program with. Indeed, in such a case, the framework is responsible for creating the threads and the JVM is responsible for scheduling the threads, so that you don't even have to think about this at all, yet your code is concurrently executed.

On the other hand, with the evented model, concurrency control is explicit and you should care about it. Indeed, the fact that the same execution thread is used to run several concurrent actions has an important implication on your code: it should not block the thread. Indeed, while the code of an action is executed, no other action code can be concurrently executed on the same thread.

What does blocking mean? It means holding a thread for too long a duration. It typically happens when you perform a heavy computation or wait for a remote response. However, we saw that these cases, especially waiting for remote responses, are very common in web servers, so how should you handle them? You have to wait in a non-blocking way or implement your heavy computations as incremental computations. In all the cases, you have to break down your code into computation fragments, where the execution is managed by the execution context. In the diagram illustrating the evented execution model, computation fragments are materialized by the rectangles. You can see that rectangles of different colors are interleaved; you can find rectangles of another color between two rectangles of the same color.

However, by default, the code you write forms a single block of execution instead of several computation fragments. It means that, by default, your code is executed sequentially; the rectangles are not interleaved! This is depicted in the following diagram:

Embracing non-blocking APIs

Evented execution model running blocking code

The previous figure still shows both the execution threads. The second one handles the blue action and then the purple infinite action, so that all the other actions can only be handled by the first execution context. This figure illustrates the fact that while the evented model can potentially be more efficient than the threaded model, it can also have negative consequences on the performances of your application: infinite actions block an execution thread forever and the sequential execution of actions can lead to much longer response times.

So, how can you break down your code into blocks that can be managed by an execution context? In Scala, you can do so by wrapping your code in a Future block:

Future {
  // This is a computation fragment
}

The Future API comes from the standard Scala library. For Java users, Play provides a convenient wrapper named play.libs.F.Promise:

Promise.promise(() -> { 
  // This is a computation fragment 
});

Such a block is a value of type Future[A] or, in Java, Promise<A> (where A is the type of the value computed by the block). We say that these blocks are asynchronous because they break the execution flow; you have no guarantee that the block will be sequentially executed before the following statement. When the block is effectively evaluated depends on the execution context implementation that manages it. The role of an execution context is to schedule the execution of computation fragments. In the figure showing the evented model, the execution context consists of a thread pool containing two threads (represented by the two lines under the rectangles).

Actually, each time you create an asynchronous value, you have to supply the execution context that will manage its evaluation. In Scala, this is usually achieved using an implicit parameter of type ExecutionContext. You can, for instance, use an execution context provided by Play that consists, by default, of a thread pool with one thread per processor:

import play.api.libs.concurrent.Execution.Implicits.defaultContext

In Java, this execution context is automatically used by default, but you can explicitly supply another one:

Promise.promise(() -> { ... }, myExecutionContext);

Now that you know how to create asynchronous values, you need to know how to manipulate them. For instance, a sequence of several Future blocks is concurrently executed; how do we define an asynchronous computation depending on another one?

You can eventually schedule a computation after an asynchronous value has been resolved using the foreach method:

val futureX = Future { 42 }
futureX.foreach(x => println(x))

In Java, you can perform the same operation using the onRedeem method:

Promise<Integer> futureX = Promise.promise(() -> 42);
futureX.onRedeem((x) -> System.out.println(x));

More interestingly, you can eventually transform an asynchronous value using the map method:

val futureIsEven = futureX.map(x => x % 2 == 0)

The map method exists in Java too:

Promise<Boolean> futureIsEven = futureX.map((x) -> x % 2 == 0);

If the function you use to transform an asynchronous value returned an asynchronous value too, you would end up with an inconvenient Future[Future[A]] value (or a Promise<Promise<A>> value, in Java). So, use the flatMap method in that case:

val futureIsEven = futureX.flatMap(x => Future { x % 2 == 0 })

The flatMap method is also available in Java:

Promise<Boolean> futureIsEven = futureX.flatMap((x) -> {
  Promise.promise(() -> x % 2 == 0)
});

The foreach, map, and flatMap functions (or their Java equivalent) all have in common to set a dependency between two asynchronous values; the computation they take as the parameter is always evaluated after the asynchronous computation they are applied to.

Another method that is worth mentioning is zip:

val futureXY: Future[(Int, Int)] = futureX.zip(futureY)

The zip method is also available in Java:

Promise<Tuple<Integer, Integer>> futureXY = futureX.zip(futureY);

The zip method returns an asynchronous value eventually resolved to a tuple containing the two resolved asynchronous values. It can be thought of as a way to join two asynchronous values without specifying any execution order between them.

Tip

If you want to join more than two asynchronous values, you can use the zip method several times (for example, futureX.zip(futureY).zip(futureZ).zip(…)), but an alternative is to use the Future.sequence function:

val futureXs: Future[Seq[Int]] =
  Future.sequence(Seq(futureX, futureY, futureZ, …))

This function transforms a sequence of future values into a future sequence value. In Java, this function is named Promise.sequence.

In the preceding descriptions, I always used the word eventually, and it has a reason. Indeed, if we use an asynchronous value to manipulate a result sent by a remote machine (such as a database system or a web service), the communication may eventually fail due to some technical issue (for example, if the network is down). For this reason, asynchronous values have error recovery methods; for example, the recover method:

futureX.recover { case NonFatal(e) => y }

The recover method is also available in Java:

futureX.recover((throwable) -> y);

The previous code resolves futureX to the value of y in the case of an error.

Libraries performing remote calls (such as an HTTP client or a database client) return such asynchronous values when they are implemented in a non-blocking way. You should always be careful whether the libraries you use are blocking or not and keep in mind that, by default, Play is tuned to be efficient with non-blocking APIs.

Note

It is worth noting that JDBC is blocking. It means that the majority of Java-based libraries for database communication are blocking.

Obviously, once you get a value of type Future[A] (or Promise<A>, in Java), there is no way to get the A value unless you wait (and block) for the value to be resolved. We saw that the map and flatMap methods make it possible to manipulate the future A value, but you still end up with a Future[SomethingElse] value (or a Promise<SomethingElse>, in Java). It means that if your action's code calls an asynchronous API, it will end up with a Future[Result] value rather than a Result value. In that case, you have to use Action.async instead of Action, as illustrated in this typical code example:

val asynchronousAction = Action.async { implicit request =>
  service.asynchronousComputation().map(result => Ok(result))
}

In Java, there is nothing special to do; simply make your method return a Promise<Result> object:

public static Promise<Result> asynchronousAction() {
  service.asynchronousComputation().map((result) -> ok(result));
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.189.228