play framework: async i/o with java and scala

184
Asynchronous I/O with Java and Scala

Post on 17-Oct-2014

21.286 views

Category:

Technology


4 download

DESCRIPTION

This talk is a brief introduction to writing asynchronous code with the Play Framework.

TRANSCRIPT

Page 1: Play Framework: async I/O with Java and Scala

Asynchronous I/Owith Java and Scala

Page 2: Play Framework: async I/O with Java and Scala

LinkedIn uses a service oriented architecture (SOA)

Page 3: Play Framework: async I/O with Java and Scala

Hundreds of different types of services, thousands of instances in multiple data centers.

Internet Load Balancer

Profile frontend

Company frontend

Recruiter frontend

Profile backend

Search backend

Company backend

Recruiter backend

Ads backend

Data Store

Data Store

Data Store

Data Store

Page 4: Play Framework: async I/O with Java and Scala

Services communicate with each other via remote calls

Page 5: Play Framework: async I/O with Java and Scala

Profile frontend Profile backend/profile/123

HTTP request

Page 6: Play Framework: async I/O with Java and Scala

Profile frontend Profile backend

JSON response

{ "id": 123, "first": "Yevgeniy", "last": "Brikman"}

Page 7: Play Framework: async I/O with Java and Scala

Most people are used to synchronous I/O when making requests between servers

Page 8: Play Framework: async I/O with Java and Scala

The most popular frameworks typically use one-thread-per-request and blocking I/O

Page 9: Play Framework: async I/O with Java and Scala

executeMethod blocks the thread until the response comes back

