futures and rx observables: powerful abstractions for consuming web services asynchronously...

67
@crichardson Consuming web services asynchronously with Futures and Rx Observables Chris Richardson Author of POJOs in Action Founder of the original CloudFoundry.com @crichardson [email protected] http://plainoldobjects.com

Upload: chris-richardson

Post on 09-May-2015

2.005 views

Category:

Technology


2 download

DESCRIPTION

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 on the JVM. You will learn how to access web services using reactive observables (RxJava), which are asynchronous data streams. We will describe how these mechanisms let you write asynchronous code in a very straightforward, declarative fashion.

TRANSCRIPT

Page 1: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Consuming web services asynchronously with Futures

and Rx ObservablesChris Richardson

Author of POJOs in ActionFounder of the original CloudFoundry.com

@[email protected]://plainoldobjects.com

Page 2: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Presentation goal

Learn how to use (Scala) Futures and Rx

Observables to write simple yet robust and scalable

concurrent code

Page 3: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

About Chris

Page 4: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

About Chris

Founder of a buzzword compliant (stealthy, social, mobile, big data, machine learning, ...) startup

Consultant helping organizations improve how they architect and deploy applications using cloud, micro services, polyglot applications, NoSQL, ...

Page 5: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Agenda

The need for concurrency

Simplifying concurrent code with Futures

Consuming asynchronous streams with Reactive Extensions

Page 6: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Let’s imagine you are building an online store

Page 7: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Shipping

Recomendations

ProductInfo

Reviews

Page 8: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Reviews

ProductInfo Sales

ranking

Page 9: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Related books

Viewing history

Forum

Page 10: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

+ mobile apps

Page 11: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Application architecture

Product Info ServiceDesktop browser

Native mobile client

REST

REST

REST

Recomendation Service

Review Service

Front end server

API gatewayREST

Mobile browser

Web Application

HTML/JSON

JSON

Page 12: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

How does the client get product details?

Front-end server

Product Info Service

RecommendationsService

ReviewService

getProductInfo()

getRecommendations()

getReviews()

Browser/Client

?

Page 13: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Product details - client-side aggregation

Front-end server

Product Info Service

RecommendationsService

ReviewService

getProductInfo()

getRecommendations()

getReviews()

getProductInfo()

Browser/Client

getRecommendations()

getReviews()

Requiresgood network performance

Page 14: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Product details - server-side aggregation

Front-end server

Product Info Service

RecommendationsService

ReviewService

getProductInfo()

getRecommendations()

getReviews()

getProductDetails()Browser/

Client

One roundtrip

HTML or JSON

Page 15: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Product details - server-side aggregation: sequentially

Front-end server

Product Info Service

RecommendationsService

ReviewService

getProductInfo()

getRecommendations()

getReviews()

getProductDetails()

Higher response time :-(

Page 16: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Product details - server-side aggregation: parallel

Front-end server

Product Info Service

RecommendationsService

ReviewService

getProductInfo()

getRecommendations()

getReviews()

getProductDetails()

Lower response time :-)

Page 17: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@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

And it must handle partial failures

Page 18: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Agenda

The need for concurrency

Simplifying concurrent code with Futures

Consuming asynchronous streams with Reactive Extensions

Page 19: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Futures are a great concurrency abstraction

http://en.wikipedia.org/wiki/Futures_and_promises

Page 20: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Worker thread or event-driven

Main threadHow futures work

Outcome

Future

Client

get

Asynchronous operation

set

initiates

Page 21: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

BenefitsSimple way for two concurrent activities to communicate safely

Abstraction:

Client does not know how the asynchronous operation is implemented

Easy to implement scatter/gather:

Scatter: Client can invoke multiple asynchronous operations and gets a Future for each one.

Gather: Get values from the futures

Page 22: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Front-end server design: handling GetProductDetails request

ProductDetailsController

getProductDetails()

ProductDetailsService

getProductDetails()

ProductInfoService

getProductInfo()

ReviewService

getReviews()

RecommendationService

getRecommendations()

RestTemplate

Proxies

Page 23: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

REST client using Spring @Async

