SlideShare a Scribd company logo
Asynchronous I/O
with Java and Scala
LinkedIn uses a service oriented architecture
(SOA)
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
Services communicate with each other via
remote calls
Profile frontend Profile backend
/profile/123
HTTP request
Profile frontend Profile backend
JSON response
{
"id": 123,
"first": "Yevgeniy",
"last": "Brikman"
}
Most people are used to synchronous I/O when
making requests between servers
The most popular frameworks typically use
one-thread-per-request and blocking I/O
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
Evented servers have one thread/process per
CPU core and use non-blocking I/O
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 call
http.request(options, callback);
console.log('This line may execute before the callback!');
Why threaded vs. evented matters for
LinkedIn
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
I/O is very expensive
http://www.eecs.berkeley.edu/~rcs/research/interactive_latency.html
In a threaded server, threads spend most of
the time idle, waiting on I/O
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 overhead
b. Context switching overhead
3. Too few threads in the thread pool:
a. Run out of threads, latency goes up
b. Sensitive to downstream latency!
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
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
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
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
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
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
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
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.
This is thread pool hell
Play is built on top of Netty, so it supports non-
blocking I/O
NIO benefits
1. No sensitivity to downstream slowness
2. Easy to parallelize I/O
3. Supports many concurrent and long-running
connections, enabling:
a. WebSockets
b. Comet
c. Server-Sent Events
This talk is a brief introduction to writing
asynchronous code with the Play Framework.
For each section, I will include simplified
examples: first in Java and then Scala.
The world’s largest professional network
at
We've been using Play in production for
more than 6 months
A few apps built on Play
Channels (frontend)
Premium Subscriptions (frontend)
Polls (frontend + backend)
REST search (internal tool)
About me
Leading the Play project as part of LinkedIn's Service Infrastructure Team.
Also: hackdays, engineering blog, incubator, open source.
Outline
1. A quick intro to Play
2. Basic async code
3. map and flatMap
4. Parallel and sequential
5. Errors and timeouts
6. Coming soon
Outline
1. A quick intro to Play
2. Basic async code
3. map and flatMap
4. Parallel and sequential
5. Errors and timeouts
6. Coming soon
Download and install Play from
http://www.playframework.com
> play new my-app
> play idea
> play eclipse
> play run
http://localhost:9000
Application layout
app → Application sources
└ assets → Compiled asset sources
└ controllers → Application controllers
└ models → Application business layer
└ views → Templates
conf → Configurations files
└ application.conf → Main configuration file
└ routes → Routes definition
public → Public assets
└ stylesheets → CSS files
└ javascripts → Javascript files
└ images → Image files
project → sbt configuration files
└ Build.scala → Application build script
└ plugins.sbt → sbt plugins
lib → Unmanaged libraries
dependencies
logs → Standard logs folder
target → Generated stuff
test → Unit or functional tests
Let's get a feel for Play by creating a
Java Controller
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
Don't worry about the use of static. Yes,
Play supports IOC. Using static (and other
shortcuts) lets me keep the examples simple.
GET /hello controllers.HelloWorld.index()
Expose the controller/action at a URL
conf/routes
http://localhost:9000/hello
Woohoo, hot reload!
public class HelloWorld extends Controller {
public static Result index(String name) {
return ok("Hello " + name);
}
}
Add a parameter
app/controllers/HelloWorld.java
GET /hello controllers.HelloWorld.index( name)
Read the parameter from the query string
conf/routes
http://localhost:9000/hello?name=Jim
GET /hello/:name controllers.HelloWorld.index(name)
Read the parameter from the URL instead
conf/routes
http://localhost:9000/hello/Jim
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
GET /hello/:name/ :age controllers.HelloWorld.index(name: String, age: Int)
Add the parameter. Note the type checking!
conf/routes
http://localhost:9000/hello/Jim/28
@(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
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
http://localhost:9000/hello/Jim/28
Play also natively supports 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")
}
}
GET /scala controllers.HelloWorldScala.index()
Add it to the routes file as usual
conf/routes
http://localhost:9000/scala
Outline
1. A quick intro to Play
2. Basic async code
3. map and flatMap
4. Parallel and sequential
5. Errors and timeouts
6. Coming soon
Let's use Play's Web Services library (WS) to
make some non-blocking HTTP calls
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
A Promise<T> will eventually contain the
value T (or an error)
(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;
}
}
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>
GET /proxy controllers.Proxy.index(url)
Add this endpoint to the routes file
conf/routes
http://localhost:9000/proxy?url=http://example.com
We just built a completely
non-blocking proxy!
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
Refresh the page and the logs show the
HTTP call really is non-blocking
Let's create the same Proxy in 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
A Future[T] will eventually contain the
value T (or an error)
(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)
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]
GET /scala/proxy controllers.ProxyScala.index
(url)
Add this endpoint to the routes file
conf/routes
http://localhost:9000/scala/proxy?url=http://example.com
Outline
1. A quick intro to Play
2. Basic async code
3. map and flatMap
4. Parallel and sequential
5. Errors and timeouts
6. Coming soon
What is this "map" thing all about?
It's easiest to think about map with Lists.
val names = List("Jim", "Dean", "Kunal")
Let's start with a List of Strings
val names = List("Jim", "Dean", "Kunal")
def lower(str: String): String = str.toLowerCase
And a simple function that converts a String to
lowercase
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
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
We saw map transform a List[String] into
a new List[String]. Can we transform a
List[X] into some other type List[Y]?
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
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
Now we see that map can transform a List
[String] into a new List[Int].
class List[A] {
def map[B](f: A => B): List[B]
}
More generally, this is map's signature
Let's look at one more example
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
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?
class List[A] {
def flatMap[B](f: A => List[B]): List[B]
}
We can use flatMap , which will combine (flatten)
any nested Lists
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
map and flatMap are defined on any
"collection" or "container": List, Set, Map, etc
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
Futures and Promises are also "containers":
they just happen to contain 1 item.
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]
The "container" class controls when the function
passed to map or flatMap actually gets
applied!
Outline
1. A quick intro to Play
2. Basic async code
3. map and flatMap
4. Parallel and sequential
5. Errors and timeouts
6. Coming soon
Making I/O requests in parallel is essential for
performance in a Service Oriented Architecture
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();
Let's fetch 3 websites in parallel and time them
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;
}
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");
}
}
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);
}
}
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));
}
}));
}
}
GET /parallel controllers.Parallel.index()
Add it to the routes file
conf/routes
http://localhost:9000/parallel
How about parallel requests in 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)
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")
}
}
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))
}
}
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)))
}
}
}
GET /scala/parallel controllers.ParallelScala.index()
Add it to the routes file
conf/routes
http://localhost:9000/scala/parallel
If parallel is the default, how do you do
sequential steps that depend on each other?
Example: make a request to duckduckgo's
instant answer API (step 1) and proxy an image
from the response (step 2)
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();
}
}
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"));
}
}));
}
}
GET /lucky controllers.LuckyImage.index
(url)
Add it to the routes file
conf/routes
http://localhost:9000/lucky?query=linkedin
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"));
}
});
}
}));
http://localhost:9000/lucky?query=linkedin
Ok, let's try the same example in 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()
}
}
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"))
}
}
}
}
}
GET /scala/lucky controllers.LuckyImageScala.index
(url)
Add it to the routes file
conf/routes
http://localhost:9000/lucky?query=play+framework
In both Java and Scala, you order async actions
sequentially by nesting map and flatMap calls.
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
}
}
}
});
}
});
This is "callback hell"
ParSeq
https://github.com/linkedin/parseq
ParSeq is a framework that makes it easier to
write asynchronous code in Java
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"))
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)
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)
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)
)
We'll soon be open sourcing a plugin soon to
make it easy to use ParSeq in Play.
Sequence Comprehensions
Syntactic sugar built into Scala. Translates into
map and flatMap calls without the nesting.
Instead of this...
aFuture.flatMap { a =>
bFuture.flatMap { b =>
cFuture.flatMap { c =>
dFuture.map { d =>
// Build a result using a, b, c, d
}
}
}
}
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
}
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
}
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
}
Sequence comprehensions provide a clean and
consistent API for async code: you can read it
top to bottom!
Outline
1. A quick intro to Play
2. Basic async code
3. map and flatMap
4. Parallel and sequential
5. Errors and timeouts
6. Coming soon
With a single server, the odds of hitting an error
are relatively low
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
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%
Here is how to make your async code more
resilient to errors and slow performance
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);
}
}));
}
}
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)
}
}
}
}
GET /errors controllers.Errors.index(url)
GET /scala/errors controllers.ErrorsScala.index(url)
Add to the routes file
conf/routes
http://localhost:9000/errors?url=http://www.example.com
http://localhost:9000/errors?url=http://www.not-a-real-url.com
If some of the data you fetch isn't required to
render the page, you can use an Option
pattern
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();
}
});
}
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
}
}
}));
}
The same pattern is even prettier in Scala, as
Option is a first-class citizen of the language
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
}
}
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
}
}
}
}
Sometimes, waiting too long for data is worse
than not showing that data at all
You can create a Promise that will be redeemed
with someValue after the specified timeout
Promise.timeout(someValue, 500, TimeUnit.MILLISECONDS)
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);
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
}
}
});
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
}
}
}
Outline
1. A quick intro to Play
2. Basic async code
3. map and flatMap
4. Parallel and sequential
5. Errors and timeouts
6. Coming soon
play-async-plugin
We'll be open sourcing this plugin soon
It is a collection of async utilities, including...
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)
Config-driven SLAs (timeouts) for any async I/O
sla.plugin.slas = [
{
resources: ["/profile", "/companies"]
timeout: "2s"
},
{
resources: ["/pymk", "/skills/*"],
timeout: "500ms"
}
]
In-browser visualization of all async requests for a
page, including timing, responses, and errors
We're just getting started with Play!
We'll be sharing more as we go.
LinkedIn Engineering Blog
http://engineering.linkedin.com
@LinkedInEng on Twitter
https://twitter.com/LinkedInEng
Thank you!