void doGet(HttpServletRequest req, HttpServletResponse res) { // Apache HttpClient HttpClient client = new HttpClient(); GetMethod method = new GetMethod("www.example.com"); // executeMethod is a blocking, synchronous call int statusCode = client.executeMethod(method); System.out.println("Response " + statusCode);}

MyServlet.java

Page 10: Play Framework: async I/O with Java and Scala

Evented servers have one thread/process per CPU core and use non-blocking I/O

Page 11: Play Framework: async I/O with Java and Scala

http.request is a non-blocking call: the next line executes before the response comes back

MyNodeApp.js

var callback = function(data) { console.log("Response: " + data);};

var options = { hostname: 'www.google.com', path: '/upload'}; // Non-blocking HTTP callhttp.request(options, callback); console.log('This line may execute before the callback!');

Page 12: Play Framework: async I/O with Java and Scala

Why threaded vs. evented matters for LinkedIn

Page 13: Play Framework: async I/O with Java and Scala

void doGet(HttpServletRequest req, HttpServletResponse res) { // Call a number of backend services to get data Profile profile = profileSvc.getProfile(); Company company = companySvc.getCompany(); Skills skills = skillsSvc.getSkills();}

MyServlet.java

Our services spend most of their time waiting for data from other services and data stores

Page 14: Play Framework: async I/O with Java and Scala

I/O is very expensivehttp://www.eecs.berkeley.edu/~rcs/research/interactive_latency.html

Page 15: Play Framework: async I/O with Java and Scala

In a threaded server, threads spend most of the time idle, waiting on I/O

Page 16: Play Framework: async I/O with Java and Scala

Threading dilemma

1. Creating new threads on the fly is expensive: a. Use a thread pool

2. Too many threads in the thread pool: a. Memory overheadb. Context switching overhead

3. Too few threads in the thread pool: a. Run out of threads, latency goes upb. Sensitive to downstream latency!

Page 17: Play Framework: async I/O with Java and Scala

Internet Load Balancer

Frontend Server

Frontend Server

Frontend Server

Backend Server

Backend Server

Backend Server

Backend Server

Backend Server

Data Store

Data Store

Data Store

Data Store

Let's say latency goes up a little here

Page 18: Play Framework: async I/O with Java and Scala

Internet Load Balancer

Frontend Server

Frontend Server

Frontend Server

Backend Server

Backend Server

Backend Server

Backend Server

Backend Server

Data Store

Data Store

Data Store

Data Store

Causes threads to get backed up here

Page 19: Play Framework: async I/O with Java and Scala

Internet Load Balancer

Frontend Server

Frontend Server

Frontend Server

Backend Server

Backend Server

Backend Server

Backend Server

Backend Server

Data Store

Data Store

Data Store

Data Store

Latency goes up

Page 20: Play Framework: async I/O with Java and Scala

Internet Load Balancer

Frontend Server

Frontend Server

Frontend Server

Backend Server

Backend Server

Backend Server

Backend Server

Backend Server

Data Store

Data Store

Data Store

Data Store

Now threads get backed up here

Page 21: Play Framework: async I/O with Java and Scala

Internet Load Balancer

Frontend Server

Frontend Server

Frontend Server

Backend Server

Backend Server

Backend Server

Backend Server

Backend Server

Data Store

Data Store

Data Store

Data Store

And here

Page 22: Play Framework: async I/O with Java and Scala

Internet Load Balancer

Frontend Server

Frontend Server

Frontend Server

Backend Server

Backend Server

Backend Server

Backend Server

Backend Server

Data Store

Data Store

Data Store

Data Store

Here too

Page 23: Play Framework: async I/O with Java and Scala

Internet Load Balancer

Frontend Server

Frontend Server

Frontend Server

Backend Server

Backend Server

Backend Server

Backend Server

Backend Server

Data Store

Data Store

Data Store

Data Store

And there

Page 24: Play Framework: async I/O with Java and Scala

Internet Load Balancer

Frontend Server

Frontend Server

Frontend Server

Backend Server

Backend Server

Backend Server

Backend Server

Backend Server

Data Store

Data Store

Data Store

Data Store

And... the site is down.

Page 25: Play Framework: async I/O with Java and Scala

This is thread pool hell

Page 26: Play Framework: async I/O with Java and Scala

Play is built on top of Netty, so it supports non-blocking I/O

Page 27: Play Framework: async I/O with Java and Scala

NIO benefits

1. No sensitivity to downstream slowness

2. Easy to parallelize I/O

3. Supports many concurrent and long-running connections, enabling:a. WebSocketsb. Cometc. Server-Sent Events

Page 28: Play Framework: async I/O with Java and Scala

This talk is a brief introduction to writing asynchronous code with the Play Framework.

Page 29: Play Framework: async I/O with Java and Scala

For each section, I will include simplified examples: first in Java and then Scala.

Page 30: Play Framework: async I/O with Java and Scala

The world’s largest professional network

Page 31: Play Framework: async I/O with Java and Scala

at

We've been using Play in production for more than 6 months

Page 32: Play Framework: async I/O with Java and Scala

A few apps built on Play

Page 33: Play Framework: async I/O with Java and Scala

Channels (frontend)

Page 34: Play Framework: async I/O with Java and Scala

Premium Subscriptions (frontend)

Page 35: Play Framework: async I/O with Java and Scala

Polls (frontend + backend)

Page 36: Play Framework: async I/O with Java and Scala

REST search (internal tool)

Page 37: Play Framework: async I/O with Java and Scala

About me

Leading the Play project as part of LinkedIn's Service Infrastructure Team. Also: hackdays, engineering blog, incubator, open source.

Page 38: Play Framework: async I/O with Java and Scala

Outline1. A quick intro to Play

2. Basic async code

3. map and flatMap

4. Parallel and sequential

5. Errors and timeouts

6. Coming soon

Page 39: Play Framework: async I/O with Java and Scala

Outline1. A quick intro to Play2. Basic async code

3. map and flatMap

4. Parallel and sequential

5. Errors and timeouts

6. Coming soon

Page 40: Play Framework: async I/O with Java and Scala

Download and install Play fromhttp://www.playframework.com

Page 41: Play Framework: async I/O with Java and Scala

> play new my-app

Page 42: Play Framework: async I/O with Java and Scala

> play idea> play eclipse

Page 43: Play Framework: async I/O with Java and Scala

> play run

Page 44: Play Framework: async I/O with Java and Scala

http://localhost:9000

Page 45: Play Framework: async I/O with Java and Scala

Application layout

app → Application sources └ assets → Compiled asset sources └ controllers → Application controllers └ models → Application business layer └ views → Templatesconf → Configurations files └ application.conf → Main configuration file └ routes → Routes definitionpublic → Public assets └ stylesheets → CSS files └ javascripts → Javascript files └ images → Image filesproject → sbt configuration files └ Build.scala → Application build script └ plugins.sbt → sbt pluginslib → Unmanaged libraries dependencieslogs → Standard logs foldertarget → Generated stufftest → Unit or functional tests

Page 46: Play Framework: async I/O with Java and Scala

Let's get a feel for Play by creating a Java Controller

Page 47: Play Framework: async I/O with Java and Scala

public class HelloWorld extends Controller {

public static Result index() { return ok("Hello World"); } }

Controllers are Java classes with methods that return a Result, such as a 200 OK

app/controllers/HelloWorld.java

Page 48: Play Framework: async I/O with Java and Scala

Don't worry about the use of static. Yes, Play supports IOC. Using static (and other shortcuts) lets me keep the examples simple.

Page 49: Play Framework: async I/O with Java and Scala

GET /hello controllers.HelloWorld.index()

Expose the controller/action at a URL

conf/routes

Page 50: Play Framework: async I/O with Java and Scala

http://localhost:9000/hello

Page 51: Play Framework: async I/O with Java and Scala

Woohoo, hot reload!

Page 52: Play Framework: async I/O with Java and Scala

public class HelloWorld extends Controller {

public static Result index(String name) { return ok("Hello " + name); } }

Add a parameter

app/controllers/HelloWorld.java

Page 53: Play Framework: async I/O with Java and Scala

GET /hello controllers.HelloWorld.index( name)

Read the parameter from the query string

conf/routes

Page 54: Play Framework: async I/O with Java and Scala

http://localhost:9000/hello?name=Jim

Page 55: Play Framework: async I/O with Java and Scala

GET /hello/:name controllers.HelloWorld.index(name)

Read the parameter from the URL instead

conf/routes

Page 56: Play Framework: async I/O with Java and Scala

http://localhost:9000/hello/Jim

Page 57: Play Framework: async I/O with Java and Scala

public class HelloWorld extends Controller {

public static Result index(String name, int age) { return ok("Hello " + name + " you are " + age + " years old"); } }

Add another parameter, this time an int

app/controllers/HelloWorld.java

Page 58: Play Framework: async I/O with Java and Scala

GET /hello/:name/:age controllers.HelloWorld.index(name: String, age: Int)

Add the parameter. Note the type checking!

conf/routes

Page 59: Play Framework: async I/O with Java and Scala

http://localhost:9000/hello/Jim/28

Page 60: Play Framework: async I/O with Java and Scala

@(name: String, age: Int)

<html> <head></head> <body> <img src="/assets/images/play-logo.png"/> <p> Hello <b>@name</b>, you are <b>@age</b> years old </p> </body></html>

Add a view

app/views/hello.scala.html

Page 61: Play Framework: async I/O with Java and Scala

public class HelloWorld extends Controller {

public static Result index(String name, int age) { return ok(views.html.hello.render(name, age)); } }

Render the view from the controller

app/controllers/HelloWorld.java

Page 62: Play Framework: async I/O with Java and Scala

http://localhost:9000/hello/Jim/28

Page 63: Play Framework: async I/O with Java and Scala

Play also natively supports Scala

Page 64: Play Framework: async I/O with Java and Scala

app/controllers/HelloWorldScala.scala

Just add a .scala file under /app and Play will compile it

object HelloWorldScala extends Controller {

def index = Action { Ok("Hello World Scala") }}

Page 65: Play Framework: async I/O with Java and Scala

GET /scala controllers.HelloWorldScala.index()

Add it to the routes file as usual

conf/routes

Page 67: Play Framework: async I/O with Java and Scala

Outline1. A quick intro to Play

2. Basic async code3. map and flatMap

4. Parallel and sequential

5. Errors and timeouts

6. Coming soon

Page 68: Play Framework: async I/O with Java and Scala

Let's use Play's Web Services library (WS) to make some non-blocking HTTP calls

Page 69: Play Framework: async I/O with Java and Scala

public class Proxy extends Controller {

public static Result index(String url) { // Non blocking HTTP call Promise<Response> responsePromise = WS.url(url).get(); // How do we turn a Promise into a Play Result? }}

app/controllers/Proxy.java

Create a new controller and use WS to make an HTTP GET

Page 70: Play Framework: async I/O with Java and Scala

A Promise<T> will eventually contain the value T (or an error)

Page 71: Play Framework: async I/O with Java and Scala

(Play Framework source code)

Play has a built-in subclass of Result called AsyncResult that takes a Promise<Result>

public static class AsyncResult implements Result { private final Promise<Result> promise; public AsyncResult(Promise<Result> promise) { this.promise = promise; }}

Page 72: Play Framework: async I/O with Java and Scala

public class Proxy extends Controller { public static Result index(String url) { Promise<Response> response = WS.url(url).get();

// Transform asynchronously into a Play Result Promise<Result> result = response.map(toResult);

return async(result); }

// A function that can transform a Response into a Result private static Function<Response, Result> toResult = new Function<Response, Result>() { public Result apply(Response response) { return ok(response.getBody()).as(("text/html"); } };}

app/controllers/Proxy.java

We can use the map method to turn a Promise<Response> into a Promise<Result>

Page 73: Play Framework: async I/O with Java and Scala

GET /proxy controllers.Proxy.index(url)

Add this endpoint to the routes file

conf/routes

Page 75: Play Framework: async I/O with Java and Scala

We just built a completely non-blocking proxy!

Page 76: Play Framework: async I/O with Java and Scala

public class Proxy extends Controller { public static Result index(String url) { Logger.info("Before the HTTP call"); Promise<Response> response = WS.url(url).get(); Promise<Result> result = response.map(toResult); Logger.info("After the HTTP call");

return async(result); }

private static Function<Response, Result> toResult = new Function<Response, Result>() { public Result apply(Response response) { Logger.info("Inside the toResult function"); return ok(response.getBody()).as("text/html"); } };}

app/controllers/Proxy.java

To see that it's non-blocking, add logging

Page 77: Play Framework: async I/O with Java and Scala

Refresh the page and the logs show the HTTP call really is non-blocking

Page 78: Play Framework: async I/O with Java and Scala

Let's create the same Proxy in Scala

Page 79: Play Framework: async I/O with Java and Scala

object ProxyScala extends Controller {

def index(url: String) = Action {

val future: Future[Response] = WS.url(url).get()

// How do we turn a Future into a Play Result?

}

}

app/controllers/ProxyScala.scala

Create a new controller and use WS to make an HTTP GET

Page 80: Play Framework: async I/O with Java and Scala

A Future[T] will eventually contain the value T (or an error)

Page 81: Play Framework: async I/O with Java and Scala

(Play Framework source code)

Play has a built-in subclass of Result called AsyncResult that takes a Future<Result>

case class AsyncResult(result: Future[Result]) extends

Result

// Convenience function to create an AsyncResult

def Async(promise: Promise[Result]) = AsyncResult(promise)

Page 82: Play Framework: async I/O with Java and Scala

object ProxyScala extends Controller {

def index(url: String) = Action {

val future: Future[Response] = WS.url(url).get()

Async {

future.map { response =>

Ok(response.body).as("text/html")

}

}

}

}

app/controllers/ProxyScala.scala

We can use the map method to turn a Future[Response] into a Future[Result]

Page 83: Play Framework: async I/O with Java and Scala

GET /scala/proxy controllers.ProxyScala.index(url)

Add this endpoint to the routes file

conf/routes

Page 85: Play Framework: async I/O with Java and Scala

Outline1. A quick intro to Play

2. Basic async code

3. map and flatMap4. Parallel and sequential

5. Errors and timeouts

6. Coming soon

Page 86: Play Framework: async I/O with Java and Scala

What is this "map" thing all about?

Page 87: Play Framework: async I/O with Java and Scala

It's easiest to think about map with Lists.

Page 88: Play Framework: async I/O with Java and Scala

val names = List("Jim", "Dean", "Kunal")

Let's start with a List of Strings

Page 89: Play Framework: async I/O with Java and Scala

val names = List("Jim", "Dean", "Kunal")

def lower(str: String): String = str.toLowerCase

And a simple function that converts a String to lowercase

Page 90: Play Framework: async I/O with Java and Scala

List.map(f) will return a new List where each element in the new List is the result of

calling f on each element of the original List

Page 91: Play Framework: async I/O with Java and Scala

val names = List("Jim", "Dean", "Kunal")

def lower(str: String): String = str.toLowerCase

names.map(lower)

// Output: List("jim", "dean", "kunal")

Mapping the lower function over names gives us a new List where each name is lowercase

Page 92: Play Framework: async I/O with Java and Scala

We saw map transform a List[String] into a new List[String]. Can we transform a List[X] into some other type List[Y]?

Page 93: Play Framework: async I/O with Java and Scala

val names = List("Jim", "Dean", "Kunal")

def strlen(str: String): Int = str.length

Start with the same List, but now a new function strlen that returns the length of a String

Page 94: Play Framework: async I/O with Java and Scala

val names = List("Jim", "Dean", "Kunal")

def strlen(str: String): Int = str.length

names.map(strlen)

// Output: List(3, 4, 5)

Mapping strlen over names returns a new List with the length of each String in names

Page 95: Play Framework: async I/O with Java and Scala

Now we see that map can transform a List[String] into a new List[Int].

Page 96: Play Framework: async I/O with Java and Scala

class List[A] {

def map[B](f: A => B): List[B]

}

More generally, this is map's signature

Page 97: Play Framework: async I/O with Java and Scala

Let's look at one more example

Page 98: Play Framework: async I/O with Java and Scala

val names = List("Jim", "Dean", "Kunal")

def explode(str: String): List[Char] = str.toCharArray.

toList

Same List, but now a new function explode that returns a List of Characters in a String

Page 99: Play Framework: async I/O with Java and Scala

val names = List("Jim", "Dean", "Kunal")

def explode(str: String): List[Char] = str.toCharArray.

toList

names.map(explode)

// Output:

// List(List(J, i, m), List(D, e, a, n), List(K, u, n, a,

l))

If we map explode over names, we get nested Lists. But what if we want just one, flat List?

Page 100: Play Framework: async I/O with Java and Scala

class List[A] {

def flatMap[B](f: A => List[B]): List[B]

}

We can use flatMap , which will combine (flatten) any nested Lists

Page 101: Play Framework: async I/O with Java and Scala

val names = List("Jim", "Dean", "Kunal")

def explode(str: String): List[Char] = str.toCharArray.

toList

names.flatMap(explode)

// Output: List(J, i, m, D, e, a, n, K, u, n, a, l)

Using flatMap gives us a single List with each individual character

Page 102: Play Framework: async I/O with Java and Scala

map and flatMap are defined on any "collection" or "container": List, Set, Map, etc

Page 103: Play Framework: async I/O with Java and Scala

val namesSet = Set("Jim", "Dean", "Kunal")

def explode(str: String): List[Char] = str.toCharArray.

toList

namesSet.flatMap(explode)

// Output: Set(e, n, J, u, a, m, i, l, K, D)

Using flatMap on a Set

Page 104: Play Framework: async I/O with Java and Scala

Futures and Promises are also "containers": they just happen to contain 1 item.

Page 105: Play Framework: async I/O with Java and Scala

val future: Future[Response] = WS.url(url).get()

val future: Future[Result] = future.map { response =>

Ok(response.body).as("text/html")

}

This is why it makes sense to use map to turn a Future[Response] into a Future[Result]

Page 106: Play Framework: async I/O with Java and Scala

The "container" class controls when the function passed to map or flatMap actually gets

applied!

Page 107: Play Framework: async I/O with Java and Scala

Outline1. A quick intro to Play

2. Basic async code

3. map and flatMap

4. Parallel and sequential5. Errors and timeouts

6. Coming soon

Page 108: Play Framework: async I/O with Java and Scala

Making I/O requests in parallel is essential for performance in a Service Oriented Architecture

Page 109: Play Framework: async I/O with Java and Scala

With non-blocking I/O, parallel is the default

// These 3 HTTP requests will execute in parallel

Promise<Response> yahoo = WS.url("http://yahoo.com").get();

Promise<Response> google = WS.url("http://google.com").get();

Promise<Response> bing = WS.url("http://bing.com").get();

Page 110: Play Framework: async I/O with Java and Scala

Let's fetch 3 websites in parallel and time them

Page 111: Play Framework: async I/O with Java and Scala

First, define a function that makes makes an HTTP GET and returns a Promise with timing info

public Promise<Timing> timed(final String url) {

final long start = System.currentTimeMillis();

return WS.url(url).get().map(new Function<Response, Timing>() {

public Timing apply(Response response) throws Throwable {

return new Timing(url, System.currentTimeMillis() - start);

}

});

}

public class Timing {

public String url;

public long latency;

}

Page 112: Play Framework: async I/O with Java and Scala

Next, make a controller that fires 3 requests in parallel using the timed function we just created

public class Parallel extends Controller {

public static Result index() {

final Promise<Timing> yahoo = timed("http://www.yahoo.com");

final Promise<Timing> google = timed("http://www.google.com");

final Promise<Timing> bing = timed("http://www.bing.com");

}

}

Page 113: Play Framework: async I/O with Java and Scala

Compose the 3 Promises into a single Promise that will redeem when all 3 are done

public class Parallel extends Controller {

public static Result index() {

final Promise<Long> yahoo = timed("http://www.yahoo.com");

final Promise<Long> google = timed("http://www.google.com");

final Promise<Long> bing = timed("http://www.bing.com");

Promise<List<Timing>> all = Promise.waitAll(yahoo, google, bing);

}

}

Page 114: Play Framework: async I/O with Java and Scala

Render the results as JSON

public class Parallel extends Controller {

public static Result index() {

final Promise<Timing> yahoo = timed("http://www.yahoo.com");

final Promise<Timing> google = timed("http://www.google.com");

final Promise<Timing> bing = timed("http://www.bing.com");

Promise<List<Timing>> all = Promise.waitAll(yahoo, google, bing);

return async(all.map(new Function<List<Timing>, Result>() {

public Result apply(List<Timing> timings) throws Throwable {

return ok(Json.toJson(timings));

}

}));

}

}

Page 115: Play Framework: async I/O with Java and Scala

GET /parallel controllers.Parallel.index()

Add it to the routes file

conf/routes

Page 117: Play Framework: async I/O with Java and Scala

How about parallel requests in Scala?

Page 118: Play Framework: async I/O with Java and Scala

Once again, define a function that makes an HTTP GET and returns a Future with timing info

def timed(url: String): Future[Timing] = {

val start = System.currentTimeMillis()

WS.url(url).get().map(_ =>

Timing(url, System.currentTimeMillis() - start)

)

}

case class Timing(url: String, latency: Long)

Page 119: Play Framework: async I/O with Java and Scala

Next, make a controller that fires 3 requests in parallel using the timed function we just created

object ParallelScala extends Controller {

def index = Action {

val yahoo = timed("http://www.yahoo.com")

val google = timed("http://www.google.com")

val bing = timed("http://www.bing.com")

}

}

Page 120: Play Framework: async I/O with Java and Scala

Compose the 3 Futures into a single Future that will redeem when all 3 are done

object ParallelScala extends Controller {

def index = Action {

val yahoo = timed("http://www.yahoo.com")

val google = timed("http://www.google.com")

val bing = timed("http://www.bing.com")

val all = Future.sequence(Seq(yahoo, google, bing))

}

}

Page 121: Play Framework: async I/O with Java and Scala

Render the results as JSON

object ParallelScala extends Controller {

def index = Action {

val yahoo = timed("http://www.yahoo.com")

val google = timed("http://www.google.com")

val bing = timed("http://www.bing.com")

val all = Future.sequence(Seq(yahoo, google, bing))

Async {

all.map(timings => Ok(Json.toJson(timings)))

}

}

}

Page 122: Play Framework: async I/O with Java and Scala

GET /scala/parallel controllers.ParallelScala.index()

Add it to the routes file

conf/routes

Page 124: Play Framework: async I/O with Java and Scala

If parallel is the default, how do you do sequential steps that depend on each other?

Page 125: Play Framework: async I/O with Java and Scala

Example: make a request to duckduckgo's instant answer API (step 1) and proxy an image

from the response (step 2)

Page 126: Play Framework: async I/O with Java and Scala

First, call make a non-blocking call to duckduckgo

public class LuckyImage extends Controller {

public static Result index(String query) {

Promise<Response> duck = WS.url("http://www.duckduckgo.com")

.setQueryParameter("q", query)

.setQueryParameter("format", "json")

.get();

}

}

Page 127: Play Framework: async I/O with Java and Scala

As a first step, we'll just proxy the response

public class LuckyImage extends Controller {

public static Result index(String query) {

Promise<Response> duck = WS.url("http://www.duckduckgo.com")

.setQueryParameter("q", query)

.setQueryParameter("format", "json")

.get();

return async(duck.map(new Function<Response, Result>() {

public Result apply(Response response) throws Throwable {

return ok(response.getBodyAsStream())

.as(response.getHeader("Content-Type"));

}

}));

}

}

Page 128: Play Framework: async I/O with Java and Scala

GET /lucky controllers.LuckyImage.index(url)

Add it to the routes file

conf/routes

Page 130: Play Framework: async I/O with Java and Scala

As the second step, get the image URL from the response and proxy just that.

Promise<Response> duck = // ... (same request as before)

return async(duck.flatMap(new Function<Response, Promise<Result>>() {

public Promise<Result> apply(Response response) {

String url = getImageUrlFromResponse(response);

return WS.url(url).get().map(new Function<Response, Result>() {

public Result apply(Response response) {

return ok(response.getBodyAsStream())

.as(response.getHeader("Content-Type"));

}

});

}

}));

Page 132: Play Framework: async I/O with Java and Scala

Ok, let's try the same example in Scala

Page 133: Play Framework: async I/O with Java and Scala

First, make the request

object LuckyImageScala extends Controller {

def index(query: String) = Action {

val duck = WS.url("http://www.duckduckgo.com")

.withQueryString("q" -> query, "format" -> "json").get()

}

}

Page 134: Play Framework: async I/O with Java and Scala

Then extract the image URL and proxy it

object LuckyImageScala extends Controller {

def index(query: String) = Action {

val duck = WS.url("http://www.duckduckgo.com")

.withQueryString("q" -> query, "format" -> "json").get()

Async {

duck.flatMap { response =>

val url = getImageUrlFromResponse(response)

WS.url(url).get().map { r =>

Ok(r.getAHCResponse.getResponseBodyAsBytes)

.as(r.getAHCResponse.getHeader("Content-Type"))

}

}

}

}

}

Page 135: Play Framework: async I/O with Java and Scala

GET /scala/lucky controllers.LuckyImageScala.index(url)

Add it to the routes file

conf/routes

Page 137: Play Framework: async I/O with Java and Scala

In both Java and Scala, you order async actions sequentially by nesting map and flatMap calls.

Page 138: Play Framework: async I/O with Java and Scala

Many sequential steps will lead to lots of nesting.

step1.flatMap(new Function<Response, Promise<Result>>() {

public Promise<Result> apply(Response response1) {

step2.flatMap(new Function<Response, Promise<Result>>() {

public Promise<Result> apply(Response response2) {

step3.flatMap(new Function<Response, Promise<Result>>() {

public Promise<Result> apply(Response response3) {

// etc

}

}

}

});

}

});

Page 139: Play Framework: async I/O with Java and Scala

This is "callback hell"

Page 140: Play Framework: async I/O with Java and Scala

ParSeq

https://github.com/linkedin/parseq

Page 141: Play Framework: async I/O with Java and Scala

ParSeq is a framework that makes it easier to write asynchronous code in Java

Page 142: Play Framework: async I/O with Java and Scala

Wrap asynchronous work in ParSeq Task objects,which are similar to Promises and Futures

Task<Response> yahoo = Tasks.wrap(WS.url("http://www.yahoo.com"))

Task<Response> google = Tasks.wrap(WS.url("http://www.google.com"))

Task<Response> bing = Tasks.wrap(WS.url("http://www.bing.com"))

Page 143: Play Framework: async I/O with Java and Scala

Use Tasks.par to compose tasks in parallel

Task<Response> yahoo = Tasks.wrap(WS.url("http://www.yahoo.com"))

Task<Response> google = Tasks.wrap(WS.url("http://www.google.com"))

Task<Response> bing = Tasks.wrap(WS.url("http://www.bing.com"))

// Create a new Task that will run all 3 tasks above at the same time

// and redeem when they are all done

Task<?> parallel = Tasks.par(yahoo, google, bing)

Page 144: Play Framework: async I/O with Java and Scala

Use Tasks.seq to compose tasks sequentially

Task<Response> step1 = new Task() { ... }

Task<Response> step2 = new Task() { ... }

Task<Response> step3 = new Task() { ... }

// Create a new Task that will run the tasks above one at a time,

// in the order specified, and complete with the return value of the

// last one

Task<Response> sequential = Tasks.seq(task1, task2, task3)

Page 145: Play Framework: async I/O with Java and Scala

ParSeq makes async code declarative and easier to reason about: you can read it top to bottom!

Task<Result> complex = Tasks.seq(

Tasks.par(profileTask, companyTask, skillsTask),

Tasks.par(recommendedJobsTask, wvmxTask),

Tasks.par(hydrateImagesTask, hydrateJobsTask, hydrateSkillsTask)

Tasks.seq(assemblePageTask, fireTrackingTask)

)

Page 146: Play Framework: async I/O with Java and Scala

We'll soon be open sourcing a plugin soon to make it easy to use ParSeq in Play.

Page 147: Play Framework: async I/O with Java and Scala

Sequence Comprehensions

Page 148: Play Framework: async I/O with Java and Scala

Syntactic sugar built into Scala. Translates into map and flatMap calls without the nesting.

Page 149: Play Framework: async I/O with Java and Scala

Instead of this...

aFuture.flatMap { a =>

bFuture.flatMap { b =>

cFuture.flatMap { c =>

dFuture.map { d =>

// Build a result using a, b, c, d

}

}

}

}

Page 150: Play Framework: async I/O with Java and Scala

Use this. Note that this syntax works for any object with map and flatMap methods.

for {

a <- aFuture

b <- bFuture

c <- cFuture

d <- dFuture

} yield {

// Build a result using a, b, c, d

}

Page 151: Play Framework: async I/O with Java and Scala

Sequential async I/O example. Note that each step can refer to previous ones.

for {

a <- WS.url(...).get()

b <- WS.url(a).get()

c <- WS.url(a + b).get()

d <- WS.url(a + b + c).get()

} yield {

// Build a result using a, b, c, d

}

Page 152: Play Framework: async I/O with Java and Scala

Parallel async I/O example. Only difference is that the async calls are outside the for statement.

val futureA = WS.url(...)

val futureB = WS.url(...)

val futureC = WS.url(...)

val futureD = WS.url(...)

for {

a <- futureA

b <- futureB

c <- futureC

d <- futureD

} yield {

// Build a result using a, b, c, d

}

Page 153: Play Framework: async I/O with Java and Scala

Sequence comprehensions provide a clean and consistent API for async code: you can read it

top to bottom!

Page 154: Play Framework: async I/O with Java and Scala

Outline1. A quick intro to Play

2. Basic async code

3. map and flatMap

4. Parallel and sequential

5. Errors and timeouts6. Coming soon

Page 155: Play Framework: async I/O with Java and Scala

With a single server, the odds of hitting an error are relatively low

Page 156: Play Framework: async I/O with Java and Scala

In a distributed system with thousands of servers, the odds that you hit an error are very high

Internet Load Balancer

Profile frontend

Company frontend

Recruiter frontend

Profile backend

Search backend

Company backend

Recruiter backend

Ads backend

Data Store

Data Store

Data Store

Data Store

Page 157: Play Framework: async I/O with Java and Scala

Even if a single server is up 99.999% of the time, with 1000 servers, the odds that one is

down are 1 - 0.999991000 = ~1%

Page 158: Play Framework: async I/O with Java and Scala

Here is how to make your async code more resilient to errors and slow performance

Page 159: Play Framework: async I/O with Java and Scala

We can use the recover method on a Promise to specify how to handle errors

public class Errors extends Controller {

public static Result index(String url) {

F.Promise<WS.Response> promise = WS.url(url).get();

return async(promise.map(new F.Function<WS.Response, Result>() {

public Result apply(WS.Response response) throws Throwable {

return ok("Got a response!");

}

}).recover(new F.Function<Throwable, Result>() {

public Result apply(Throwable throwable) throws Throwable {

return internalServerError("Got an exception: " + throwable);

}

}));

}

}

Page 160: Play Framework: async I/O with Java and Scala

There is an analogous recover method on Scala Futures as well

object ErrorsScala extends Controller {

def index(url: String) = Action {

Async {

WS.url(url).get().map { response =>

Ok("Got a response: " + response.status)

}.recover { case t: Throwable =>

InternalServerError("Got an exception: " + t)

}

}

}

}

Page 161: Play Framework: async I/O with Java and Scala

GET /errors controllers.Errors.index(url)GET /scala/errors controllers.ErrorsScala.index(url)

Add to the routes file

conf/routes

Page 163: Play Framework: async I/O with Java and Scala

http://localhost:9000/errors?url=http://www.not-a-real-url.com

Page 164: Play Framework: async I/O with Java and Scala

If some of the data you fetch isn't required to render the page, you can use an Option

pattern

Page 165: Play Framework: async I/O with Java and Scala

Create a helper method: on success, return Some<T>. On failure, log the error, return None.

public static <T> Promise<Option<T>> optional(Promise<T> promise) {

return promise.map(new Function<T, Option<T>>() {

public Option<T> apply(T value) throws Throwable {

if (value == null) {

return Option.None();

}

return Option.Some(value);

}

}).recover(new Function<Throwable, Option<T>>() {

public Option<T> apply(Throwable t) throws Throwable {

Logger.error("Hit an error", t);

return Option.None();

}

});

}

Page 166: Play Framework: async I/O with Java and Scala

Wrap Promises with optional and inside of map, use isDefined to see if you have a value

public static Result index(String url) {

Promise<Option<Response>> promise = optional(WS.url(url).get());

return async(promise.map(new Function<Option<Response>, Result>() {

public Result apply(Option<Response> responseOpt){

if (responseOpt.isDefined()) {

Response response = responseOpt.get();

// Use the response to build the page

} else {

// Build the page without the response

}

}

}));

}

Page 167: Play Framework: async I/O with Java and Scala

The same pattern is even prettier in Scala, as Option is a first-class citizen of the language

Page 168: Play Framework: async I/O with Java and Scala

Reusable helper method to convert Future[T] to Future[Option[T]]

def optional[T](future: Future[T]): Future[Option[T]] = {

future.map(Option.apply).recover { case t: Throwable =>

Logger.error("Hit an error", t)

None

}

}

Page 169: Play Framework: async I/O with Java and Scala

Wrap Futures with optional and use pattern matching, comprehensions, etc within map

object OptionExampleScala extends Controller {

def index(url: String) = Action {

Async {

optional(WS.url(url).get()).map {

case Some(response) => // Use the response to build the page

case _ => // Build the page without the response

}

}

}

}

Page 170: Play Framework: async I/O with Java and Scala

Sometimes, waiting too long for data is worse than not showing that data at all

Page 171: Play Framework: async I/O with Java and Scala

You can create a Promise that will be redeemed with someValue after the specified timeout

Promise.timeout(someValue, 500, TimeUnit.MILLISECONDS)

Page 172: Play Framework: async I/O with Java and Scala

Compose two Promises using or: the result takes on the value of the first one to complete

Promise<Response> response = WS.url(url).get();

Promise<Either<Object, Response>> withTimeout =

Promise.timeout(null, timeout, TimeUnit.MILLISECONDS).or(response);

Page 173: Play Framework: async I/O with Java and Scala

If right is defined, you got a value in time; otherwise, it must have timed out first.

Promise<Response> response = WS.url(url).get();

Promise<Either<Object, Response>> withTimeout =

Promise.timeout(null, timeout, TimeUnit.MILLISECONDS).or(response);

withTimeout.map(new Function<Either<Object, Response>, Result>() {

public Result apply(Either<Object, Response> either) {

if (either.right.isDefined()) {

Response response = either.right.get();

// Use the response to build the page

} else {

// Hit a timeout, build the page without the response

}

}

});

Page 174: Play Framework: async I/O with Java and Scala

The Scala version

import play.api.libs.concurrent._

object TimeoutExampleScala extends Controller {

def index(url: String, timeout: Long) = Action {

val timeout = Promise.timeout(null, timeout, MILLISECONDS)

val future = timeout.either(WS.url(url).get())

future.map {

case Right(response) => // Use the response to build the page

case _ => // Hit a timeout, build the page without the response

}

}

}

Page 175: Play Framework: async I/O with Java and Scala

Outline1. A quick intro to Play

2. Basic async code

3. map and flatMap

4. Parallel and sequential

5. Errors and timeouts

6. Coming soon

Page 176: Play Framework: async I/O with Java and Scala

play-async-plugin

We'll be open sourcing this plugin soon

Page 177: Play Framework: async I/O with Java and Scala

It is a collection of async utilities, including...

Page 178: Play Framework: async I/O with Java and Scala

ParSeq integration for Play to make asynchronous Java code easier

Task<Response> yahoo = Tasks.wrap(WS.url("http://www.yahoo.com"))

Task<Response> google = Tasks.wrap(WS.url("http://www.google.com"))

Task<Response> bing = Tasks.wrap(WS.url("http://www.bing.com"))

// Create a new Task that will run the tasks above one at a time,

// in the order specified, and complete with the return value of the

// last one

Task<Response> sequential = Tasks.seq(yahoo, google, bing)

Page 179: Play Framework: async I/O with Java and Scala

Config-driven SLAs (timeouts) for any async I/O

sla.plugin.slas = [

{

resources: ["/profile", "/companies"]

timeout: "2s"

},

{

resources: ["/pymk", "/skills/*"],

timeout: "500ms"

}

]

Page 180: Play Framework: async I/O with Java and Scala

In-browser visualization of all async requests for a page, including timing, responses, and errors

Page 181: Play Framework: async I/O with Java and Scala

We're just getting started with Play! We'll be sharing more as we go.

Page 184: Play Framework: async I/O with Java and Scala

Thank you!