Slide 1

Slide 1 text

Ben Christensen Developer – Edge Engineering at Netflix @benjchristensen ! ! ! ! ! ! http://techblog.netflix.com/ JavaOne - September 2014 Reactive Streams with Rx

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Single Multiple Sync T getData() Iterable getData() Async Future getData() Observable getData()

Slide 6

Slide 6 text

public interface Observer { public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) } public class Observable { public static Observable create(OnSubscribe f) public Subscription subscribe(Observer super T> subscriber) } Lazy Cold or Hot Asynchronous Push *onNext (onError | onCompleted)?

Slide 7

Slide 7 text

public interface Observer { public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) } public class Observable { public static Observable create(OnSubscribe f) public Subscription subscribe(Observer super T> subscriber) } Lazy Cold or Hot Asynchronous (or Synchronous) Push (or Pull) *onNext (onError | onCompleted)?

Slide 8

Slide 8 text

public interface Observer { public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) } public class Observable { public static Observable create(OnSubscribe f) public Subscription subscribe(Observer super T> subscriber) } Lazy Cold or Hot Asynchronous (or Synchronous) Push (or Pull) *onNext (onError | onCompleted)?

Slide 9

Slide 9 text

public interface Subscriber implements Observer { public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) } public class Observable { public static Observable create(OnSubscribe f) public Subscription subscribe(Subscriber super T> subscriber) } Lazy Cold or Hot Asynchronous (or Synchronous) Push (or Pull) *onNext (onError | onCompleted)?

Slide 10

Slide 10 text

public abstract class Subscriber implements Observer, Subscription public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) ! public final void add(Subscription s) public void setProducer(Producer producer) } ! public interface Producer { public void request(long n); }

Slide 11

Slide 11 text

public abstract class Subscriber implements Observer, Subscription public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) ! public final void add(Subscription s) public void setProducer(Producer producer) } ! public interface Producer { public void request(long n); }

Slide 12

Slide 12 text

public abstract class Subscriber implements Observer, Subscription public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) ! public final void add(Subscription s) public void setProducer(Producer producer) } ! public interface Producer { public void request(long n); }

Slide 13

Slide 13 text

public abstract class Subscriber implements Observer, Subscription public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) ! public final void add(Subscription s) public void setProducer(Producer producer) } ! public interface Producer { public void request(long n); }

Slide 14

Slide 14 text

public abstract class Subscriber implements Observer, Subscription public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) ! public final void add(Subscription s) public void setProducer(Producer producer) } ! public interface Producer { public void request(long n); } “reactive pull” dynamic push/pull for flow control (backpressure)

Slide 15

Slide 15 text

public class Observable { public static Observable create(OnSubscribe f) public Subscription subscribe(Subscriber super T> subscriber) } public static interface OnSubscribe { public void call(Subscriber super T> subscriber); } public abstract class Subscriber implements Observer, Subscription public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) ! public final void add(Subscription s) public void setProducer(Producer producer) } ! public interface Producer { public void request(long n); }

Slide 16

Slide 16 text

public class Observable { public static Observable create(OnSubscribe f) public Subscription subscribe(Subscriber super T> subscriber) } public static interface OnSubscribe { public void call(Subscriber super T> subscriber); } public abstract class Subscriber implements Observer, Subscription public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) ! public final void add(Subscription s) public void setProducer(Producer producer) } ! public interface Producer { public void request(long n); }

Slide 17

Slide 17 text

public class Observable { public static Observable create(OnSubscribe f) public Subscription subscribe(Subscriber super T> subscriber) } public static interface OnSubscribe { public void call(Subscriber super T> subscriber); } public abstract class Subscriber implements Observer, Subscription public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) ! public final void add(Subscription s) public void setProducer(Producer producer) } ! public interface Producer { public void request(long n); }

Slide 18

Slide 18 text

Observable.create(subscriber -> { subscriber.onNext("Hello world!"); subscriber.onCompleted(); }).forEach(System.out::println); synchronous single value public static interface OnSubscribe { public void call(Subscriber super T> subscriber); } public abstract class Subscriber implements Observer, Subscription public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) ! public final void add(Subscription s) public void setProducer(Producer producer) } ! public interface Producer { public void request(long n); } public class Observable { public static Observable create(OnSubscribe f) public Subscription subscribe(Subscriber super T> subscriber) }

Slide 19

Slide 19 text

