Reactive Programming with Rx QConSF - November 2014 Ben Christensen - - PowerPoint PPT Presentation

reactive programming with rx
SMART_READER_LITE
LIVE PREVIEW

Reactive Programming with Rx QConSF - November 2014 Ben Christensen - - PowerPoint PPT Presentation

Reactive Programming with Rx QConSF - November 2014 Ben Christensen Developer Edge Engineering at Netflix @benjchristensen http://techblog.netflix.com/ RxJava http://github.com/ReactiveX/RxJava http://reactivex.io Maven Central:


slide-1
SLIDE 1

Ben Christensen

Developer – Edge Engineering at Netflix @benjchristensen

http://techblog.netflix.com/

QConSF - November 2014

Reactive Programming with Rx

slide-2
SLIDE 2

RxJava

http://github.com/ReactiveX/RxJava http://reactivex.io Maven Central: 'io.reactivex:rxjava:1.0.+'

slide-3
SLIDE 3

Iterable<T> pull Observable<T> push T next() throws Exception returns;

  • nNext(T)
  • nError(Exception)
  • nCompleted()
slide-4
SLIDE 4

Iterable<T> pull Observable<T> push T next() throws Exception returns;

  • nNext(T)
  • nError(Exception)
  • nCompleted()

¡// ¡Iterable<String> ¡or ¡Stream<String> ¡ ¡ ¡// ¡that ¡contains ¡75 ¡Strings ¡ ¡getDataFromLocalMemory() ¡ ¡ ¡.skip(10) ¡ ¡ ¡.limit(5) ¡ ¡ ¡.map(s ¡-­‑> ¡s ¡+ ¡"_transformed") ¡ ¡ ¡.forEach(t ¡-­‑> ¡System.out.println("onNext ¡=> ¡" ¡+ ¡t)) ¡ ¡// ¡Observable<String> ¡ ¡ ¡// ¡that ¡emits ¡75 ¡Strings ¡ ¡getDataFromNetwork() ¡ ¡ ¡.skip(10) ¡ ¡ ¡.take(5) ¡ ¡ ¡.map(s ¡-­‑> ¡s ¡+ ¡"_transformed") ¡ ¡ ¡.forEach(t ¡-­‑> ¡System.out.println("onNext ¡=> ¡" ¡+ ¡t)) ¡

slide-5
SLIDE 5

Iterable<T> pull Observable<T> push T next() throws Exception returns;

  • nNext(T)
  • nError(Exception)
  • nCompleted()

¡// ¡Observable<String> ¡ ¡ ¡// ¡that ¡emits ¡75 ¡Strings ¡ ¡getDataFromNetwork() ¡ ¡ ¡.skip(10) ¡ ¡ ¡.take(5) ¡ ¡ ¡.map(s ¡-­‑> ¡s ¡+ ¡"_transformed") ¡ ¡ ¡.forEach(t ¡-­‑> ¡System.out.println("onNext ¡=> ¡" ¡+ ¡t)) ¡ ¡// ¡Iterable<String> ¡or ¡Stream<String> ¡ ¡ ¡// ¡that ¡contains ¡75 ¡Strings ¡ ¡getDataFromLocalMemory() ¡ ¡ ¡.skip(10) ¡ ¡ ¡.limit(5) ¡ ¡ ¡.map(s ¡-­‑> ¡s ¡+ ¡"_transformed") ¡ ¡ ¡.forEach(t ¡-­‑> ¡System.out.println("onNext ¡=> ¡" ¡+ ¡t)) ¡

slide-6
SLIDE 6

Single Multiple Sync T getData() Iterable<T> getData() Stream<T> getData() Async Future<T> getData() Observable<T> getData()

slide-7
SLIDE 7

Observable.create(subscriber -> { subscriber.onNext("Hello World!"); subscriber.onCompleted(); }).subscribe(System.out::println);

slide-8
SLIDE 8

Observable.create(subscriber -> { subscriber.onNext("Hello"); subscriber.onNext("World!"); subscriber.onCompleted(); }).subscribe(System.out::println);

slide-9
SLIDE 9

// shorten by using helper method Observable.just(“Hello”, “World!”) .subscribe(System.out::println);

slide-10
SLIDE 10

// add onError and onComplete listeners Observable.just(“Hello”, “World!”) .subscribe(System.out::println, Throwable::printStackTrace, () -> System.out.println("Done"));

slide-11
SLIDE 11

// expand to show full classes Observable.create(new OnSubscribe<String>() { @Override public void call(Subscriber<? super String> subscriber) { subscriber.onNext("Hello World!"); subscriber.onCompleted(); } }).subscribe(new Subscriber<String>() { @Override public void onCompleted() { System.out.println("Done"); } @Override public void onError(Throwable e) { e.printStackTrace(); } @Override public void onNext(String t) { System.out.println(t); } });

slide-12
SLIDE 12