More Related Content

What's hot (20)

How to Lock Down Apache Kafka and Keep Your Streams Safe
How to Lock Down Apache Kafka and Keep Your Streams SafeHow to Lock Down Apache Kafka and Keep Your Streams Safe
How to Lock Down Apache Kafka and Keep Your Streams Safe
confluent
 
給你一個使用 Laravel 的理由
給你一個使用 Laravel 的理由給你一個使用 Laravel 的理由
給你一個使用 Laravel 的理由
Shengyou Fan
 
Clickhouse at Cloudflare. By Marek Vavrusa
Clickhouse at Cloudflare. By Marek VavrusaClickhouse at Cloudflare. By Marek Vavrusa
Clickhouse at Cloudflare. By Marek Vavrusa
Altinity Ltd
 
Building an Interactive Query Service in Kafka Streams With Bill Bejeck | Cur...
Building an Interactive Query Service in Kafka Streams With Bill Bejeck | Cur...Building an Interactive Query Service in Kafka Streams With Bill Bejeck | Cur...
Building an Interactive Query Service in Kafka Streams With Bill Bejeck | Cur...
HostedbyConfluent
 
Spring boot
Spring bootSpring boot
Spring boot
sdeeg
 
PostgreSQL Replication Tutorial
PostgreSQL Replication TutorialPostgreSQL Replication Tutorial
PostgreSQL Replication Tutorial
Hans-Jürgen Schönig
 
(Mis)trusting and (ab)using ssh
(Mis)trusting and (ab)using ssh(Mis)trusting and (ab)using ssh
(Mis)trusting and (ab)using ssh
morisson
 
What should a hacker know about WebDav?
What should a hacker know about WebDav?What should a hacker know about WebDav?
What should a hacker know about WebDav?
Mikhail Egorov
 