Observable.create(subscriber -> { subscriber.onNext("Hello"); subscriber.onNext("world"); subscriber.onNext("!"); subscriber.onCompleted(); }).forEach(System.out::println); synchronous multiple values public static interface OnSubscribe { public void call(Subscriber super T> subscriber); } public abstract class Subscriber implements Observer, Subscription public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) ! public final void add(Subscription s) public void setProducer(Producer producer) } ! public interface Producer { public void request(long n); } public class Observable { public static Observable create(OnSubscribe f) public Subscription subscribe(Subscriber super T> subscriber) }

Slide 20

Slide 20 text

Observable.create(subscriber -> { try { subscriber.onNext(doSomething()); subscriber.onCompleted(); } catch (Throwable e) { subscriber.onError(e); } }).subscribeOn(Schedulers.io()) .forEach(System.out::println); asynchronous single value error notification public static interface OnSubscribe { public void call(Subscriber super T> subscriber); } public abstract class Subscriber implements Observer, Subscription public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) ! public final void add(Subscription s) public void setProducer(Producer producer) } ! public interface Producer { public void request(long n); } public class Observable { public static Observable create(OnSubscribe f) public Subscription subscribe(Subscriber super T> subscriber) }

Slide 21

Slide 21 text

Observable.create(subscriber -> { int i = 0; while (!subscriber.isUnsubscribed()) { subscriber.onNext(i++); } }).take(10).forEach(System.out::println); synchronous multiple values with unsubscribe public static interface OnSubscribe { public void call(Subscriber super T> subscriber); } public abstract class Subscriber implements Observer, Subscription public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) ! public final void add(Subscription s) public void setProducer(Producer producer) } ! public interface Producer { public void request(long n); } public class Observable { public static Observable create(OnSubscribe f) public Subscription subscribe(Subscriber super T> subscriber) }

Slide 22

Slide 22 text

Observable.create(subscriber -> { AtomicInteger i = new AtomicInteger(); AtomicLong requested = new AtomicLong(); subscriber.setProducer(r -> { if (requested.getAndAdd(r) == 0) { do { if (subscriber.isUnsubscribed()) { break; } subscriber.onNext(i.incrementAndGet()); } while (requested.decrementAndGet() > 0); } }); }).observeOn(Schedulers.newThread()) .take(10).forEach(System.out::println); synchronous multiple values flow control with “reactive pull” backpressure thread scheduling public static interface OnSubscribe { public void call(Subscriber super T> subscriber); } public abstract class Subscriber implements Observer, Subscription public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) ! public final void add(Subscription s) public void setProducer(Producer producer) } ! public interface Producer { public void request(long n); } public class Observable { public static Observable create(OnSubscribe f) public Subscription subscribe(Subscriber super T> subscriber) }

Slide 23

Slide 23 text

Observable.create(subscriber -> { AtomicInteger i = new AtomicInteger(); AtomicLong requested = new AtomicLong(); subscriber.setProducer(r -> { if (requested.getAndAdd(r) == 0) { do { if (subscriber.isUnsubscribed()) { break; } subscriber.onNext(i.incrementAndGet()); } while (requested.decrementAndGet() > 0); } }); }).observeOn(Schedulers.newThread()) .take(10).forEach(System.out::println); public static interface OnSubscribe { public void call(Subscriber super T> subscriber); } public abstract class Subscriber implements Observer, Subscription public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) ! public final void add(Subscription s) public void setProducer(Producer producer) } ! public interface Producer { public void request(long n); } public class Observable { public static Observable create(OnSubscribe f) public Subscription subscribe(Subscriber super T> subscriber) }

Slide 24

Slide 24 text

Observable.from(iterable) .observeOn(Schedulers.newThread()) .take(10).forEach(System.out::println); public static interface OnSubscribe { public void call(Subscriber super T> subscriber); } public abstract class Subscriber implements Observer, Subscription public abstract void onCompleted() public abstract void onError(Throwable e) public abstract void onNext(T t) ! public final void add(Subscription s) public void setProducer(Producer producer) } ! public interface Producer { public void request(long n); } public class Observable { public static Observable create(OnSubscribe f) public Subscription subscribe(Subscriber super T> subscriber) }

Slide 25

Slide 25 text

finite

Slide 26

Slide 26 text

infinite finite

Slide 27

Slide 27 text