// add error propagation Observable.create(subscriber -> { try { subscriber.onNext("Hello World!"); subscriber.onCompleted(); } catch (Exception e) { subscriber.onError(e); } }).subscribe(System.out::println);

slide-13
SLIDE 13

// add error propagation Observable.create(subscriber -> { try { subscriber.onNext(throwException()); subscriber.onCompleted(); } catch (Exception e) { subscriber.onError(e); } }).subscribe(System.out::println);

slide-14
SLIDE 14

// add error propagation Observable.create(subscriber -> { try { subscriber.onNext("Hello World!"); subscriber.onNext(throwException()); subscriber.onCompleted(); } catch (Exception e) { subscriber.onError(e); } }).subscribe(System.out::println);

slide-15
SLIDE 15

// add concurrency (manually) Observable.create(subscriber -> { new Thread(() -> { try { subscriber.onNext(getData()); subscriber.onCompleted(); } catch (Exception e) { subscriber.onError(e); } }).start(); }).subscribe(System.out::println);

?

slide-16
SLIDE 16

// add concurrency (using a Scheduler) Observable.create(subscriber -> { try { subscriber.onNext(getData()); subscriber.onCompleted(); } catch (Exception e) { subscriber.onError(e); } }).subscribeOn(Schedulers.io()) .subscribe(System.out::println);

?

Learn more about parameterized concurrency and virtual time with Rx Schedulers at https://github.com/ReactiveX/RxJava/wiki/Scheduler

slide-17
SLIDE 17

// add operator Observable.create(subscriber -> { try { subscriber.onNext(getData()); subscriber.onCompleted(); } catch (Exception e) { subscriber.onError(e); } }).subscribeOn(Schedulers.io()) .map(data -> data + " --> at " + System.currentTimeMillis()) .subscribe(System.out::println);

?

slide-18
SLIDE 18

// add error handling Observable.create(subscriber -> { try { subscriber.onNext(getData()); subscriber.onCompleted(); } catch (Exception e) { subscriber.onError(e); } }).subscribeOn(Schedulers.io()) .map(data -> data + " --> at " + System.currentTimeMillis()) .onErrorResumeNext(e -> Observable.just("Fallback Data")) .subscribe(System.out::println);

slide-19
SLIDE 19

// infinite Observable.create(subscriber -> { int i=0; while(!subscriber.isUnsubscribed()) { subscriber.onNext(i++); } }).subscribe(System.out::println);

Note: No backpressure support here. See Observable.from(Iterable)

  • r Observable.range() for actual implementations
slide-20
SLIDE 20

Hot Cold

emits whether you’re ready or not examples mouse and keyboard events system events stock prices emits when requested (generally at controlled rate) examples database query web service request reading file

