A modular, polyglot architecture has many advantages but it also adds complexity since each incoming request typically fans out to multiple distributed services. For example, in an online store application the information on a product details page - description, price, recommendations, etc - comes from numerous services. To minimize response time and improve scalability, these services must be invoked concurrently. However, traditional concurrency mechanisms are low-level, painful to use and error-prone. In this talk you will learn about some powerful yet easy to use abstractions for consuming web services asynchronously. We will compare the various implementations of futures that are available in Java, Scala and JavaScript. You will learn how to use reactive observables, which are asynchronous data streams, to access web services from both Java and JavaScript. We will describe how these mechanisms let you write asynchronous code in a very straightforward, declarative fashion.
Unblocking The Main Thread Solving ANRs and Frozen Frames
Consuming web services asynchronously with Futures and Rx Observables (svcc, svcc2013)
1. @crichardson
Consuming web services
asynchronously with Futures
and Rx Observables
Chris Richardson
Author of POJOs in Action
Founder of the original CloudFoundry.com
@crichardson
chris@chrisrichardson.net
http://plainoldobjects.com
9. @crichardson
Agenda
The need for concurrency
Simplifying concurrent code with Futures
Taming callback hell with JavaScript promises
Consuming asynchronous streams with Reactive
Extensions
15. @crichardson
Application architecture
Product Info Service
Desktop
browser
Native
mobile
client
REST
REST
REST
Recomendation
Service
Review Service
Front end server
API gatewayREST
Mobile
browser
Web Application
HTML/
JSON
JSON
18. @crichardson
Handling getProductDetails() -
in parallel
Front-end
server
Product Info Service
Recommendations
Service
Review
Service
getProductInfo()
getRecommendations()
getReviews()
getProductDetails()
Lower response
time :-)
19. @crichardson
Implementing a concurrent
REST client
Thread-pool based approach
executorService.submit(new Callable(...))
Simpler but less scalable - lots of idle threads
consuming memory
Event-driven approach
NIO with completion callbacks
More complex but more scalable
20. @crichardson
The front-end server must handle
partial failure of backend services
Front-end
server
Product Info Service
Recommendations
Service
Review
Service
getProductInfo()
getRecommendations()
getReviews()
getProductDetails()
X
How to provide
a good user
experience?
21. @crichardson
Agenda
The need for concurrency
Simplifying concurrent code with Futures
Taming callback hell with JavaScript promises
Consuming asynchronous streams with Reactive
Extensions
22. @crichardson
Futures are a great
concurrency abstraction
http://en.wikipedia.org/wiki/Futures_and_promises
24. @crichardson
Benefits
Client does not know how the asynchronous operation is
implemented
Client can invoke multiple asynchronous operations and
gets a Future for each one.
29. @crichardson
Blocks Tomcat thread until
Future completes
Not bad but...
class ProductDetailsService
def getProductDetails(productId: Long): ProductDetails = {
val productInfo =
productInfoFuture.get(300, TimeUnit.MILLISECONDS)
Not so scalable :-(
30. @crichardson
... and also...
Java Futures work well for a single-level of asynchronous
execution
BUT
#fail for more complex, scalable scenarios
Difficult to compose and coordinate multiple concurrent
operations
See this blog post for more details:
http://techblog.netflix.com/2013/02/rxjava-netflix-api.html
31. @crichardson
Better: Futures with callbacks
no blocking!
val f : Future[Int] = Future { ... }
f onSuccess {
case x : Int => println(x)
}
f onFailure {
case e : Exception =>
println("exception thrown")
}
Guava ListenableFutures, Spring 4 ListenableFuture
Java 8 CompletableFuture, Scala Futures
33. @crichardson
map() is asynchronous - uses callbacks
class Future[T] {
def map[S](f: T => S): Future[S] = {
val p = Promise[S]()
onSuccess {
case r => p success f(r)
}
onFailure {
case t => p failure t
}
p.future
}
When ‘this’ completes
propagate outcome to ‘p’
Return new Future
34. @crichardson
Chaining using flatmap()
def asyncOperation() = Future { ... ; 3 }
def asyncSquare(x : Int) : Future[Int] = Future { x * x}
val r = asyncOperation().flatMap { x => asyncSquare(x) }
assertEquals(9, Await.result(r, 1 second))
Scala, Java 8 CompletableFuture (partially)
Chaining asynchronous
operations
35. @crichardson
Combining futures
val f1 = Future { ... ; 1}
val f2 = Future { .... ; 2}
val fzip = f1 zip f2
assertEquals((1, 2), Await.result(fzip, 1 second))
def asyncOp(x : Int) = Future { x * x}
val f = Future.sequence((1 to 5).map { x => asyncOp(x) })
assertEquals(List(1, 4, 9, 16, 25),
Await.result(f, 1 second))
Scala, Java 8 CompletableFuture (partially)
Combines two futures
Transforms list of futures to a
future containing a list
36. @crichardson
Scala futures are Monads
def callB() : Future[...] = ...
def callC() : Future[...] = ...
def callD() : Future[...] = ...
val result = for {
(b, c) <- callB() zip callC();
d <- callD(b, c)
} yield d
result onSuccess { .... }
Two calls execute in parallel
And then invokes D
Get the result of D
37. @crichardson
‘for’ is shorthand for map() and flatMap()
(callB() zip callC()) flatMap { r1 =>
val (b, c) = r1
callD(b, c) map { d => d }
}
for {
(b, c) <- callB() zip callC();
d <- callD(b, c)
} yield d
38. @crichardson
Scala Future + RestTemplate
import scala.concurrent.Future
@Component
class ProductInfoService {
def getProductInfo(productId: Long): Future[ProductInfo]
= {
Future { restTemplate.getForObject(....) }
}
}
Executes in thread pool
Scala Future
39. @crichardson
Scala Future + RestTemplate
class ProductDetailsService @Autowired()(....) {
def getProductDetails(productId: Long) = {
val productInfoFuture = productInfoService.getProductInfo(productId)
val recommendationsFuture =
recommendationService.getRecommendations(productId)
val reviewsFuture = reviewService.getReviews(productId)
for (((productInfo, recommendations), reviews) <-
productInfoFuture zip recommendationsFuture zip reviewsFuture)
yield ProductDetails(productInfo, recommendations, reviews)
}
}
Non-blocking!
40. @crichardson
Async Spring MVC + Scala
Futures
@Controller
class ProductController ... {
@RequestMapping(Array("/productdetails/{productId}"))
@ResponseBody
def productDetails(@PathVariable productId: Long) = {
val productDetails =
productDetailsService.getProductDetails(productId)
val result = new DeferredResult[ProductDetails]
productDetails onSuccess {
case r => result.setResult(r)
}
productDetails onFailure {
case t => result.setErrorResult(t)
}
result
}
Convert Scala
Future to
Spring MVC
DeferredResult
42. @crichardson
New in Spring 4
Mirrors RestTemplate
Methods return a ListenableFuture
ListenableFuture = JDK 7 Future + callback methods
Can use HttpComponents NIO-based AsyncHttpClient
Spring AsyncRestTemplate
43. @crichardson
Using the AsyncRestTemplate
http://hc.apache.org/httpcomponents-asyncclient-dev/
val asyncRestTemplate = new AsyncRestTemplate(
new HttpComponentsAsyncClientHttpRequestFactory())
override def getProductInfo(productId: Long) = {
val listenableFuture =
asyncRestTemplate.getForEntity("{baseUrl}/productinfo/{productId}",
classOf[ProductInfo],
baseUrl, productId)
toScalaFuture(listenableFuture).map { _.getBody }
}
Convert to Scala Future and get entity
44. @crichardson
Converting ListenableFuture to
Scala Future
implicit def toScalaFuture[T](f :
ListenableFuture[T]) : Future[T] = {
val p = promise[T]()
f.addCallback(new ListenableFutureCallback[T] {
def onSuccess(result: T) { p.success(result)}
def onFailure(t: Throwable) { p.failure(t) }
})
p.future
}
The producer side of Scala Futures
Supply outcome
Return future
46. @crichardson
If recommendation service is
down...
Never responds front-end server waits indefinitely
Consumes valuable front-end server resources
Page never displayed and customer gives up
Returns an error
Error returned to the front-end server error page is displayed
Customer gives up
47. @crichardson
Fault tolerance at Netflix
Network timeouts and retries
Invoke remote services via a bounded thread pool
Use the Circuit Breaker pattern
On failure:
return default/cached data
return error to caller
Implementation: https://github.com/Netflix/Hystrix
http://techblog.netflix.com/2012/02/fault-tolerance-in-high-
volume.html
48. @crichardson
How to handling partial
failures?
productDetailsFuture zip
recommendationsFuture zip
reviewsFuture
Fails if any Future
has failed
50. @crichardson
Agenda
The need for concurrency
Simplifying concurrent code with Futures
Taming callback hell with JavaScript promises
Consuming asynchronous streams with Reactive
Extensions
51. @crichardson
Why solve this problem for
JavaScript?
Browser invokes web services
Implement front-end server/API gateway using NodeJS
56. @crichardson
Messy callback code
getProductDetails = (req, res) ->
productId = req.params.productId
result = {productId: productId}
makeHandler = (key) ->
(error, clientResponse, body) ->
if clientResponse.statusCode != 200
res.status(clientResponse.statusCode)
res.write(body)
res.end()
else
result[key] = JSON.parse(body)
if (result.productInfo and result.recommendations and result.reviews)
res.json(result)
request("http://localhost:3000/productdetails/" + productId, makeHandler("productInfo"))
request("http://localhost:3000/recommendations/" + productId,
makeHandler('recommendations'))
request("http://localhost:3000/reviews/" + productId, makeHandler('reviews'))
app.get('/productinfo/:productId', getProductInfo)
Returns a callback
function
Have all three requests
completed?
57. @crichardson
Simplifying code with
Promises (a.k.a. Futures)
Functions return a promise - no callback parameter
A promise represents an eventual outcome
Use a library of functions for transforming and composing
promises
Promises/A+ specification - http://promises-aplus.github.io/promises-spec
58. @crichardson
Basic promise API
promise2 = promise1.then(fulfilledHandler, errorHandler, ...)
called when promise1 is fulfilled
returns a promise or value
resolved with outcome of
callback
called when promise1 is
rejected
59. About when.js
Rich API = Promises spec
+ use full extensions
Creation:
when.defer()
when.reject()...
Combinators:
all, some, join, map,
reduce
Other:
Calling non-promise
code
timeout
....
https://github.com/cujojs/when
65. @crichardson
Agenda
The need for concurrency
Simplifying concurrent code with Futures
Taming callback hell with JavaScript promises
Consuming asynchronous streams with Reactive
Extensions
66. @crichardson
Let’s imagine you have a
stream of trades
and
you need to calculate the rolling
average price of each stock
70. @crichardson
Introducing Reactive
Extensions (Rx)
The Reactive Extensions (Rx) is a library
for composing asynchronous and event-
based programs using observable
sequences and LINQ-style query
operators. Using Rx, developers
represent asynchronous data streams
with Observables , query
asynchronous data streams using
LINQ operators , and .....
https://rx.codeplex.com/
72. @crichardson
RxJava core concepts
class Observable<T> {
Subscription subscribe(Observer<T> observer)
...
}
interface Observer<T> {
void onNext(T args)
void onCompleted()
void onError(Throwable e)
}
Notifies
An asynchronous
stream of items
Used to
unsubscribe
73. @crichardson
Comparing Observable to...
Observer pattern - similar but adds
Observer.onComplete()
Observer.onError()
Iterator pattern - mirror image
Push rather than pull
Future - similar but
Represents a stream of multiple values
77. @crichardson
Combining observables
class Observable<T> {
static <R,T1,T2>
Observable<R> zip(Observable<T0> o1,
Observable<T1> o2,
Func2<T1,T2,R> reduceFunction)
...
}
Invoked with pairs of
items from o1 and o2
Stream of results from
reduceFunction
78. @crichardson
Creating observables from
data
class Observable<T> {
static Observable<T> from(Iterable<T> iterable]);
static Observable<T> from(T items ...]);
static Observable<T> from(Future<T> future]);
...
}
79. @crichardson
Arranges to call obs.onNext/
onComplete/...
Creating observables from
event sources
Observable<T> o = Observable.create(
new SubscriberFunc<T> () {
Subscription onSubscribe(Observer<T> obs) {
... connect to event source....
return new Subscriber () {
void unsubscribe() { ... };
};
});
Called
once for
each
Observer
Called when
unsubscribing
93. @crichardson
Using merge()
merge()
APPL : 401
IBM : 405
CAT : 405 ...
APPL: 403
APPL : 401 IBM : 405 CAT : 405 APPL: 403
Observable<Observable<AveragePrice>>
Observable<AveragePrice>
94. @crichardson
Calculating average prices
def calculateAverages(trades: Observable[Trade]): Observable[AveragePrice] = {
trades.groupBy(_.symbol).map { symbolAndTrades =>
val (symbol, tradesForSymbol) = symbolAndTrades
...
tradesForSymbol.window(...).map {
windowOfTradesForSymbol =>
windowOfTradesForSymbol.fold((0.0, 0, List[Double]())) { (soFar, trade) =>
val (sum, count, prices) = soFar
(sum + trade.price, count + 1, trade.price +: prices)
} map { x =>
val (sum, length, prices) = x
AveragePrice(symbol, sum / length, prices)
}
}.flatMap(identity)
}.flatMap(identity)
}
95. @crichardson
RxJS / NodeJS example
var rx = require("rx");
function tailFile (fileName) {
var self = this;
var o = rx.Observable.create(function (observer) {
var watcher = self.tail(fileName, function (line) {
observer.onNext(line);
});
return function () {
watcher.close();
}
});
return o.map(parseLogLine);
}
function parseLogLine(line) { ... }
96. @crichardson
Rx in the browser -
implementing completion
TextChanges(input)
.DistinctUntilChanged()
.Throttle(TimeSpan.FromMilliSeconds(10))
.Select(word=>Completions(word))
.Switch()
.Subscribe(ObserveChanges(output));
Your Mouse is a Database
http://queue.acm.org/detail.cfm?id=2169076
Ajax call returning
Observable
Change events Observable
Display completions
97. @crichardson
Summary
Consuming web services asynchronously is essential
Scala-style are a powerful concurrency abstraction
Rx Observables are even more powerful
Rx Observables are a unifying abstraction for a wide
variety of use cases