finite infinite distributed

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 text

 Observable  b  =  Observable.flatMap({  T  t  -­‐>            Observable  r  =  ...  transform  t  ...          return  r;    }) flatMap

Slide 36

Slide 36 text

 Observable  b  =  Observable.flatMap({  T  t  -­‐>            Observable  r  =  ...  transform  t  ...          return  r;    }) flatMap

Slide 37

Slide 37 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 43

Slide 43 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 46

Slide 46 text

       Observable.zip(a,  b,  {  a,  b,  -­‐>                ...  operate  on  values  from  both  a  &  b  ...              return  [a,  b];  //  i.e.  return  tuple          })

Slide 47

Slide 47 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 49

Slide 49 text

No content

Slide 50

Slide 50 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 51

Slide 51 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 52

Slide 52 text

class  VideoService  {        def  Observable  getPersonalizedListOfMovies(userId);        def  Observable  getBookmark(userId,  videoId);        def  Observable  getRating(userId,  videoId);        def  Observable  getMetadata(videoId);   } an observable api: finite Treat everything like a stream

Slide 53

Slide 53 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 54

Slide 54 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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))); }); } 3 4 5

Slide 55

Slide 55 text

public Observable handle(HttpServerRequest request, HttpServerResponse response) { // first request User object return new UserCommand(request.getQueryParameters().get("userId")).observe().flatMap(user -> { // then fetch personal catalog Observable> catalog = new PersonalizedCatalogCommand(user).observe() .flatMap(catalogList -> { return catalogList.videos().> flatMap(video -> { Observable bookmark = new BookmarkCommand(video).observe(); Observable rating = new RatingsCommand(video).observe(); Observable metadata = new VideoMetadataCommand(video).observe(); return Observable.zip(bookmark, rating, metadata, (b, r, m) -> { return combineVideoData(video, b, r, m); }); }); }); // and fetch social data in parallel Observable> social = new SocialCommand(user).observe().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 56

Slide 56 text

infinite

Slide 57

Slide 57 text

private static Observable>> aggregateUsingPivot(Observable>> instanceStreams) { return instanceStreams.map(instanceStream -> { return createGroupedObservable(instanceStream.getKey(), instanceStream .groupBy((Map json) -> { return TypeAndNameKey.from(String.valueOf(json.get("type")), String.valueOf(json.get("name"))); })); }).lift(OperatorPivot.create()).map(commandGroup -> { // merge all instances per group into a single stream of deltas and sum them return createGroupedObservable(commandGroup.getKey(), commandGroup.flatMap(instanceGroup -> { return instanceGroup.startWith(Collections. emptyMap()) .buffer(2, 1) .map(StreamAggregator::previousAndCurrentToDelta) .filter(data -> data != null && !data.isEmpty()); }).scan(new HashMap(), StreamAggregator::sumOfDelta) .skip(1)); }); }

Slide 58

Slide 58 text

private static Observable>> aggregateUsingPivot(Observable>> instanceStreams) { return instanceStreams.map(instanceStream -> { return createGroupedObservable(instanceStream.getKey(), instanceStream .groupBy((Map json) -> { return TypeAndNameKey.from(String.valueOf(json.get("type")), String.valueOf(json.get("name"))); })); }).lift(OperatorPivot.create()).map(commandGroup -> { // merge all instances per group into a single stream of deltas and sum them return createGroupedObservable(commandGroup.getKey(), commandGroup.flatMap(instanceGroup -> { return instanceGroup.startWith(Collections. emptyMap()) .buffer(2, 1) .map(StreamAggregator::previousAndCurrentToDelta) .filter(data -> data != null && !data.isEmpty()); }).scan(new HashMap(), StreamAggregator::sumOfDelta) .skip(1)); }); }

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

private static Observable>> aggregateUsingPivot(Observable>> instanceStreams) { return instanceStreams.map(instanceStream -> { return createGroupedObservable(instanceStream.getKey(), instanceStream .groupBy((Map json) -> { return TypeAndNameKey.from(String.valueOf(json.get("type")), String.valueOf(json.get("name"))); })); }).lift(OperatorPivot.create()).map(commandGroup -> { // merge all instances per group into a single stream of deltas and sum them return createGroupedObservable(commandGroup.getKey(), commandGroup.flatMap(instanceGroup -> { return instanceGroup.startWith(Collections. emptyMap()) .buffer(2, 1) .map(StreamAggregator::previousAndCurrentToDelta) .filter(data -> data != null && !data.isEmpty()); }).scan(new HashMap(), StreamAggregator::sumOfDelta) .skip(1)); }); }

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

private static Observable>> aggregateUsingPivot(Observable>> instanceStreams) { return instanceStreams.map(instanceStream -> { return createGroupedObservable(instanceStream.getKey(), instanceStream .groupBy((Map json) -> { return TypeAndNameKey.from(String.valueOf(json.get("type")), String.valueOf(json.get("name"))); })); }).lift(OperatorPivot.create()).map(commandGroup -> { // merge all instances per group into a single stream of deltas and sum them return createGroupedObservable(commandGroup.getKey(), commandGroup.flatMap(instanceGroup -> { return instanceGroup.startWith(Collections. emptyMap()) .buffer(2, 1) .map(StreamAggregator::previousAndCurrentToDelta) .filter(data -> data != null && !data.isEmpty()); }).scan(new HashMap(), StreamAggregator::sumOfDelta) .skip(1)); }); }

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

data: {“type":"HystrixCommand","name":"GetPredictions","requestCount":317,"rollingCountSuccess":327,"reportingHosts":1} data: {“type":"HystrixCommand","name":"GetPredictions","requestCount":1376,"rollingCountSuccess":1432,"reportingHosts":4}

Slide 65

Slide 65 text

data: {“type":"HystrixCommand","name":"GetPredictions","requestCount":317,"rollingCountSuccess":327,"reportingHosts":1} data: {“type":"HystrixCommand","name":"GetPredictions","requestCount":1376,"rollingCountSuccess":1432,"reportingHosts":4}

Slide 66

Slide 66 text

data => {"rollingCountFallbackFailure":0,"rollingCountFallbackSuccess": 0,"propertyValue_circuitBreakerRequestVolumeThreshold":"20","propertyValue_circuitBreakerForceOpen":false,"propertyValue_metricsRol lingStatisticalWindowInMilliseconds":"10000","latencyTotal_mean":32,"type":"HystrixCommand","rollingCountResponsesFromCache": 0,"rollingCountTimeout":0,"propertyValue_executionIsolationStrategy":"SEMAPHORE","instanceId":"34567","rollingCountFailure": 0,"rollingCountExceptionsThrown":0,"latencyExecute_mean":32,"isCircuitBreakerOpen":false,"errorCount": 0,"rollingCountSemaphoreRejected":0,"latencyTotal":{"0":0,"25":0,"50":12,"75":48,"90":68,"95":96,"99":192,"99.5":1452,"100": 1560},"requestCount":1376,"rollingCountCollapsedRequests":94516,"rollingCountShortCircuited":0,"latencyExecute":{"0":0,"25":0,"50": 12,"75":48,"90":68,"95":96,"99":192,"99.5":1452,"100": 1560},"propertyValue_circuitBreakerSleepWindowInMilliseconds":"5000","currentConcurrentExecutionCount": 0,"propertyValue_executionIsolationSemaphoreMaxConcurrentRequests":"10","errorPercentage":0,"rollingCountThreadPoolRejected": 0,"propertyValue_circuitBreakerEnabled":true,"propertyValue_executionIsolationThreadInterruptOnTimeout":true,"propertyValue_request CacheEnabled":true,"rollingCountFallbackRejection":0,"propertyValue_requestLogEnabled":true,"rollingCountSuccess": 1432,"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests":"10","propertyValue_circuitBreakerErrorThresholdPercentage":"5 0","propertyValue_circuitBreakerForceClosed":false,"name":"GetPredictions","reportingHosts": 4,"propertyValue_executionIsolationThreadPoolKeyOverride":"null","propertyValue_executionIsolationThreadTimeoutInMilliseconds":"100 0"} data: {“type":"HystrixCommand","name":"GetPredictions","requestCount":317,"rollingCountSuccess":327,"reportingHosts":1} data: {“type":"HystrixCommand","name":"GetPredictions","requestCount":1376,"rollingCountSuccess":1432,"reportingHosts":4}

Slide 67

Slide 67 text

Flow Control

Slide 68

Slide 68 text

Flow Control (backpressure)

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

Observable.from(iterable).take(1000).map(i -> "value_" + i).subscribe(System.out::println); no backpressure needed synchronous on same thread (no queueing)

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

Flow Control Options

Slide 74

Slide 74 text

Block (callstack blocking and/or park the thread)

Slide 75

Slide 75 text

Temporal Operators (batch or drop data using time)

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

Observable.range(1, 1000000).throttleFirst(10, TimeUnit.MILLISECONDS).forEach(System.out::println); 1 55463 163962 308545 457445 592638 751789 897159

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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 80

Slide 80 text

No content

Slide 81

Slide 81 text

/* 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 burstStream = intermittentBursts().take(20).publish().refCount(); // then we get the debounced version Observable debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS); // then the buffered one that uses the debounced stream to demark window start/stop Observable> 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 82

Slide 82 text

/* 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 burstStream = intermittentBursts().take(20).publish().refCount(); // then we get the debounced version Observable debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS); // then the buffered one that uses the debounced stream to demark window start/stop Observable> 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 83

Slide 83 text

/* 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 burstStream = intermittentBursts().take(20).publish().refCount(); // then we get the debounced version Observable debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS); // then the buffered one that uses the debounced stream to demark window start/stop Observable> 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 84

Slide 84 text

/* 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 burstStream = intermittentBursts().take(20).publish().refCount(); // then we get the debounced version Observable debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS); // then the buffered one that uses the debounced stream to demark window start/stop Observable> 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 85

Slide 85 text

/* 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 burstStream = intermittentBursts().take(20).publish().refCount(); // then we get the debounced version Observable debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS); // then the buffered one that uses the debounced stream to demark window start/stop Observable> 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 86

Slide 86 text

/* 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 burstStream = intermittentBursts().take(20).publish().refCount(); // then we get the debounced version Observable debounced = burstStream.debounce(10, TimeUnit.MILLISECONDS); // then the buffered one that uses the debounced stream to demark window start/stop Observable> 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 87

Slide 87 text

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 88

Slide 88 text

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 89

Slide 89 text

Reactive Pull (dynamic push-pull)

Slide 90

Slide 90 text

Push when consumer keeps up with producer. ! Switch to Pull when consumer is slow. ! Bound all* queues.

Slide 91

Slide 91 text

Push when consumer keeps up with producer. ! Switch to Pull when consumer is slow. ! Bound all* queues. *vertically, not horizontally

Slide 92

Slide 92 text

Observable Subscriber Observable.create(subscriber -> { // emit to subscriber }).subscribe(new Subscriber() { ! public void onCompleted() { // do stuff } ! public void onError(Throwable e) { // handle error } ! public void onNext(String t) { ! } ! });

Slide 93

Slide 93 text

observable.subscribe Observable.create(subscriber -> { // emit to subscriber }).subscribe(new Subscriber() { ! public void onCompleted() { // do stuff } ! public void onError(Throwable e) { // handle error } ! public void onNext(String t) { ! } ! });

Slide 94

Slide 94 text

observable.subscribe subscriber.setProducer Observable.create(subscriber -> { subscriber.setProducer(request -> { }); }).subscribe(new Subscriber() { ! public void onCompleted() { // do stuff } ! public void onError(Throwable e) { // handle error } ! public void onNext(String t) { ! } ! });

Slide 95

Slide 95 text

Observable.create(subscriber -> { subscriber.setProducer(request -> { }); }).subscribe(new Subscriber() { ! public void onStart() { request(5); } ! public void onCompleted() { // do stuff } ! public void onError(Throwable e) { // handle error } ! public void onNext(String t) { ! } ! }); observable.subscribe subscriber.setProducer producer.request(n)

Slide 96

Slide 96 text

Observable.create(subscriber -> { subscriber.setProducer(request -> { subscriber.onNext(t); // 5 times }); }).subscribe(new Subscriber() { ! public void onStart() { request(5); } ! public void onCompleted() { // do stuff } ! public void onError(Throwable e) { // handle error } ! public void onNext(String t) { ! } ! }); observable.subscribe subscriber.setProducer producer.request(n) subscriber.onNext(t) …

Slide 97

Slide 97 text

Observable.create(subscriber -> { subscriber.setProducer(request -> { subscriber.onNext(t); // 5 times }); }).subscribe(new Subscriber() { ! public void onStart() { request(5); } ! public void onCompleted() { // do stuff } ! public void onError(Throwable e) { // handle error } ! public void onNext(String t) { doStuffWith(t); if(requested—- == 0) request(5); } ! }); observable.subscribe subscriber.setProducer producer.request(n) subscriber.onNext(t) … producer.request(n)

Slide 98

Slide 98 text

Observable.create(subscriber -> { subscriber.setProducer(request -> { … previous logic … subscriber.onCompleted(); }); }).subscribe(new Subscriber() { ! public void onStart() { request(5); } ! public void onCompleted() { // do stuff } ! public void onError(Throwable e) { // handle error } ! public void onNext(String t) { doStuffWith(t); if(requested—- == 0) request(5); } ! }); observable.subscribe subscriber.setProducer producer.request(n) subscriber.onNext(t) … producer.request(n) subscriber.onCompleted/onError unsubscribe

Slide 99

Slide 99 text

Observable Subscriber Observable Subscriber observable.subscribe observable.subscribe

Slide 100

Slide 100 text

Observable.from(iterable) .observeOn(Schedulers.computation()) .subscribe(System.out::println);

Slide 101

Slide 101 text

Observable.from(iterable) .observeOn(Schedulers.computation()) .subscribe(System.out::println);

Slide 102

Slide 102 text

Observable Subscriber Observable Subscriber observable.subscribe observable.subscribe

Slide 103

Slide 103 text

Observable Subscriber Observable Subscriber subscriber.setProducer subscriber.setProducer

Slide 104

Slide 104 text

Observable Subscriber Observable Subscriber producer.request(n) producer.request(5) outstanding requested = 5

Slide 105

Slide 105 text

Observable Subscriber Observable Subscriber producer.request(n) producer.request(5) outstanding requested = 5

Slide 106

Slide 106 text

Observable Subscriber Observable Subscriber producer.request(n) producer.request(5) outstanding requested = 5

Slide 107

Slide 107 text

Observable Subscriber Observable Subscriber subscriber.onNext outstanding requested = 5

Slide 108

Slide 108 text

Observable Subscriber Observable Subscriber outstanding requested = 5 subscriber.onNext

Slide 109

Slide 109 text

Observable Subscriber Observable Subscriber outstanding requested = 4 subscriber.onNext

Slide 110

Slide 110 text

Observable Subscriber Observable Subscriber outstanding requested = 3 subscriber.onNext

Slide 111

Slide 111 text

Observable Subscriber Observable Subscriber outstanding requested = 3 subscriber.onNext producer.request(1) request(1)

Slide 112

Slide 112 text

Observable Subscriber Observable Subscriber outstanding requested = 4 subscriber.onNext producer.request(1)

Slide 113

Slide 113 text

Observable Subscriber Observable Subscriber outstanding requested = 4 subscriber.onNext subscriber.onNext

Slide 114

Slide 114 text

Observable Subscriber Observable Subscriber outstanding requested = 0 subscriber.onNext

Slide 115

Slide 115 text

Observable Subscriber Observable Subscriber outstanding requested = 0 subscriber.onNext

Slide 116

Slide 116 text

Observable Subscriber Observable Subscriber outstanding requested = 0 subscriber.onNext

Slide 117

Slide 117 text

Observable Subscriber Observable Subscriber outstanding requested = 0 subscriber.onNext

Slide 118

Slide 118 text

Observable Subscriber Observable Subscriber outstanding requested = 0 subscriber.onNext

Slide 119

Slide 119 text

Observable Subscriber Observable Subscriber outstanding requested = 2 subscriber.onNext request(2) producer.request(2)

Slide 120

Slide 120 text

someObservable.subscribe(new Subscriber() { @Override public void onStart() { request(1); } ! @Override public void onCompleted() { // gracefully handle sequence-complete } ! @Override public void onError(Throwable e) { // gracefully handle error } ! @Override public void onNext(T n) { // do something with the emitted item "n" // request another item: request(1); } });

Slide 121

Slide 121 text

someObservable.subscribe(new Subscriber() { @Override public void onStart() { request(1); } ! @Override public void onCompleted() { // gracefully handle sequence-complete } ! @Override public void onError(Throwable e) { // gracefully handle error } ! @Override public void onNext(T n) { // do something with the emitted item "n" // request another item: request(1); } });

Slide 122

Slide 122 text

someObservable.subscribe(new Subscriber() { @Override public void onStart() { request(1); } ! @Override public void onCompleted() { // gracefully handle sequence-complete } ! @Override public void onError(Throwable e) { // gracefully handle error } ! @Override public void onNext(T n) { // do something with the emitted item "n" // request another item: request(1); } });

Slide 123

Slide 123 text

someObservable.subscribe(new Subscriber() { @Override public void onStart() { request(1024); } ! @Override public void onCompleted() { // gracefully handle sequence-complete } ! @Override public void onError(Throwable e) { // gracefully handle error } ! @Override public void onNext(T n) { enqueue(n); // elsewhere the queue is drained // and request(m) is called } ! });

Slide 124

Slide 124 text

someObservable.subscribe(new Subscriber() { @Override public void onStart() { request(1024); } ! @Override public void onCompleted() { // gracefully handle sequence-complete } ! @Override public void onError(Throwable e) { // gracefully handle error } ! @Override public void onNext(T n) { enqueue(n); // elsewhere the queue is drained // and request(m) is called } ! });

Slide 125

Slide 125 text

someObservable.subscribe(new Subscriber() { @Override public void onStart() { request(1024); } ! @Override public void onCompleted() { // gracefully handle sequence-complete } ! @Override public void onError(Throwable e) { // gracefully handle error } ! @Override public void onNext(T n) { enqueue(n); // elsewhere the queue is drained // and request(m) is called } ! });

Slide 126

Slide 126 text

Reactive Pull hot vs cold?

Slide 127

Slide 127 text

Reactive Pull … or when source doesn’t support request(n)?

Slide 128

Slide 128 text

hotSourceStream.onBackpressureBuffer().observeOn(aScheduler);

Slide 129

Slide 129 text

hotSourceStream.onBackpressureBuffer().observeOn(aScheduler);

Slide 130

Slide 130 text

hotSourceStream.onBackpressureBuffer().observeOn(aScheduler);

Slide 131

Slide 131 text

hotSourceStream.onBackpressureBuffer().observeOn(aScheduler);

Slide 132

Slide 132 text

hotSourceStream.onBackpressureDrop().observeOn(aScheduler);

Slide 133

Slide 133 text

stream.onBackpressure(strategy).subscribe

Slide 134

Slide 134 text

distributed

Slide 135

Slide 135 text

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 136

Slide 136 text

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 137

Slide 137 text

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 138

Slide 138 text

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 139

Slide 139 text

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 140

Slide 140 text

(movieId) movieId=12345 movieId=34567

Slide 141

Slide 141 text

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 142

Slide 142 text

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 143

Slide 143 text

Stage 1 Stage 2 groupBy Event Streams sink

Slide 144

Slide 144 text

Stage 1 Stage 2 12345 56789 34567

Slide 145

Slide 145 text

Stage 1 Stage 2 12345 56789 34567

Slide 146

Slide 146 text

Stage 1 Stage 2 12345 56789 34567

Slide 147

Slide 147 text

Stage 1 Stage 2 12345 56789 34567

Slide 148

Slide 148 text

Stage 1 Stage 2 12345 56789 34567 12345 56789 34567

Slide 149

Slide 149 text

Stage 1 Stage 2 12345 56789 34567 12345 56789 34567

Slide 150

Slide 150 text

Stage 1 Stage 2 12345 56789 34567 12345 56789 34567

Slide 151

Slide 151 text

Stage 1 Stage 2 12345 56789 34567 12345 56789 34567

Slide 152

Slide 152 text

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 153

Slide 153 text

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

Slide 154

Slide 154 text

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

Slide 155

Slide 155 text

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 156

Slide 156 text

flatMap

Slide 157

Slide 157 text

flatMap

Slide 158

Slide 158 text

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 159

Slide 159 text

No content

Slide 160

Slide 160 text

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 161

Slide 161 text

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 162

Slide 162 text

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 163

Slide 163 text

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 164

Slide 164 text

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 165

Slide 165 text

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 166

Slide 166 text

stream.onBackpressure(strategy?).subscribe

Slide 167

Slide 167 text

stream.onBackpressure(buffer).subscribe

Slide 168

Slide 168 text

stream.onBackpressure(drop).subscribe

Slide 169

Slide 169 text

stream.onBackpressure(sample).subscribe

Slide 170

Slide 170 text

stream.onBackpressure(scaleHorizontally).subscribe

Slide 171

Slide 171 text

finite infinite distributed

Slide 172

Slide 172 text

Single Multiple Sync T getData() Iterable getData() Async Future getData() Observable getData()

Slide 173

Slide 173 text

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

Slide 174

Slide 174 text

! 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