Observable.create(subscriber -> { // register with data source }) Observable.create(subscriber -> { // fetch data })

slide-21
SLIDE 21

Hot Cold

emits whether you’re ready or not examples mouse and keyboard events system events stock prices emits when requested (generally at controlled rate) examples database query web service request reading file

Observable.create(subscriber -> { // register with data source }) Observable.create(subscriber -> { // fetch data })

flow control flow control & backpressure

slide-22
SLIDE 22
slide-23
SLIDE 23
slide-24
SLIDE 24
slide-25
SLIDE 25

Abstract Concurrency

slide-26
SLIDE 26

Cold Finite Streams

slide-27
SLIDE 27

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-28
SLIDE 28

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-29
SLIDE 29

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-30
SLIDE 30

¡Observable<R> ¡b ¡= ¡Observable<T>.flatMap({ ¡T ¡t ¡-­‑> ¡ ¡ ¡ ¡ ¡ ¡Observable<R> ¡r ¡= ¡... ¡transform ¡t ¡... ¡ ¡ ¡ ¡ ¡return ¡r; ¡ ¡})

flatMap

slide-31
SLIDE 31

¡Observable<R> ¡b ¡= ¡Observable<T>.flatMap({ ¡T ¡t ¡-­‑> ¡ ¡ ¡ ¡ ¡ ¡Observable<R> ¡r ¡= ¡... ¡transform ¡t ¡... ¡ ¡ ¡ ¡ ¡return ¡r; ¡ ¡})

flatMap

slide-32
SLIDE 32

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-33
SLIDE 33

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-34
SLIDE 34

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-35
SLIDE 35

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-36
SLIDE 36

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-37
SLIDE 37

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-38
SLIDE 38

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-39
SLIDE 39

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-40
SLIDE 40

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-41
SLIDE 41

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-42
SLIDE 42

¡ ¡ ¡ ¡Observable.zip(a, ¡b, ¡(a, ¡b) ¡-­‑> ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡... ¡operate ¡on ¡values ¡from ¡both ¡a ¡& ¡b ¡... ¡ ¡ ¡ ¡ ¡ ¡ ¡return ¡Arrays.asList(a, ¡b); ¡ ¡ ¡ ¡ ¡})

zip { ( , ) }

slide-43
SLIDE 43

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-44
SLIDE 44

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-45
SLIDE 45
slide-46
SLIDE 46

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-47
SLIDE 47

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-48
SLIDE 48

class ¡VideoService ¡{ ¡ ¡ ¡ ¡def ¡VideoList ¡getPersonalizedListOfMovies(userId); ¡ ¡ ¡ ¡def ¡VideoBookmark ¡getBookmark(userId, ¡videoId); ¡ ¡ ¡ ¡def ¡VideoRating ¡getRating(userId, ¡videoId); ¡ ¡ ¡ ¡def ¡VideoMetadata ¡getMetadata(videoId); ¡ } class ¡VideoService ¡{ ¡ ¡ ¡ ¡def ¡Observable<VideoList> ¡getPersonalizedListOfMovies(userId); ¡ ¡ ¡ ¡def ¡Observable<VideoBookmark> ¡getBookmark(userId, ¡videoId); ¡ ¡ ¡ ¡def ¡Observable<VideoRating> ¡getRating(userId, ¡videoId); ¡ ¡ ¡ ¡def ¡Observable<VideoMetadata> ¡getMetadata(videoId); ¡ }

... create an observable api: instead of a blocking api ...

slide-49
SLIDE 49

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

1 2 3 4 5 6

slide-50
SLIDE 50

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

1 2 3 4 5 6

slide-51
SLIDE 51

Non-Opinionated Concurrency

slide-52
SLIDE 52
slide-53
SLIDE 53

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-54
SLIDE 54
slide-55
SLIDE 55
slide-56
SLIDE 56
slide-57
SLIDE 57
slide-58
SLIDE 58

public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); }).flatMap(data -> { // output as SSE as we get back the data (no waiting until all is done) return response.writeAndFlush(new ServerSentEvent(SimpleJson.mapToJson(data))); }); }

slide-59
SLIDE 59
slide-60
SLIDE 60

Decouples Consumption from Production

// first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); })

slide-61
SLIDE 61

// first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); })

Decouples Consumption from Production

slide-62
SLIDE 62

// first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); })

Decouples Consumption from Production

slide-63
SLIDE 63

Decouples Consumption from Production

Event Loop API Request API Request API Request API Request API Request Dependency REST API new Command().observe() new Command(). observe() new Command(). observe() new Command(). observe() new Command(). observe() Collapser

slide-64
SLIDE 64

// first request User object return getUser(request.getQueryParameters().get("userId")).flatMap(user -> { // then fetch personal catalog Observable<Map<String, Object>> catalog = getPersonalizedCatalog(user) .flatMap(catalogList -> { return catalogList.videos().<Map<String, Object>> flatMap(video -> { Observable<Bookmark> bookmark = getBookmark(video); Observable<Rating> rating = getRating(video); Observable<VideoMetadata> metadata = getMetadata(video); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable<Map<String, Object>> social = getSocialData(user).map(s -> { return s.getDataAsMap(); }); // merge the results return Observable.merge(catalog, social); })

~5 network calls

(#3 and #4 may result in more due to windowing)

1 2 3 4 5

slide-65
SLIDE 65

Clear API Communicates Potential Cost

class ¡VideoService ¡{ ¡ ¡ ¡ ¡def ¡Observable<VideoList> ¡getPersonalizedListOfMovies(userId); ¡ ¡ ¡ ¡def ¡Observable<VideoBookmark> ¡getBookmark(userId, ¡videoId); ¡ ¡ ¡ ¡def ¡Observable<VideoRating> ¡getRating(userId, ¡videoId); ¡ ¡ ¡ ¡def ¡Observable<VideoMetadata> ¡getMetadata(videoId); ¡ }

slide-66
SLIDE 66

class ¡VideoService ¡{ ¡ ¡ ¡ ¡def ¡Observable<VideoList> ¡getPersonalizedListOfMovies(userId); ¡ ¡ ¡ ¡def ¡Observable<VideoBookmark> ¡getBookmark(userId, ¡videoId); ¡ ¡ ¡ ¡def ¡Observable<VideoRating> ¡getRating(userId, ¡videoId); ¡ ¡ ¡ ¡def ¡Observable<VideoMetadata> ¡getMetadata(videoId); ¡ }

Implementation Can Differ

BIO Network Call Local Cache Collapsed Network Call

slide-67
SLIDE 67

class ¡VideoService ¡{ ¡ ¡ ¡ ¡def ¡Observable<VideoList> ¡getPersonalizedListOfMovies(userId); ¡ ¡ ¡ ¡def ¡Observable<VideoBookmark> ¡getBookmark(userId, ¡videoId); ¡ ¡ ¡ ¡def ¡Observable<VideoRating> ¡getRating(userId, ¡videoId); ¡ ¡ ¡ ¡def ¡Observable<VideoMetadata> ¡getMetadata(videoId); ¡ }

Implementation Can Differ and Change

Local Cache Collapsed Network Call Collapsed Network Call BIO NIO Network Call

slide-68
SLIDE 68

Retrieval, Transformation, Combination all done in same declarative manner

slide-69
SLIDE 69

What about … ?

slide-70
SLIDE 70

Error Handling

slide-71
SLIDE 71

Observable.create(subscriber -> { throw new RuntimeException("failed!"); }).onErrorResumeNext(throwable -> { return Observable.just("fallback value"); }).subscribe(System.out::println);

slide-72
SLIDE 72

Observable.create(subscriber -> { throw new RuntimeException("failed!"); }).onErrorReturn(throwable -> { return "fallback value"; }).subscribe(System.out::println);

slide-73
SLIDE 73

Observable.create(subscriber -> { throw new RuntimeException("failed!"); }).retryWhen(attempts -> { return attempts.zipWith(Observable.range(1, 3), (throwable, i) -> i) .flatMap(i -> { System.out.println("delay retry by " + i + " second(s)"); return Observable.timer(i, TimeUnit.SECONDS); }).concatWith(Observable.error(new RuntimeException("Exceeded 3 retries"))); }) .subscribe(System.out::println, t -> t.printStackTrace());

slide-74
SLIDE 74

Observable.create(subscriber -> { throw new RuntimeException("failed!"); }).retryWhen(attempts -> { return attempts.zipWith(Observable.range(1, 3), (throwable, i) -> i) .flatMap(i -> { System.out.println("delay retry by " + i + " second(s)"); return Observable.timer(i, TimeUnit.SECONDS); }).concatWith(Observable.error(new RuntimeException("Exceeded 3 retries"))); }) .subscribe(System.out::println, t -> t.printStackTrace());

slide-75
SLIDE 75

Observable.create(subscriber -> { throw new RuntimeException("failed!"); }).retryWhen(attempts -> { return attempts.zipWith(Observable.range(1, 3), (throwable, i) -> i) .flatMap(i -> { System.out.println("delay retry by " + i + " second(s)"); return Observable.timer(i, TimeUnit.SECONDS); }).concatWith(Observable.error(new RuntimeException("Exceeded 3 retries"))); }) .subscribe(System.out::println, t -> t.printStackTrace());

slide-76
SLIDE 76

Observable.create(subscriber -> { throw new RuntimeException("failed!"); }).retryWhen(attempts -> { return attempts.zipWith(Observable.range(1, 3), (throwable, i) -> i) .flatMap(i -> { System.out.println("delay retry by " + i + " second(s)"); return Observable.timer(i, TimeUnit.SECONDS); }).concatWith(Observable.error(new RuntimeException("Exceeded 3 retries"))); }) .subscribe(System.out::println, t -> t.printStackTrace());

slide-77
SLIDE 77

Concurrency

slide-78
SLIDE 78

Concurrency

an Observable is sequential (no concurrent emissions) scheduling and combining Observables enables concurrency while retaining sequential emission

slide-79
SLIDE 79

// merging async Observables allows each // to execute concurrently Observable.merge(getDataAsync(1), getDataAsync(2))

merge

slide-80
SLIDE 80

// concurrently fetch data for 5 items Observable.range(0, 5).flatMap(i -> { return getDataAsync(i); })

slide-81
SLIDE 81

Observable.range(0, 5000).window(500).flatMap(work -> { return work.observeOn(Schedulers.computation()) .map(item -> { // simulate computational work try { Thread.sleep(1); } catch (Exception e) {} return item + " processed " + Thread.currentThread(); }); })

slide-82
SLIDE 82

Observable.range(0, 5000).buffer(500).flatMap(is -> { return Observable.from(is).subscribeOn(Schedulers.computation()) .map(item -> { // simulate computational work try { Thread.sleep(1); } catch (Exception e) {} return item + " processed " + Thread.currentThread(); }); })

slide-83
SLIDE 83

Flow Control

slide-84
SLIDE 84

Flow Control

(backpressure)

slide-85
SLIDE 85

no backpressure needed

Observable.from(iterable).take(1000).map(i -> "value_" + i).subscribe(System.out::println);

slide-86
SLIDE 86

Observable.from(iterable).take(1000).map(i -> "value_" + i).subscribe(System.out::println);

no backpressure needed

synchronous on same thread (no queueing)

slide-87
SLIDE 87

Observable.from(iterable).take(1000).map(i -> "value_" + i) .observeOn(Schedulers.computation()).subscribe(System.out::println);

backpressure needed

slide-88
SLIDE 88

Observable.from(iterable).take(1000).map(i -> "value_" + i) .observeOn(Schedulers.computation()).subscribe(System.out::println);

backpressure needed

asynchronous (queueing)

slide-89
SLIDE 89

Flow Control Options

slide-90
SLIDE 90

Hot Cold

emits whether you’re ready or not examples mouse and keyboard events system events stock prices emits when requested (generally at controlled rate) examples database query web service request reading file

Observable.create(subscriber -> { // register with data source }) Observable.create(subscriber -> { // fetch data })

flow control flow control & backpressure

slide-91
SLIDE 91

Block

(callstack blocking and/or park the thread)

Hot or Cold Streams

slide-92
SLIDE 92

Temporal Operators

(batch or drop data using time)

Hot Streams

slide-93
SLIDE 93

Observable.range(1, 1000000).sample(10, TimeUnit.MILLISECONDS).forEach(System.out::println); 110584 242165 544453 942880

slide-94
SLIDE 94

Observable.range(1, 1000000).throttleFirst(10, TimeUnit.MILLISECONDS).forEach(System.out::println);

1 55463 163962 308545 457445 592638 751789 897159

slide-95
SLIDE 95

Observable.range(1, 1000000).debounce(10, TimeUnit.MILLISECONDS).forEach(System.out::println);

1000000

slide-96
SLIDE 96

Observable.range(1, 1000000).buffer(10, TimeUnit.MILLISECONDS) .toBlocking().forEach(list -> System.out.println("batch: " + list.size()));

batch: 71141 batch: 49488 batch: 141147 batch: 141432 batch: 195920 batch: 240462 batch: 160410

slide-97
SLIDE 97
slide-98
SLIDE 98

/* The following will emit a buffered list as it is debounced */ // first we multicast the stream ... using refCount so it handles the subscribe/unsubscribe Observable<Integer> burstStream = intermittentBursts().take(20).publish().refCount(); // then we get the debounced version Observable<Integer> debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS); // then the buffered one that uses the debounced stream to demark window start/stop Observable<List<Integer>> buffered = burstStream.buffer(debounced); // then we subscribe to the buffered stream so it does what we want buffered.toBlocking().forEach(System.out::println);

[0, 1, 2] [0, 1, 2] [0, 1, 2, 3, 4, 5, 6] [0, 1, 2, 3, 4] [0, 1] []

slide-99
SLIDE 99

/* The following will emit a buffered list as it is debounced */ // first we multicast the stream ... using refCount so it handles the subscribe/unsubscribe Observable<Integer> burstStream = intermittentBursts().take(20).publish().refCount(); // then we get the debounced version Observable<Integer> debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS); // then the buffered one that uses the debounced stream to demark window start/stop Observable<List<Integer>> buffered = burstStream.buffer(debounced); // then we subscribe to the buffered stream so it does what we want buffered.toBlocking().forEach(System.out::println);

[0, 1, 2] [0, 1, 2] [0, 1, 2, 3, 4, 5, 6] [0, 1, 2, 3, 4] [0, 1] []

slide-100
SLIDE 100

/* The following will emit a buffered list as it is debounced */ // first we multicast the stream ... using refCount so it handles the subscribe/unsubscribe Observable<Integer> burstStream = intermittentBursts().take(20).publish().refCount(); // then we get the debounced version Observable<Integer> debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS); // then the buffered one that uses the debounced stream to demark window start/stop Observable<List<Integer>> buffered = burstStream.buffer(debounced); // then we subscribe to the buffered stream so it does what we want buffered.toBlocking().forEach(System.out::println);

[0, 1, 2] [0, 1, 2] [0, 1, 2, 3, 4, 5, 6] [0, 1, 2, 3, 4] [0, 1] []

slide-101
SLIDE 101

/* The following will emit a buffered list as it is debounced */ // first we multicast the stream ... using refCount so it handles the subscribe/unsubscribe Observable<Integer> burstStream = intermittentBursts().take(20).publish().refCount(); // then we get the debounced version Observable<Integer> debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS); // then the buffered one that uses the debounced stream to demark window start/stop Observable<List<Integer>> buffered = burstStream.buffer(debounced); // then we subscribe to the buffered stream so it does what we want buffered.toBlocking().forEach(System.out::println);

[0, 1, 2] [0, 1, 2] [0, 1, 2, 3, 4, 5, 6] [0, 1, 2, 3, 4] [0, 1] []

slide-102
SLIDE 102

/* The following will emit a buffered list as it is debounced */ // first we multicast the stream ... using refCount so it handles the subscribe/unsubscribe Observable<Integer> burstStream = intermittentBursts().take(20).publish().refCount(); // then we get the debounced version Observable<Integer> debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS); // then the buffered one that uses the debounced stream to demark window start/stop Observable<List<Integer>> buffered = burstStream.buffer(debounced); // then we subscribe to the buffered stream so it does what we want buffered.toBlocking().forEach(System.out::println);

[0, 1, 2] [0, 1, 2] [0, 1, 2, 3, 4, 5, 6] [0, 1, 2, 3, 4] [0, 1] []

slide-103
SLIDE 103

/* The following will emit a buffered list as it is debounced */ // first we multicast the stream ... using refCount so it handles the subscribe/unsubscribe Observable<Integer> burstStream = intermittentBursts().take(20).publish().refCount(); // then we get the debounced version Observable<Integer> debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS); // then the buffered one that uses the debounced stream to demark window start/stop Observable<List<Integer>> buffered = burstStream.buffer(debounced); // then we subscribe to the buffered stream so it does what we want buffered.toBlocking().forEach(System.out::println);

[0, 1, 2] [0, 1, 2] [0, 1, 2, 3, 4, 5, 6] [0, 1, 2, 3, 4] [0, 1] []

https://gist.github.com/benjchristensen/e4524a308456f3c21c0b

slide-104
SLIDE 104

Observable.range(1, 1000000).window(50, TimeUnit.MILLISECONDS) .flatMap(window -> window.count()) .toBlocking().forEach(count -> System.out.println("num items: " + count));

num items: 477769 num items: 155463 num items: 366768

slide-105
SLIDE 105

Observable.range(1, 1000000).window(500000) .flatMap(window -> window.count()) .toBlocking().forEach(count -> System.out.println("num items: " + count)); num items: 500000 num items: 500000

slide-106
SLIDE 106

Reactive Pull

(dynamic push-pull)

slide-107
SLIDE 107

Push (reactive) when consumer keeps up with producer. Switch to Pull (interactive) when consumer is slow. Bound all* queues.

slide-108
SLIDE 108

*vertically, not horizontally

Push (reactive) when consumer keeps up with producer. Switch to Pull (interactive) when consumer is slow. Bound all* queues.

slide-109
SLIDE 109

Reactive Pull

hot vs cold

slide-110
SLIDE 110

Reactive Pull

cold supports pull

slide-111
SLIDE 111

Observable.from(iterable) Observable.from(0, 100000)

Cold Streams

emits when requested (generally at controlled rate) examples database query web service request reading file

slide-112
SLIDE 112

Observable.from(iterable) Observable.from(0, 100000)

Cold Streams

emits when requested (generally at controlled rate) examples database query web service request reading file

Pull

slide-113
SLIDE 113

Reactive Pull

hot receives signal

slide-114
SLIDE 114

Reactive Pull

hot receives signal

*including Observables that don’t implement reactive pull support

slide-115
SLIDE 115

hotSourceStream.onBackpressureBuffer().observeOn(aScheduler);

slide-116
SLIDE 116

hotSourceStream.onBackpressureBuffer().observeOn(aScheduler);

slide-117
SLIDE 117

hotSourceStream.onBackpressureBuffer().observeOn(aScheduler);

slide-118
SLIDE 118

hotSourceStream.onBackpressureBuffer().observeOn(aScheduler);

slide-119
SLIDE 119

hotSourceStream.onBackpressureDrop().observeOn(aScheduler);

slide-120
SLIDE 120

stream.onBackpressure(strategy).subscribe

slide-121
SLIDE 121

Hot Infinite Streams

slide-122
SLIDE 122

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-123
SLIDE 123

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-124
SLIDE 124

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

Hot Infinite Stream

slide-125
SLIDE 125

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-126
SLIDE 126

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-127
SLIDE 127

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-128
SLIDE 128

(movieId) movieId=12345 movieId=34567

slide-129
SLIDE 129

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-130
SLIDE 130

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-131
SLIDE 131

Stage 1 Stage 2 groupBy Event Streams sink

slide-132
SLIDE 132

Stage 1 Stage 2 12345 56789 34567

slide-133
SLIDE 133

Stage 1 Stage 2 12345 56789 34567

slide-134
SLIDE 134

Stage 1 Stage 2 12345 56789 34567

slide-135
SLIDE 135

Stage 1 Stage 2 12345 56789 34567

slide-136
SLIDE 136

Stage 1 Stage 2 12345 56789 34567 12345 56789 34567

slide-137
SLIDE 137

Stage 1 Stage 2 12345 56789 34567 12345 56789 34567

slide-138
SLIDE 138

Stage 1 Stage 2 12345 56789 34567 12345 56789 34567

slide-139
SLIDE 139

Stage 1 Stage 2 12345 56789 34567 12345 56789 34567

slide-140
SLIDE 140

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-141
SLIDE 141

10mins || 1000 4:40-4:50pm 4:50-5:00pm 5:00-5:07pm (burst to 1000) 5:07-5:17pm

slide-142
SLIDE 142

10mins || 1000 4:40-4:50pm 4:50-5:00pm 5:00-5:07pm (burst to 1000) 5:07-5:17pm

slide-143
SLIDE 143

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-144
SLIDE 144

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-145
SLIDE 145
slide-146
SLIDE 146

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-147
SLIDE 147

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-148
SLIDE 148

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-149
SLIDE 149

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-150
SLIDE 150

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-151
SLIDE 151

MantisJob .source(NetflixSources.moviePlayAttempts()) .stage(playAttempts -> { return playAttempts.groupBy(playAttempt -> { return playAttempt.getMovieId(); }) }) .stage(playAttemptsByMovieId -> { playAttemptsByMovieId .window(10,TimeUnit.MINUTES, 1000) // buffer for 10 minutes, or 1000 play attempts .flatMap(windowOfPlayAttempts -> { return windowOfPlayAttempts .reduce(new FailRatioExperiment(playAttemptsByMovieId.getKey()), (experiment, playAttempt) -> { experiment.updateFailRatio(playAttempt); experiment.updateExamples(playAttempt); return experiment; }).doOnNext(experiment -> { logToHistorical("Play attempt experiment", experiment.getId(),experiment); // log for offline analysis }).filter(experiment -> { return experiment.failRatio() >= DYNAMIC_PROP("fail_threshold").get(); }).map(experiment -> { return new FailReport(experiment, runCorrelations(experiment.getExamples())); }).doOnNext(report -> { logToHistorical("Failure report", report.getId(), report); // log for offline analysis }) }) }) .sink(Sinks.emailAlert(report -> { return toEmail(report)})) // anomalies trigger events (simple email here)

slide-152
SLIDE 152

stream.onBackpressure(strategy?).subscribe

slide-153
SLIDE 153

stream.onBackpressure(buffer).subscribe

slide-154
SLIDE 154

stream.onBackpressure(drop).subscribe

slide-155
SLIDE 155

stream.onBackpressure(sample).subscribe

slide-156
SLIDE 156

stream.onBackpressure(scaleHorizontally).subscribe

slide-157
SLIDE 157

Reactive-Streams

https://github.com/reactive-streams/reactive-streams

slide-158
SLIDE 158

Reactive-Streams

https://github.com/reactive-streams/reactive-streams https://github.com/ReactiveX/RxJavaReactiveStreams

slide-159
SLIDE 159

final ActorSystem system = ActorSystem.create("InteropTest"); final FlowMaterializer mat = FlowMaterializer.create(system); // RxJava Observable Observable<GroupedObservable<Boolean, Integer>> oddAndEvenGroups = Observable.range(1, 1000000) .groupBy(i -> i % 2 == 0) .take(2); Observable<String> strings = oddAndEvenGroups.<String> flatMap(group -> { // schedule odd and even on different event loops Observable<Integer> asyncGroup = group.observeOn(Schedulers.computation()); // convert to Reactive Streams Publisher Publisher<Integer> groupPublisher = RxReactiveStreams.toPublisher(asyncGroup); // convert to Akka Streams Source and transform using Akka Streams ‘map’ and ‘take’ operators Source<String> stringSource = Source.from(groupPublisher).map(i -> i + " " + group.getKey()).take(2000); // convert back from Akka to Rx Observable return RxReactiveStreams.toObservable(stringSource.runWith(Sink.<String> fanoutPublisher(1, 1), mat)); }); strings.toBlocking().forEach(System.out::println); system.shutdown();

compile 'io.reactivex:rxjava:1.0.+' compile 'io.reactivex:rxjava-reactive-streams:0.3.0' compile 'com.typesafe.akka:akka-stream-experimental_2.11:0.10-M1'

slide-160
SLIDE 160

final ActorSystem system = ActorSystem.create("InteropTest"); final FlowMaterializer mat = FlowMaterializer.create(system); // RxJava Observable Observable<GroupedObservable<Boolean, Integer>> oddAndEvenGroups = Observable.range(1, 1000000) .groupBy(i -> i % 2 == 0) .take(2); Observable<String> strings = oddAndEvenGroups.<String> flatMap(group -> { // schedule odd and even on different event loops Observable<Integer> asyncGroup = group.observeOn(Schedulers.computation()); // convert to Reactive Streams Publisher Publisher<Integer> groupPublisher = RxReactiveStreams.toPublisher(asyncGroup); // convert to Akka Streams Source and transform using Akka Streams ‘map’ and ‘take’ operators Source<String> stringSource = Source.from(groupPublisher).map(i -> i + " " + group.getKey()).take(2000); // convert back from Akka to Rx Observable return RxReactiveStreams.toObservable(stringSource.runWith(Sink.<String> fanoutPublisher(1, 1), mat)); }); strings.toBlocking().forEach(System.out::println); system.shutdown();

compile 'io.reactivex:rxjava:1.0.+' compile 'io.reactivex:rxjava-reactive-streams:0.3.0' compile 'com.typesafe.akka:akka-stream-experimental_2.11:0.10-M1'

slide-161
SLIDE 161

// RxJava Observable Observable<GroupedObservable<Boolean, Integer>> oddAndEvenGroups = Observable.range(1, 1000000) .groupBy(i -> i % 2 == 0) .take(2); Observable<String> strings = oddAndEvenGroups.<String> flatMap(group -> { // schedule odd and even on different event loops Observable<Integer> asyncGroup = group.observeOn(Schedulers.computation()); // convert to Reactive Streams Publisher Publisher<Integer> groupPublisher = RxReactiveStreams.toPublisher(asyncGroup); // Convert to Reactor Stream and transform using Reactor Stream ‘map’ and ‘take’ operators Stream<String> linesStream = Streams.create(groupPublisher).map(i -> i + " " + group.getKey()).take(2000); // convert back from Reactor Stream to Rx Observable return RxReactiveStreams.toObservable(linesStream); }); strings.toBlocking().forEach(System.out::println);

compile 'io.reactivex:rxjava:1.0.+' compile 'io.reactivex:rxjava-reactive-streams:0.3.0' compile 'org.projectreactor:reactor-core:2.0.0.M1'

slide-162
SLIDE 162

// RxJava Observable Observable<GroupedObservable<Boolean, Integer>> oddAndEvenGroups = Observable.range(1, 1000000) .groupBy(i -> i % 2 == 0) .take(2); Observable<String> strings = oddAndEvenGroups.<String> flatMap(group -> { // schedule odd and even on different event loops Observable<Integer> asyncGroup = group.observeOn(Schedulers.computation()); // convert to Reactive Streams Publisher Publisher<Integer> groupPublisher = RxReactiveStreams.toPublisher(asyncGroup); // Convert to Reactor Stream and transform using Reactor Stream ‘map’ and ‘take’ operators Stream<String> linesStream = Streams.create(groupPublisher).map(i -> i + " " + group.getKey()).take(2000); // convert back from Reactor Stream to Rx Observable return RxReactiveStreams.toObservable(linesStream); }); strings.toBlocking().forEach(System.out::println);

compile 'io.reactivex:rxjava:1.0.+' compile 'io.reactivex:rxjava-reactive-streams:0.3.0' compile 'org.projectreactor:reactor-core:2.0.0.M1'

slide-163
SLIDE 163

compile 'io.reactivex:rxjava:1.0.+' compile 'io.reactivex:rxjava-reactive-streams:0.3.0' compile 'io.ratpack:ratpack-rx:0.9.10'

try { RatpackServer server = EmbeddedApp.fromHandler(ctx -> { Observable<String> o1 = Observable.range(0, 2000) .observeOn(Schedulers.computation()).map(i -> { return "A " + i; }); Observable<String> o2 = Observable.range(0, 2000) .observeOn(Schedulers.computation()).map(i -> { return "B " + i; }); Observable<String> o = Observable.merge(o1, o2); ctx.render( ServerSentEvents.serverSentEvents(RxReactiveStreams.toPublisher(o), e -> e.event("counter").data("event " + e.getItem())) ); }).getServer(); server.start(); System.out.println("Port: " + server.getBindPort()); } catch (Exception e) { e.printStackTrace(); }

slide-164
SLIDE 164

compile 'io.reactivex:rxjava:1.0.+' compile 'io.reactivex:rxjava-reactive-streams:0.3.0' compile 'io.ratpack:ratpack-rx:0.9.10'

try { RatpackServer server = EmbeddedApp.fromHandler(ctx -> { Observable<String> o1 = Observable.range(0, 2000) .observeOn(Schedulers.computation()).map(i -> { return "A " + i; }); Observable<String> o2 = Observable.range(0, 2000) .observeOn(Schedulers.computation()).map(i -> { return "B " + i; }); Observable<String> o = Observable.merge(o1, o2); ctx.render( ServerSentEvents.serverSentEvents(RxReactiveStreams.toPublisher(o), e -> e.event("counter").data("event " + e.getItem())) ); }).getServer(); server.start(); System.out.println("Port: " + server.getBindPort()); } catch (Exception e) { e.printStackTrace(); }

slide-165
SLIDE 165

RxJava 1.0 Final

November 18th

slide-166
SLIDE 166

Mental Shift imperative → functional sync → async pull → push

slide-167
SLIDE 167

Concurrency and async are non-trivial. Rx doesn’t trivialize it. Rx is powerful and rewards those who go through the learning curve.

slide-168
SLIDE 168

Single Multiple Sync T getData() Iterable<T> getData() Stream<T> getData() Async Future<T> getData() Observable<T> getData()

slide-169
SLIDE 169

Abstract Concurrency

slide-170
SLIDE 170

Non-Opinionated Concurrency

slide-171
SLIDE 171

Decouple Production from Consumption

slide-172
SLIDE 172

Powerful Composition

  • f Nested, Conditional Flows
slide-173
SLIDE 173

First-class Support of Error Handling, Scheduling & Flow Control

slide-174
SLIDE 174

RxJava

http://github.com/ReactiveX/RxJava http://reactivex.io

slide-175
SLIDE 175

Reactive Programming in the Netflix API with RxJava http://techblog.netflix.com/2013/02/rxjava-netflix-api.html Optimizing the Netflix API http://techblog.netflix.com/2013/01/optimizing-netflix-api.html Reactive Extensions (Rx) http://www.reactivex.io Reactive Streams https://github.com/reactive-streams/reactive-streams

Ben Christensen

@benjchristensen

RxJava

https://github.com/ReactiveX/RxJava @RxJava

RxJS

http://reactive-extensions.github.io/RxJS/ @ReactiveX

jobs.netflix.com