A little bit about code injection in WebApplication Frameworks (CVE-2018-1466...
A little bit about code injection in WebApplication Frameworks (CVE-2018-1466...A little bit about code injection in WebApplication Frameworks (CVE-2018-1466...
A little bit about code injection in WebApplication Frameworks (CVE-2018-1466...
ufpb
 
Spring Framework - AOP
Spring Framework - AOPSpring Framework - AOP
Spring Framework - AOP
Dzmitry Naskou
 
Spring Boot Tutorial
Spring Boot TutorialSpring Boot Tutorial
Spring Boot Tutorial
Naphachara Rattanawilai
 
REST Service Authetication with TLS & JWTs
REST Service Authetication with TLS & JWTsREST Service Authetication with TLS & JWTs
REST Service Authetication with TLS & JWTs
Jon Todd
 
Spring Boot
Spring BootSpring Boot
Spring Boot
HongSeong Jeon
 
Xke spring boot
Xke spring bootXke spring boot
Xke spring boot
sourabh aggarwal
 
Rest API Security
Rest API SecurityRest API Security
Rest API Security
Stormpath
 
Applications secure by default
Applications secure by defaultApplications secure by default
Applications secure by default
SecuRing
 
A Hacker's perspective on AEM applications security
A Hacker's perspective on AEM applications securityA Hacker's perspective on AEM applications security
A Hacker's perspective on AEM applications security
Mikhail Egorov
 
Cryptography for Java Developers: Nakov jProfessionals (Jan 2019)
Cryptography for Java Developers: Nakov jProfessionals (Jan 2019)Cryptography for Java Developers: Nakov jProfessionals (Jan 2019)
Cryptography for Java Developers: Nakov jProfessionals (Jan 2019)
Svetlin Nakov
 
twMVC#43 YARP
twMVC#43 YARPtwMVC#43 YARP
twMVC#43 YARP
twMVC
 
The never-ending REST API design debate
The never-ending REST API design debateThe never-ending REST API design debate
The never-ending REST API design debate
Restlet
 
How to Lock Down Apache Kafka and Keep Your Streams Safe
How to Lock Down Apache Kafka and Keep Your Streams SafeHow to Lock Down Apache Kafka and Keep Your Streams Safe
How to Lock Down Apache Kafka and Keep Your Streams Safe
confluent
 
給你一個使用 Laravel 的理由
給你一個使用 Laravel 的理由給你一個使用 Laravel 的理由
給你一個使用 Laravel 的理由
Shengyou Fan
 
Clickhouse at Cloudflare. By Marek Vavrusa
Clickhouse at Cloudflare. By Marek VavrusaClickhouse at Cloudflare. By Marek Vavrusa
Clickhouse at Cloudflare. By Marek Vavrusa
Altinity Ltd
 
Building an Interactive Query Service in Kafka Streams With Bill Bejeck | Cur...
Building an Interactive Query Service in Kafka Streams With Bill Bejeck | Cur...Building an Interactive Query Service in Kafka Streams With Bill Bejeck | Cur...
Building an Interactive Query Service in Kafka Streams With Bill Bejeck | Cur...
HostedbyConfluent
 
Spring boot
Spring bootSpring boot
Spring boot
sdeeg
 
(Mis)trusting and (ab)using ssh
(Mis)trusting and (ab)using ssh(Mis)trusting and (ab)using ssh
(Mis)trusting and (ab)using ssh
morisson
 
What should a hacker know about WebDav?
What should a hacker know about WebDav?What should a hacker know about WebDav?
What should a hacker know about WebDav?
Mikhail Egorov
 
A little bit about code injection in WebApplication Frameworks (CVE-2018-1466...
A little bit about code injection in WebApplication Frameworks (CVE-2018-1466...A little bit about code injection in WebApplication Frameworks (CVE-2018-1466...
A little bit about code injection in WebApplication Frameworks (CVE-2018-1466...
ufpb
 
Spring Framework - AOP
Spring Framework - AOPSpring Framework - AOP
Spring Framework - AOP
Dzmitry Naskou
 
REST Service Authetication with TLS & JWTs
REST Service Authetication with TLS & JWTsREST Service Authetication with TLS & JWTs
REST Service Authetication with TLS & JWTs
Jon Todd
 
Rest API Security
Rest API SecurityRest API Security
Rest API Security
Stormpath
 
Applications secure by default
Applications secure by defaultApplications secure by default
Applications secure by default
SecuRing
 
A Hacker's perspective on AEM applications security
A Hacker's perspective on AEM applications securityA Hacker's perspective on AEM applications security
A Hacker's perspective on AEM applications security
Mikhail Egorov
 
Cryptography for Java Developers: Nakov jProfessionals (Jan 2019)
Cryptography for Java Developers: Nakov jProfessionals (Jan 2019)Cryptography for Java Developers: Nakov jProfessionals (Jan 2019)
Cryptography for Java Developers: Nakov jProfessionals (Jan 2019)
Svetlin Nakov
 
twMVC#43 YARP
twMVC#43 YARPtwMVC#43 YARP
twMVC#43 YARP
twMVC
 
The never-ending REST API design debate
The never-ending REST API design debateThe never-ending REST API design debate
The never-ending REST API design debate
Restlet
 

Similar to Play Framework: async I/O with Java and Scala (20)

The Play Framework at LinkedIn
The Play Framework at LinkedInThe Play Framework at LinkedIn
The Play Framework at LinkedIn
Yevgeniy Brikman
 
The Play Framework at LinkedIn: productivity and performance at scale - Jim B...
The Play Framework at LinkedIn: productivity and performance at scale - Jim B...The Play Framework at LinkedIn: productivity and performance at scale - Jim B...
The Play Framework at LinkedIn: productivity and performance at scale - Jim B...
jaxconf
 
Play framework productivity formula
Play framework   productivity formula Play framework   productivity formula
Play framework productivity formula
Sorin Chiprian
 
Play Framework
Play FrameworkPlay Framework
Play Framework
mozgkarakaya
 
Play Framework: The Basics
Play Framework: The BasicsPlay Framework: The Basics
Play Framework: The Basics
Philip Langer
 
Play Framework
Play FrameworkPlay Framework
Play Framework
Eduard Tudenhoefner
 
Web application development using Play Framework (with Java)
Web application development using Play Framework (with Java)Web application development using Play Framework (with Java)
Web application development using Play Framework (with Java)
Saeed Zarinfam
 
eXo Platform SEA - Play Framework Introduction
eXo Platform SEA - Play Framework IntroductioneXo Platform SEA - Play Framework Introduction
eXo Platform SEA - Play Framework Introduction
vstorm83
 
Node.js vs Play Framework
Node.js vs Play FrameworkNode.js vs Play Framework
Node.js vs Play Framework
Yevgeniy Brikman
 
Silicon Valley Code Camp 2011: Play! as you REST
Silicon Valley Code Camp 2011: Play! as you RESTSilicon Valley Code Camp 2011: Play! as you REST
Silicon Valley Code Camp 2011: Play! as you REST
Manish Pandit
 
Play Framework
Play FrameworkPlay Framework
Play Framework
Harinath Krishnamoorthy
 
Play Framework and Activator
Play Framework and ActivatorPlay Framework and Activator
Play Framework and Activator
Kevin Webber
 
Play Framework Introduction
Play Framework IntroductionPlay Framework Introduction
Play Framework Introduction
m-kurz
 
Let's Play- Overview
Let's Play- OverviewLet's Play- Overview
Let's Play- Overview
benewu
 
Play framework : A Walkthrough
Play framework : A WalkthroughPlay framework : A Walkthrough
Play framework : A Walkthrough
mitesh_sharma
 
Play 2.0
Play 2.0Play 2.0
Play 2.0
elizhender
 
Xebia Knowledge Exchange (feb 2011) - Large Scale Web Development
Xebia Knowledge Exchange (feb 2011) - Large Scale Web DevelopmentXebia Knowledge Exchange (feb 2011) - Large Scale Web Development
Xebia Knowledge Exchange (feb 2011) - Large Scale Web Development
Michaël Figuière
 
Play Framework: Intro & High-Level Overview
Play Framework: Intro & High-Level OverviewPlay Framework: Intro & High-Level Overview
Play Framework: Intro & High-Level Overview
Josh Padnick
 
Why Play Framework is fast
Why Play Framework is fastWhy Play Framework is fast
Why Play Framework is fast
Legacy Typesafe (now Lightbend)
 
Asynchronous web apps with the Play Framework 2.0
Asynchronous web apps with the Play Framework 2.0Asynchronous web apps with the Play Framework 2.0
Asynchronous web apps with the Play Framework 2.0
Oscar Renalias
 
The Play Framework at LinkedIn
The Play Framework at LinkedInThe Play Framework at LinkedIn
The Play Framework at LinkedIn
Yevgeniy Brikman
 
The Play Framework at LinkedIn: productivity and performance at scale - Jim B...
The Play Framework at LinkedIn: productivity and performance at scale - Jim B...The Play Framework at LinkedIn: productivity and performance at scale - Jim B...
The Play Framework at LinkedIn: productivity and performance at scale - Jim B...
jaxconf
 
Play framework productivity formula
Play framework   productivity formula Play framework   productivity formula
Play framework productivity formula
Sorin Chiprian
 
Play Framework: The Basics
Play Framework: The BasicsPlay Framework: The Basics
Play Framework: The Basics
Philip Langer
 
Web application development using Play Framework (with Java)
Web application development using Play Framework (with Java)Web application development using Play Framework (with Java)
Web application development using Play Framework (with Java)
Saeed Zarinfam
 
eXo Platform SEA - Play Framework Introduction
eXo Platform SEA - Play Framework IntroductioneXo Platform SEA - Play Framework Introduction
eXo Platform SEA - Play Framework Introduction
vstorm83
 
Silicon Valley Code Camp 2011: Play! as you REST
Silicon Valley Code Camp 2011: Play! as you RESTSilicon Valley Code Camp 2011: Play! as you REST
Silicon Valley Code Camp 2011: Play! as you REST
Manish Pandit
 
Play Framework and Activator
Play Framework and ActivatorPlay Framework and Activator
Play Framework and Activator
Kevin Webber
 
Play Framework Introduction
Play Framework IntroductionPlay Framework Introduction
Play Framework Introduction
m-kurz
 
Let's Play- Overview
Let's Play- OverviewLet's Play- Overview
Let's Play- Overview
benewu
 
Play framework : A Walkthrough
Play framework : A WalkthroughPlay framework : A Walkthrough
Play framework : A Walkthrough
mitesh_sharma
 
Xebia Knowledge Exchange (feb 2011) - Large Scale Web Development
Xebia Knowledge Exchange (feb 2011) - Large Scale Web DevelopmentXebia Knowledge Exchange (feb 2011) - Large Scale Web Development
Xebia Knowledge Exchange (feb 2011) - Large Scale Web Development
Michaël Figuière
 
Play Framework: Intro & High-Level Overview
Play Framework: Intro & High-Level OverviewPlay Framework: Intro & High-Level Overview
Play Framework: Intro & High-Level Overview
Josh Padnick
 
Asynchronous web apps with the Play Framework 2.0
Asynchronous web apps with the Play Framework 2.0Asynchronous web apps with the Play Framework 2.0
Asynchronous web apps with the Play Framework 2.0
Oscar Renalias
 
Ad

More from Yevgeniy Brikman (20)

Cloud adoption fails - 5 ways deployments go wrong and 5 solutions
Cloud adoption fails - 5 ways deployments go wrong and 5 solutionsCloud adoption fails - 5 ways deployments go wrong and 5 solutions
Cloud adoption fails - 5 ways deployments go wrong and 5 solutions
Yevgeniy Brikman
 
How to test infrastructure code: automated testing for Terraform, Kubernetes,...
How to test infrastructure code: automated testing for Terraform, Kubernetes,...How to test infrastructure code: automated testing for Terraform, Kubernetes,...
How to test infrastructure code: automated testing for Terraform, Kubernetes,...
Yevgeniy Brikman
 
Lessons learned from writing over 300,000 lines of infrastructure code
Lessons learned from writing over 300,000 lines of infrastructure codeLessons learned from writing over 300,000 lines of infrastructure code
Lessons learned from writing over 300,000 lines of infrastructure code
Yevgeniy Brikman
 
Gruntwork Executive Summary
Gruntwork Executive SummaryGruntwork Executive Summary
Gruntwork Executive Summary
Yevgeniy Brikman
 
Reusable, composable, battle-tested Terraform modules
Reusable, composable, battle-tested Terraform modulesReusable, composable, battle-tested Terraform modules
Reusable, composable, battle-tested Terraform modules
Yevgeniy Brikman
 
The Truth About Startups: What I wish someone had told me about entrepreneurs...
The Truth About Startups: What I wish someone had told me about entrepreneurs...The Truth About Startups: What I wish someone had told me about entrepreneurs...
The Truth About Startups: What I wish someone had told me about entrepreneurs...
Yevgeniy Brikman
 
An intro to Docker, Terraform, and Amazon ECS
An intro to Docker, Terraform, and Amazon ECSAn intro to Docker, Terraform, and Amazon ECS
An intro to Docker, Terraform, and Amazon ECS
Yevgeniy Brikman
 
Comprehensive Terraform Training
Comprehensive Terraform TrainingComprehensive Terraform Training
Comprehensive Terraform Training
Yevgeniy Brikman
 
Infrastructure as code: running microservices on AWS using Docker, Terraform,...
Infrastructure as code: running microservices on AWS using Docker, Terraform,...Infrastructure as code: running microservices on AWS using Docker, Terraform,...
Infrastructure as code: running microservices on AWS using Docker, Terraform,...
Yevgeniy Brikman
 
Agility Requires Safety
Agility Requires SafetyAgility Requires Safety
Agility Requires Safety
Yevgeniy Brikman
 
Startup Ideas and Validation
Startup Ideas and ValidationStartup Ideas and Validation
Startup Ideas and Validation
Yevgeniy Brikman
 
A Guide to Hiring for your Startup
A Guide to Hiring for your StartupA Guide to Hiring for your Startup
A Guide to Hiring for your Startup
Yevgeniy Brikman
 
Startup DNA: Speed Wins
Startup DNA: Speed WinsStartup DNA: Speed Wins
Startup DNA: Speed Wins
Yevgeniy Brikman
 
Node.js vs Play Framework (with Japanese subtitles)
Node.js vs Play Framework (with Japanese subtitles)Node.js vs Play Framework (with Japanese subtitles)
Node.js vs Play Framework (with Japanese subtitles)
Yevgeniy Brikman
 
Rapid prototyping
Rapid prototypingRapid prototyping
Rapid prototyping
Yevgeniy Brikman
 
Composable and streamable Play apps
Composable and streamable Play appsComposable and streamable Play apps
Composable and streamable Play apps
Yevgeniy Brikman
 
Kings of Code Hack Battle
Kings of Code Hack BattleKings of Code Hack Battle
Kings of Code Hack Battle
Yevgeniy Brikman
 
Hackdays and [in]cubator
Hackdays and [in]cubatorHackdays and [in]cubator
Hackdays and [in]cubator
Yevgeniy Brikman
 
Startup DNA: the formula behind successful startups in Silicon Valley (update...
Startup DNA: the formula behind successful startups in Silicon Valley (update...Startup DNA: the formula behind successful startups in Silicon Valley (update...
Startup DNA: the formula behind successful startups in Silicon Valley (update...
Yevgeniy Brikman
 
Dust.js
Dust.jsDust.js
Dust.js
Yevgeniy Brikman
 
Cloud adoption fails - 5 ways deployments go wrong and 5 solutions
Cloud adoption fails - 5 ways deployments go wrong and 5 solutionsCloud adoption fails - 5 ways deployments go wrong and 5 solutions
Cloud adoption fails - 5 ways deployments go wrong and 5 solutions
Yevgeniy Brikman
 
How to test infrastructure code: automated testing for Terraform, Kubernetes,...
How to test infrastructure code: automated testing for Terraform, Kubernetes,...How to test infrastructure code: automated testing for Terraform, Kubernetes,...
How to test infrastructure code: automated testing for Terraform, Kubernetes,...
Yevgeniy Brikman
 
Lessons learned from writing over 300,000 lines of infrastructure code
Lessons learned from writing over 300,000 lines of infrastructure codeLessons learned from writing over 300,000 lines of infrastructure code
Lessons learned from writing over 300,000 lines of infrastructure code
Yevgeniy Brikman
 
Gruntwork Executive Summary
Gruntwork Executive SummaryGruntwork Executive Summary
Gruntwork Executive Summary
Yevgeniy Brikman
 
Reusable, composable, battle-tested Terraform modules
Reusable, composable, battle-tested Terraform modulesReusable, composable, battle-tested Terraform modules
Reusable, composable, battle-tested Terraform modules
Yevgeniy Brikman
 
The Truth About Startups: What I wish someone had told me about entrepreneurs...
The Truth About Startups: What I wish someone had told me about entrepreneurs...The Truth About Startups: What I wish someone had told me about entrepreneurs...
The Truth About Startups: What I wish someone had told me about entrepreneurs...
Yevgeniy Brikman
 
An intro to Docker, Terraform, and Amazon ECS
An intro to Docker, Terraform, and Amazon ECSAn intro to Docker, Terraform, and Amazon ECS
An intro to Docker, Terraform, and Amazon ECS
Yevgeniy Brikman
 
Comprehensive Terraform Training
Comprehensive Terraform TrainingComprehensive Terraform Training
Comprehensive Terraform Training
Yevgeniy Brikman
 
Infrastructure as code: running microservices on AWS using Docker, Terraform,...
Infrastructure as code: running microservices on AWS using Docker, Terraform,...Infrastructure as code: running microservices on AWS using Docker, Terraform,...
Infrastructure as code: running microservices on AWS using Docker, Terraform,...
Yevgeniy Brikman
 
Startup Ideas and Validation
Startup Ideas and ValidationStartup Ideas and Validation
Startup Ideas and Validation
Yevgeniy Brikman
 
A Guide to Hiring for your Startup
A Guide to Hiring for your StartupA Guide to Hiring for your Startup
A Guide to Hiring for your Startup
Yevgeniy Brikman
 
Node.js vs Play Framework (with Japanese subtitles)
Node.js vs Play Framework (with Japanese subtitles)Node.js vs Play Framework (with Japanese subtitles)
Node.js vs Play Framework (with Japanese subtitles)
Yevgeniy Brikman
 
Composable and streamable Play apps
Composable and streamable Play appsComposable and streamable Play apps
Composable and streamable Play apps
Yevgeniy Brikman
 
Startup DNA: the formula behind successful startups in Silicon Valley (update...
Startup DNA: the formula behind successful startups in Silicon Valley (update...Startup DNA: the formula behind successful startups in Silicon Valley (update...
Startup DNA: the formula behind successful startups in Silicon Valley (update...
Yevgeniy Brikman
 
Ad

Recently uploaded (20)

Jeremy Millul - A Talented Software Developer
Jeremy Millul - A Talented Software DeveloperJeremy Millul - A Talented Software Developer
Jeremy Millul - A Talented Software Developer
Jeremy Millul
 
vertical-cnc-processing-centers-drillteq-v-200-en.pdf
vertical-cnc-processing-centers-drillteq-v-200-en.pdfvertical-cnc-processing-centers-drillteq-v-200-en.pdf
vertical-cnc-processing-centers-drillteq-v-200-en.pdf
AmirStern2
 
Integration of Utility Data into 3D BIM Models Using a 3D Solids Modeling Wor...
Integration of Utility Data into 3D BIM Models Using a 3D Solids Modeling Wor...Integration of Utility Data into 3D BIM Models Using a 3D Solids Modeling Wor...
Integration of Utility Data into 3D BIM Models Using a 3D Solids Modeling Wor...
Safe Software
 
Agentic AI: Beyond the Buzz- LangGraph Studio V2
Agentic AI: Beyond the Buzz- LangGraph Studio V2Agentic AI: Beyond the Buzz- LangGraph Studio V2
Agentic AI: Beyond the Buzz- LangGraph Studio V2
Shashikant Jagtap
 
Your startup on AWS - How to architect and maintain a Lean and Mean account
Your startup on AWS - How to architect and maintain a Lean and Mean accountYour startup on AWS - How to architect and maintain a Lean and Mean account
Your startup on AWS - How to architect and maintain a Lean and Mean account
angelo60207
 
Creating an Accessible Future-How AI-powered Accessibility Testing is Shaping...
Creating an Accessible Future-How AI-powered Accessibility Testing is Shaping...Creating an Accessible Future-How AI-powered Accessibility Testing is Shaping...
Creating an Accessible Future-How AI-powered Accessibility Testing is Shaping...
Impelsys Inc.
 
Your startup on AWS - How to architect and maintain a Lean and Mean account J...
Your startup on AWS - How to architect and maintain a Lean and Mean account J...Your startup on AWS - How to architect and maintain a Lean and Mean account J...
Your startup on AWS - How to architect and maintain a Lean and Mean account J...
angelo60207
 
DevOps in the Modern Era - Thoughtfully Critical Podcast
DevOps in the Modern Era - Thoughtfully Critical PodcastDevOps in the Modern Era - Thoughtfully Critical Podcast
DevOps in the Modern Era - Thoughtfully Critical Podcast
Chris Wahl
 
ISOIEC 42005 Revolutionalises AI Impact Assessment.pptx
ISOIEC 42005 Revolutionalises AI Impact Assessment.pptxISOIEC 42005 Revolutionalises AI Impact Assessment.pptx
ISOIEC 42005 Revolutionalises AI Impact Assessment.pptx
AyilurRamnath1
 
7 Salesforce Data Cloud Best Practices.pdf
7 Salesforce Data Cloud Best Practices.pdf7 Salesforce Data Cloud Best Practices.pdf
7 Salesforce Data Cloud Best Practices.pdf
Minuscule Technologies
 
Scaling GenAI Inference From Prototype to Production: Real-World Lessons in S...
Scaling GenAI Inference From Prototype to Production: Real-World Lessons in S...Scaling GenAI Inference From Prototype to Production: Real-World Lessons in S...
Scaling GenAI Inference From Prototype to Production: Real-World Lessons in S...
Anish Kumar
 
National Fuels Treatments Initiative: Building a Seamless Map of Hazardous Fu...
National Fuels Treatments Initiative: Building a Seamless Map of Hazardous Fu...National Fuels Treatments Initiative: Building a Seamless Map of Hazardous Fu...
National Fuels Treatments Initiative: Building a Seamless Map of Hazardous Fu...
Safe Software
 
“State-space Models vs. Transformers for Ultra-low-power Edge AI,” a Presenta...
“State-space Models vs. Transformers for Ultra-low-power Edge AI,” a Presenta...“State-space Models vs. Transformers for Ultra-low-power Edge AI,” a Presenta...
“State-space Models vs. Transformers for Ultra-low-power Edge AI,” a Presenta...
Edge AI and Vision Alliance
 
Introduction to Typescript - GDG On Campus EUE
Introduction to Typescript - GDG On Campus EUEIntroduction to Typescript - GDG On Campus EUE
Introduction to Typescript - GDG On Campus EUE
Google Developer Group On Campus European Universities in Egypt
 
MCP vs A2A vs ACP: Choosing the Right Protocol | Bluebash
MCP vs A2A vs ACP: Choosing the Right Protocol | BluebashMCP vs A2A vs ACP: Choosing the Right Protocol | Bluebash
MCP vs A2A vs ACP: Choosing the Right Protocol | Bluebash
Bluebash
 
What is Oracle EPM A Guide to Oracle EPM Cloud Everything You Need to Know
What is Oracle EPM A Guide to Oracle EPM Cloud Everything You Need to KnowWhat is Oracle EPM A Guide to Oracle EPM Cloud Everything You Need to Know
What is Oracle EPM A Guide to Oracle EPM Cloud Everything You Need to Know
SMACT Works
 
LSNIF: Locally-Subdivided Neural Intersection Function
LSNIF: Locally-Subdivided Neural Intersection FunctionLSNIF: Locally-Subdivided Neural Intersection Function
LSNIF: Locally-Subdivided Neural Intersection Function
Takahiro Harada
 
FCF- Getting Started in Cybersecurity 3.0
FCF- Getting Started in Cybersecurity 3.0FCF- Getting Started in Cybersecurity 3.0
FCF- Getting Started in Cybersecurity 3.0
RodrigoMori7
 
Improving Developer Productivity With DORA, SPACE, and DevEx
Improving Developer Productivity With DORA, SPACE, and DevExImproving Developer Productivity With DORA, SPACE, and DevEx
Improving Developer Productivity With DORA, SPACE, and DevEx
Justin Reock
 
Oracle Cloud Infrastructure Generative AI Professional
Oracle Cloud Infrastructure Generative AI ProfessionalOracle Cloud Infrastructure Generative AI Professional
Oracle Cloud Infrastructure Generative AI Professional
VICTOR MAESTRE RAMIREZ
 
Jeremy Millul - A Talented Software Developer
Jeremy Millul - A Talented Software DeveloperJeremy Millul - A Talented Software Developer
Jeremy Millul - A Talented Software Developer
Jeremy Millul
 
vertical-cnc-processing-centers-drillteq-v-200-en.pdf
vertical-cnc-processing-centers-drillteq-v-200-en.pdfvertical-cnc-processing-centers-drillteq-v-200-en.pdf
vertical-cnc-processing-centers-drillteq-v-200-en.pdf
AmirStern2
 
Integration of Utility Data into 3D BIM Models Using a 3D Solids Modeling Wor...
Integration of Utility Data into 3D BIM Models Using a 3D Solids Modeling Wor...Integration of Utility Data into 3D BIM Models Using a 3D Solids Modeling Wor...
Integration of Utility Data into 3D BIM Models Using a 3D Solids Modeling Wor...
Safe Software
 
Agentic AI: Beyond the Buzz- LangGraph Studio V2
Agentic AI: Beyond the Buzz- LangGraph Studio V2Agentic AI: Beyond the Buzz- LangGraph Studio V2
Agentic AI: Beyond the Buzz- LangGraph Studio V2
Shashikant Jagtap
 
Your startup on AWS - How to architect and maintain a Lean and Mean account
Your startup on AWS - How to architect and maintain a Lean and Mean accountYour startup on AWS - How to architect and maintain a Lean and Mean account
Your startup on AWS - How to architect and maintain a Lean and Mean account
angelo60207
 
Creating an Accessible Future-How AI-powered Accessibility Testing is Shaping...
Creating an Accessible Future-How AI-powered Accessibility Testing is Shaping...Creating an Accessible Future-How AI-powered Accessibility Testing is Shaping...
Creating an Accessible Future-How AI-powered Accessibility Testing is Shaping...
Impelsys Inc.
 
Your startup on AWS - How to architect and maintain a Lean and Mean account J...
Your startup on AWS - How to architect and maintain a Lean and Mean account J...Your startup on AWS - How to architect and maintain a Lean and Mean account J...
Your startup on AWS - How to architect and maintain a Lean and Mean account J...
angelo60207
 
DevOps in the Modern Era - Thoughtfully Critical Podcast
DevOps in the Modern Era - Thoughtfully Critical PodcastDevOps in the Modern Era - Thoughtfully Critical Podcast
DevOps in the Modern Era - Thoughtfully Critical Podcast
Chris Wahl
 
ISOIEC 42005 Revolutionalises AI Impact Assessment.pptx
ISOIEC 42005 Revolutionalises AI Impact Assessment.pptxISOIEC 42005 Revolutionalises AI Impact Assessment.pptx
ISOIEC 42005 Revolutionalises AI Impact Assessment.pptx
AyilurRamnath1
 
7 Salesforce Data Cloud Best Practices.pdf
7 Salesforce Data Cloud Best Practices.pdf7 Salesforce Data Cloud Best Practices.pdf
7 Salesforce Data Cloud Best Practices.pdf
Minuscule Technologies
 
Scaling GenAI Inference From Prototype to Production: Real-World Lessons in S...
Scaling GenAI Inference From Prototype to Production: Real-World Lessons in S...Scaling GenAI Inference From Prototype to Production: Real-World Lessons in S...
Scaling GenAI Inference From Prototype to Production: Real-World Lessons in S...
Anish Kumar
 
National Fuels Treatments Initiative: Building a Seamless Map of Hazardous Fu...
National Fuels Treatments Initiative: Building a Seamless Map of Hazardous Fu...National Fuels Treatments Initiative: Building a Seamless Map of Hazardous Fu...
National Fuels Treatments Initiative: Building a Seamless Map of Hazardous Fu...
Safe Software
 
“State-space Models vs. Transformers for Ultra-low-power Edge AI,” a Presenta...
“State-space Models vs. Transformers for Ultra-low-power Edge AI,” a Presenta...“State-space Models vs. Transformers for Ultra-low-power Edge AI,” a Presenta...
“State-space Models vs. Transformers for Ultra-low-power Edge AI,” a Presenta...
Edge AI and Vision Alliance
 
MCP vs A2A vs ACP: Choosing the Right Protocol | Bluebash
MCP vs A2A vs ACP: Choosing the Right Protocol | BluebashMCP vs A2A vs ACP: Choosing the Right Protocol | Bluebash
MCP vs A2A vs ACP: Choosing the Right Protocol | Bluebash
Bluebash
 
What is Oracle EPM A Guide to Oracle EPM Cloud Everything You Need to Know
What is Oracle EPM A Guide to Oracle EPM Cloud Everything You Need to KnowWhat is Oracle EPM A Guide to Oracle EPM Cloud Everything You Need to Know
What is Oracle EPM A Guide to Oracle EPM Cloud Everything You Need to Know
SMACT Works
 
LSNIF: Locally-Subdivided Neural Intersection Function
LSNIF: Locally-Subdivided Neural Intersection FunctionLSNIF: Locally-Subdivided Neural Intersection Function
LSNIF: Locally-Subdivided Neural Intersection Function
Takahiro Harada
 
FCF- Getting Started in Cybersecurity 3.0
FCF- Getting Started in Cybersecurity 3.0FCF- Getting Started in Cybersecurity 3.0
FCF- Getting Started in Cybersecurity 3.0
RodrigoMori7
 
Improving Developer Productivity With DORA, SPACE, and DevEx
Improving Developer Productivity With DORA, SPACE, and DevExImproving Developer Productivity With DORA, SPACE, and DevEx
Improving Developer Productivity With DORA, SPACE, and DevEx
Justin Reock
 
Oracle Cloud Infrastructure Generative AI Professional
Oracle Cloud Infrastructure Generative AI ProfessionalOracle Cloud Infrastructure Generative AI Professional
Oracle Cloud Infrastructure Generative AI Professional
VICTOR MAESTRE RAMIREZ
 

Play Framework: async I/O with Java and Scala

  • 2. LinkedIn uses a service oriented architecture (SOA)
  • 3. 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
  • 4. Services communicate with each other via remote calls
  • 5. Profile frontend Profile backend /profile/123 HTTP request
  • 6. Profile frontend Profile backend JSON response { "id": 123, "first": "Yevgeniy", "last": "Brikman" }
  • 7. Most people are used to synchronous I/O when making requests between servers
  • 8. The most popular frameworks typically use one-thread-per-request and blocking I/O
  • 9. 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
  • 10. Evented servers have one thread/process per CPU core and use non-blocking I/O
  • 11. 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 call http.request(options, callback); console.log('This line may execute before the callback!');
  • 12. Why threaded vs. evented matters for LinkedIn
  • 13. 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
  • 14. I/O is very expensive http://www.eecs.berkeley.edu/~rcs/research/interactive_latency.html
  • 15. In a threaded server, threads spend most of the time idle, waiting on I/O
  • 16. 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 overhead b. Context switching overhead 3. Too few threads in the thread pool: a. Run out of threads, latency goes up b. Sensitive to downstream latency!
  • 25. This is thread pool hell
  • 26. Play is built on top of Netty, so it supports non- blocking I/O
  • 27. NIO benefits 1. No sensitivity to downstream slowness 2. Easy to parallelize I/O 3. Supports many concurrent and long-running connections, enabling: a. WebSockets b. Comet c. Server-Sent Events
  • 28. This talk is a brief introduction to writing asynchronous code with the Play Framework.
  • 29. For each section, I will include simplified examples: first in Java and then Scala.
  • 30. The world’s largest professional network
  • 31. at We've been using Play in production for more than 6 months
  • 32. A few apps built on Play
  • 35. Polls (frontend + backend)
  • 37. About me Leading the Play project as part of LinkedIn's Service Infrastructure Team. Also: hackdays, engineering blog, incubator, open source.
  • 38. Outline 1. A quick intro to Play 2. Basic async code 3. map and flatMap 4. Parallel and sequential 5. Errors and timeouts 6. Coming soon
  • 39. Outline 1. A quick intro to Play 2. Basic async code 3. map and flatMap 4. Parallel and sequential 5. Errors and timeouts 6. Coming soon
  • 40. Download and install Play from http://www.playframework.com
  • 41. > play new my-app
  • 42. > play idea > play eclipse
  • 45. Application layout app → Application sources └ assets → Compiled asset sources └ controllers → Application controllers └ models → Application business layer └ views → Templates conf → Configurations files └ application.conf → Main configuration file └ routes → Routes definition public → Public assets └ stylesheets → CSS files └ javascripts → Javascript files └ images → Image files project → sbt configuration files └ Build.scala → Application build script └ plugins.sbt → sbt plugins lib → Unmanaged libraries dependencies logs → Standard logs folder target → Generated stuff test → Unit or functional tests
  • 46. Let's get a feel for Play by creating a Java Controller
  • 47. 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
  • 48. Don't worry about the use of static. Yes, Play supports IOC. Using static (and other shortcuts) lets me keep the examples simple.
  • 49. GET /hello controllers.HelloWorld.index() Expose the controller/action at a URL conf/routes
  • 52. public class HelloWorld extends Controller { public static Result index(String name) { return ok("Hello " + name); } } Add a parameter app/controllers/HelloWorld.java
  • 53. GET /hello controllers.HelloWorld.index( name) Read the parameter from the query string conf/routes
  • 55. GET /hello/:name controllers.HelloWorld.index(name) Read the parameter from the URL instead conf/routes
  • 57. 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
  • 58. GET /hello/:name/ :age controllers.HelloWorld.index(name: String, age: Int) Add the parameter. Note the type checking! conf/routes
  • 60. @(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
  • 61. 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
  • 63. Play also natively supports Scala
  • 64. 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") } }
  • 65. GET /scala controllers.HelloWorldScala.index() Add it to the routes file as usual conf/routes
  • 67. Outline 1. A quick intro to Play 2. Basic async code 3. map and flatMap 4. Parallel and sequential 5. Errors and timeouts 6. Coming soon
  • 68. Let's use Play's Web Services library (WS) to make some non-blocking HTTP calls
  • 69. 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
  • 70. A Promise<T> will eventually contain the value T (or an error)
  • 71. (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; } }
  • 72. 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>
  • 73. GET /proxy controllers.Proxy.index(url) Add this endpoint to the routes file conf/routes
  • 75. We just built a completely non-blocking proxy!
  • 76. 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
  • 77. Refresh the page and the logs show the HTTP call really is non-blocking
  • 78. Let's create the same Proxy in Scala
  • 79. 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
  • 80. A Future[T] will eventually contain the value T (or an error)
  • 81. (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)
  • 82. 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]
  • 83. GET /scala/proxy controllers.ProxyScala.index (url) Add this endpoint to the routes file conf/routes
  • 85. Outline 1. A quick intro to Play 2. Basic async code 3. map and flatMap 4. Parallel and sequential 5. Errors and timeouts 6. Coming soon
  • 86. What is this "map" thing all about?
  • 87. It's easiest to think about map with Lists.
  • 88. val names = List("Jim", "Dean", "Kunal") Let's start with a List of Strings
  • 89. val names = List("Jim", "Dean", "Kunal") def lower(str: String): String = str.toLowerCase And a simple function that converts a String to lowercase
  • 90. 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
  • 91. 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
  • 92. We saw map transform a List[String] into a new List[String]. Can we transform a List[X] into some other type List[Y]?
  • 93. 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
  • 94. 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
  • 95. Now we see that map can transform a List [String] into a new List[Int].
  • 96. class List[A] { def map[B](f: A => B): List[B] } More generally, this is map's signature
  • 97. Let's look at one more example
  • 98. 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
  • 99. 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?
  • 100. class List[A] { def flatMap[B](f: A => List[B]): List[B] } We can use flatMap , which will combine (flatten) any nested Lists
  • 101. 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
  • 102. map and flatMap are defined on any "collection" or "container": List, Set, Map, etc
  • 103. 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
  • 104. Futures and Promises are also "containers": they just happen to contain 1 item.
  • 105. 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]
  • 106. The "container" class controls when the function passed to map or flatMap actually gets applied!
  • 107. Outline 1. A quick intro to Play 2. Basic async code 3. map and flatMap 4. Parallel and sequential 5. Errors and timeouts 6. Coming soon
  • 108. Making I/O requests in parallel is essential for performance in a Service Oriented Architecture
  • 109. 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();
  • 110. Let's fetch 3 websites in parallel and time them
  • 111. 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; }
  • 112. 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"); } }
  • 113. 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); } }
  • 114. 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)); } })); } }
  • 115. GET /parallel controllers.Parallel.index() Add it to the routes file conf/routes
  • 117. How about parallel requests in Scala?
  • 118. 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)
  • 119. 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") } }
  • 120. 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)) } }
  • 121. 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))) } } }
  • 122. GET /scala/parallel controllers.ParallelScala.index() Add it to the routes file conf/routes
  • 124. If parallel is the default, how do you do sequential steps that depend on each other?
  • 125. Example: make a request to duckduckgo's instant answer API (step 1) and proxy an image from the response (step 2)
  • 126. 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(); } }
  • 127. 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")); } })); } }
  • 128. GET /lucky controllers.LuckyImage.index (url) Add it to the routes file conf/routes
  • 130. 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")); } }); } }));
  • 132. Ok, let's try the same example in Scala
  • 133. 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() } }
  • 134. 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")) } } } } }
  • 137. In both Java and Scala, you order async actions sequentially by nesting map and flatMap calls.
  • 138. 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 } } } }); } });
  • 141. ParSeq is a framework that makes it easier to write asynchronous code in Java
  • 142. 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"))
  • 143. 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)
  • 144. 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)
  • 145. 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) )
  • 146. We'll soon be open sourcing a plugin soon to make it easy to use ParSeq in Play.
  • 148. Syntactic sugar built into Scala. Translates into map and flatMap calls without the nesting.
  • 149. Instead of this... aFuture.flatMap { a => bFuture.flatMap { b => cFuture.flatMap { c => dFuture.map { d => // Build a result using a, b, c, d } } } }
  • 150. 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 }
  • 151. 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 }
  • 152. 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 }
  • 153. Sequence comprehensions provide a clean and consistent API for async code: you can read it top to bottom!
  • 154. Outline 1. A quick intro to Play 2. Basic async code 3. map and flatMap 4. Parallel and sequential 5. Errors and timeouts 6. Coming soon
  • 155. With a single server, the odds of hitting an error are relatively low
  • 156. 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
  • 157. 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%
  • 158. Here is how to make your async code more resilient to errors and slow performance
  • 159. 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); } })); } }
  • 160. 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) } } } }
  • 161. GET /errors controllers.Errors.index(url) GET /scala/errors controllers.ErrorsScala.index(url) Add to the routes file conf/routes
  • 164. If some of the data you fetch isn't required to render the page, you can use an Option pattern
  • 165. 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(); } }); }
  • 166. 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 } } })); }
  • 167. The same pattern is even prettier in Scala, as Option is a first-class citizen of the language
  • 168. 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 } }
  • 169. 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 } } } }
  • 170. Sometimes, waiting too long for data is worse than not showing that data at all
  • 171. You can create a Promise that will be redeemed with someValue after the specified timeout Promise.timeout(someValue, 500, TimeUnit.MILLISECONDS)
  • 172. 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);
  • 173. 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 } } });
  • 174. 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 } } }
  • 175. Outline 1. A quick intro to Play 2. Basic async code 3. map and flatMap 4. Parallel and sequential 5. Errors and timeouts 6. Coming soon
  • 176. play-async-plugin We'll be open sourcing this plugin soon
  • 177. It is a collection of async utilities, including...
  • 178. 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)
  • 179. Config-driven SLAs (timeouts) for any async I/O sla.plugin.slas = [ { resources: ["/profile", "/companies"] timeout: "2s" }, { resources: ["/pymk", "/skills/*"], timeout: "500ms" } ]
  • 180. In-browser visualization of all async requests for a page, including timing, responses, and errors
  • 181. We're just getting started with Play! We'll be sharing more as we go.