@Componentclass ProductInfoServiceImpl extends ProducInfoService { val restTemplate : RestTemplate = ...

@Async def getProductInfo(productId: Long) = { new AsyncResult(restTemplate.getForObject(....)...) }

}

Execute asynchronously in

thread pool

A fulfilled Future

trait ProductInfoService { def getProductInfo(productId: Long):

java.util.concurrent.Future[ProductInfo]}

Page 24: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

ProductDetailsService@Componentclass ProductDetailsService @Autowired()(productInfoService: ProductInfoService, reviewService: ReviewService, recommendationService: RecommendationService) {

def getProductDetails(productId: Long): ProductDetails = { val productInfoFuture = productInfoService.getProductInfo(productId)

}

}

val recommendationsFuture = recommendationService.getRecommendations(productId)val reviewsFuture = reviewService.getReviews(productId)

val productInfo = productInfoFuture.get(300, TimeUnit.MILLISECONDS) val recommendations = recommendationsFuture.get(10, TimeUnit.MILLISECONDS) val reviews = reviewsFuture.get(10, TimeUnit.MILLISECONDS)

ProductDetails(productInfo, recommendations, reviews)

Page 25: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

ProductController

@Controllerclass ProductController @Autowired()(productDetailsService : ProductDetailsService) {

@RequestMapping(Array("/productdetails/{productId}")) @ResponseBody def productDetails(@PathVariable productId: Long) =

productDetailsService.getProductDetails(productId)

Page 26: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Gathering blocks Tomcat thread until all Futures

complete

Not bad but...

class ProductDetailsService def getProductDetails(productId: Long): ProductDetails = {

val productInfo = productInfoFuture.get(300, TimeUnit.MILLISECONDS)

Not so scalable :-(

Page 27: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@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

Page 28: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Better: Futures with callbacks ⇒ no blocking!

def asyncSquare(x : Int) : Future[Int] = ... x * x...

val f = asyncSquare(25)

Guava ListenableFutures, Spring 4 ListenableFutureJava 8 CompletableFuture, Scala Futures

f onSuccess { case x : Int => println(x)}f onFailure { case e : Exception => println("exception thrown")}

Partial function applied to successful outcome

Applied to failed outcome

Page 29: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

But callback-based scatter/gather

⇒Messy, tangled code(aka. callback hell)

Page 30: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Composable futures hide the mess

val fzip = asyncSquare(5) zip asyncSquare(7)assertEquals((25, 49), Await.result(fzip, 1 second))

val fseq = Future.sequence((1 to 5).map { x => asyncSquare(x) })

assertEquals(List(1, 4, 9, 16, 25), Await.result(fseq, 1 second))

Scala, Java 8 CompletableFuture (partially)

Combines two futures

Transforms list of futures to a future

Page 31: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

zip() is asynchronous

Outcome2

f2

(o1, o2)

f3

f3 = f1 zip f2

Outcome1

f1

Implemented using callbacks

Page 32: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Transforming futures

def asyncPlus(x : Int, y : Int) = ... x + y ...

val future2 = asyncPlus(4, 5).map{ _ * 3 }

assertEquals(27, Await.result(future2, 1 second))

Scala, Java 8 CompletableFuture (partially)

Asynchronously transforms future

Page 33: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Chaining asynchronous operations

val f2 = asyncPlus(5, 8).flatMap { x => asyncSquare(x) }

assertEquals(169, Await.result(f2, 1 second))

Scala, Java 8 CompletableFuture (partially)

Calls asyncSquare() with the eventual outcome of asyncPlus()

Page 34: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Scala futures are Monads

(asyncPlus(3, 5) zip asyncSquare(5)) .flatMap { case (a, b) => asyncPlus(a, b) map { _ * 2 } }

result onSuccess { .... }

Two calls execute in parallel

And then invokes asyncPlus()

x 2

Rewrite using ‘for’

Page 35: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Scala futures are Monads

val result = for { (a, b) <- asyncPlus(3, 5) zip asyncSquare(5); c <- asyncPlus(a, b)} yield c * 2

result onSuccess { .... }

Two calls execute in parallel

And then invokes asyncPlus()

x 2‘for’ is shorthand for map() and flatMap()

Page 36: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

ProductInfoService: using Scala Futures

import scala.concurrent.Future

@Componentclass ProductInfoService {

def getProductInfo(productId: Long): Future[ProductInfo] = { Future { restTemplate.getForObject(....) } }

}

Executed in a threaded pool

Scala Future

Page 37: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

ProductDetailsService: using Scala Futures

class ProductDetailsService ...

def getProductDetails(productId: Long) : Future[ProductDetails] = { val productInfoFuture = productInfoService.getProductInfo(productId) val recommendationsFuture = recommendationService.getRecommendations(productId) val reviewsFuture = reviewService.getReviews(productId)

}}

Gathers data without blocking

Return a Scala Future

for (((productInfo, recommendations), reviews) <- productInfoFuture zip recommendationsFuture zip reviewsFuture) yield ProductDetails(productInfo, recommendations, reviews)

Page 38: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Async ProductController: using Spring MVC DeferredResult

@Controllerclass ProductController ... {

@RequestMapping(Array("/productdetails/{productId}")) @ResponseBody def productDetails(@PathVariable productId: Long)

: DeferredResult[ProductDetails] = { val productDetails = productDetailsService.getProductDetails(productId) val result = new DeferredResult[ProductDetails]

result }

Convert Scala Future to

DeferredResult

Spring MVCDeferredResult

≅Future

productDetails onSuccess { case r => result.setResult(r) } productDetails onFailure { case t => result.setErrorResult(t) }

Page 39: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Servlet layer is asynchronous BUT

the backend uses thread pools⇒

Need event-driven REST client

Page 40: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

New in Spring 4

Mirrors RestTemplate

Can use HttpComponents NIO-based AsyncHttpClient

Methods return a ListenableFuture

JDK 7 Future + callback methods

Spring AsyncRestTemplate

Yet another “Future”!

Page 41: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

ProductInfoService: using the AsyncRestTemplate

http://hc.apache.org/httpcomponents-asyncclient-dev/

class ProductInfoService { 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

Page 42: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Converting ListenableFuture to Scala Future

def toScalaFuture[T](lf : ListenableFuture[T]) : Future[T] = { val p = promise[T]()

lf.addCallback(new ListenableFutureCallback[T] { def onSuccess(result: T) { p.success(result)} def onFailure(t: Throwable) { p.failure(t) } }) p.future }

Creates a promise = producer API

Propagate outcome to promise

Return future

Page 43: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Now everything is non-blocking :-)

We have achieved scaling Nirvana

Page 44: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

WT*#*# is my code doing?

Operations initiated in one thread but fail in another

Lack of a full stack trace can make debugging difficult

Inherent problem of async/event driven programming

Futures make it very easy to forget to handle errorssomeFuture.foreach { handleTheHappyPath }

Error is quietly ignored: similar to an empty catch {} block

Page 45: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Agenda

The need for concurrency

Simplifying concurrent code with Futures

Consuming asynchronous streams with Reactive Extensions

Page 46: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Let’s imagine you have a stream of trades

and you need to calculate the 15

minute rolling average price of each stock

Page 47: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Where is the high-level abstraction that simplifies

solving this problem?

Page 48: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Future[List[T]]

Not applicable to infinite streams

Page 49: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Pipes and Filterse.g. Spring Integration

+Complex event processing (CEP)

Not bad but tends to be an external DSL, heavy weight, statically defined, ...

Page 50: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@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/

Page 51: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

About RxJava

Reactive Extensions (Rx) for the JVM

Original motivation for Netflix was to provide rich Futures

Implemented in Java

Adaptors for Scala, Groovy and Clojure

https://github.com/Netflix/RxJava

Page 52: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

RxJava core concepts

trait Observable[T] { def subscribe(observer : Observer[T]) : Subscription ...}

trait Observer[T] {def onNext(value : T)def onCompleted()def onError(e : Throwable)

}

Notifies

An asynchronous stream of items

Used to unsubscribe

* Note: with v0.17 subscribe() takes a Subscriber = Observer++

Page 53: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@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

Page 54: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

So what?

Page 55: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Fun with observables

val every10Seconds = Observable.interval(10 seconds)

-1 0 1 ...

t=0 t=10 t=20 ...

val oneItem = Observable.items(-1L)

val ticker = oneItem ++ every10Seconds

val subscription = ticker.subscribe ( new Observer[Long] { override def onNext(value: Long) = { println("value=" + value) } }) ... subscription.unsubscribe()

Page 56: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Creating observables

Observable.create({ observer: Observer[T] =>

Subscription{ .... } } })

Function called when

Observer subscribes

Called when observer unsubscribes

...observer.onNext(...)...observer.onCompleted()...observer.onError(...)...

* Note: with v0.17 create() takes a Subscriber = Observer++

Page 57: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Creating observables from Scala Futures

def getTableStatus(tableName: String) = { val future = dynamoDbClient.describeTable(new DescribeTableRequest(tableName)) Observable.create({ observer : Observer[DynamoDbStatus] => future.onComplete { case Success(response) => observer.onNext(DynamoDbStatus(response.getTable.getTableStatus)) observer.onCompleted()

case Failure(t: ResourceNotFoundException) => observer.onNext(DynamoDbStatus("NOT_FOUND")) observer.onCompleted()

case Failure(someError) => observer.onError(someError)

} Subscription({}) }) } Propagate outcome

Query AWS

Page 58: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Transforming observables

val tableStatus = ticker.flatMap { i => logger.info("{}th describe table", i + 1) getTableStatus(name) }

Status1 Status2 Status3 ...

t=0 t=10 t=20 ...

+ Usual collection methods: map(), filter(), take(), drop(), ...

Page 59: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Back to the stream of Trades averaging example...

Page 60: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Calculating averagesclass AverageTradePriceCalculator {

def calculateAverages(trades: Observable[Trade]): Observable[AveragePrice] = { ... }

case class Trade( symbol : String, price : Double, quantity : Int ...)

case class AveragePrice(symbol : String, price : Double, ...)

Page 61: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Using groupBy()APPL : 401 IBM : 405 CAT : 405 APPL: 403

groupBy( (trade) => trade.symbol)

APPL : 401

IBM : 405

CAT : 405 ...

APPL: 403

Observable[GroupedObservable[String, Trade]]

Observable[Trade]

100 300 150 300

Page 62: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Using window()APPL : 401 APPL : 405 APPL : 405 ...

APPL : 401 APPL : 405 APPL : 405

APPL : 405 APPL : 405 APPL : 403

APPL : 405 ...

window(...)

Observable[Trade]

Observable[Observable[Trade]]

30 secs

30 secs

5 minutes

Page 63: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Using foldLeft()

APPL : 402 APPL : 405 APPL : 405

APPL : 404.4

foldLeft(0.0)(_ + _.price) /

foldLeft(0.0)(_ + _.quantity)

Observable[Trade]

Observable[AveragePrice] Singleton

100 200 200

Page 64: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Using flatten()

flatten()

APPL : 401

IBM : 405

CAT : 405 ...

APPL: 403

APPL : 401 IBM : 405 CAT : 405 APPL: 403

Observable[Observable[AveragePrice]]

Observable[AveragePrice]

Page 65: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Calculating average pricesdef calculateAverages(trades: Observable[Trade]): Observable[AveragePrice] = {

trades.groupBy(_.symbol).map { symbolAndTrades => val (symbol, tradesForSymbol) = symbolAndTrades val openingEverySecond =

Observable.items(-1L) ++ Observable.interval(1 seconds) def closingAfterSixSeconds(opening: Any) =

Observable.interval(6 seconds).take(1)

tradesForSymbol.window(...).map { windowOfTradesForSymbol => windowOfTradesForSymbol.fold((0.0, 0, List[Double]())) { (soFar, trade) => val (sum, count, prices) = soFar (sum + trade.price, count + trade.quantity, trade.price +: prices) } map { x => val (sum, length, prices) = x AveragePrice(symbol, sum / length, prices) } }.flatten }.flatten}

Page 66: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Summary

Consuming web services asynchronously is essential

Scala-style Futures are a powerful concurrency abstraction

Rx Observables

even more powerful

unifying abstraction for a wide variety of use cases

Page 67: Futures and Rx Observables: powerful abstractions for consuming web services asynchronously (devnexus2014)

@crichardson

Questions?

@crichardson [email protected]

http://plainoldobjects.com