Last Updated: February 25, 2016
·
741
· sheaney

Evented NIO with Play

This article will briefly discuss in a very high level why you would consider choosing an evented model of programming with non blocking IO to handle calls to external web services.

There's a very good discussion on evented vs. threaded web servers here.

Node.js is an example of an evented NIO model of programming where it is common to see code like this:

var http = require('http');
var fs = require('fs');
var index = fs.readFileSync('index.html');

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end(index);
}).listen(3000);

console.log("Executing before the server starts up")

In this example, the server listens on port 3000 for incoming requests and does so by not blocking the main thread of execution, which allows the last statement to print to console.

A simple Node.js process can handle many requests concurrently by employing non blocking IO which frees up resources. However it is not truly parallel because Javascript code will only be executed by a single thread at any given moment. MRI Ruby has a similar restriction by employing a Global Interpreter Lock per Ruby process, which like Javascript, hinders it from achieving parallelism within a process.

For a further discussion of the differences between concurrency and parallelism, watch Rob Pike's talk.

Rails with Unicorn is an example of a threaded synchronous web server which employs a worker model with blocking IO. Using Unicorn to process many potentially time consuming web service calls is not a scalable approach, as workers will become bottlenecked with slow clients.

One of the main advantages of using Scala is that it runs on the JVM and has access to all the libraries and frameworks that are implemented in Java.

One example of this is Java's NIO Netty library. Play is a popular Java/Scala MVC web framework which uses this library to implement an evented web server providing an interesting solution for applications with heavy external web service calls and/or CPU intensive applications because of the underlying JVM's manner of handling parallelism and concurrency.

Play combined with features from Scala provides some nice features for writing highly concurrent and parallel code. For example, you might write code like this:

def fanOut = Action.async {
  val svcFuture1 = WS.get("https://my.service1")
  val svcFuture2 = WS.get("https://my.service2")
  val svcFuture3 = WS.get("https://my.service3")

  val infoFuture =
    for {
      svc1 ← svcFuture1
      svc2 ← svcFuture2
      svc3 ← svcFuture3
    } yield Ok(presentInfo(svc1, svc2, svc3))

  val resultFuture =
    infoFuture recover {
      case e: Exception =>
        InternalServerError(e.getMessage)
    }

  println("I will likely print before controller replies to client")

  resultFuture
}

which simulates a Play controller action called fanOut that will receive a request and call three external web services. Once all services complete and return a response, the responses will be processed and a 200 OK response will be returned or if any service fails, than a 500 InternalServerError response with the exception message from the first service that failed will be returned to the client. All web service requests will happen in parallel and in a non blocking fashion, where the main thread gets freed up to process any other incoming requests to the server.

Play avoids many of the warts of working with asynchronous code such as callbacks causing the well known callback hell prevalent in many other languages. Scala does this by making use of important functional concepts such as monadic future composition in this example.

Conclusion

This type of model allows Play to provide a scalable approach to implementing an application that can handle many external requests in an efficient manner, while keeping most of the complexity out of your code.

While considering to adopt a new language can be a difficult decision as is the case with Scala, where there exists a heavy learning curve, it is worth examining if your application could benefit from considering a different paradigm of programming that may be better suited to your problem's domain.

To read more about Play's philosophy visit this link.

References