Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Reactive Programming with Rx at NYTimes

Reactive Programming with Rx at NYTimes

Reactive programming in the form of RxJS and RxJava has been adopted at Netflix over the past couple years for both client-side and server-side programming. This talk summarizes why the Rx programming model was chosen and demonstrates how it is applied to a variety of use cases including web services, interprocess communication and stream processing.

Presented at New York Times: http://developers.nytimes.com/events/

Ben Christensen

September 10, 2014
Tweet

More Decks by Ben Christensen

Other Decks in Programming

Transcript

  1. Ben Christensen
    Software Engineer – Edge Engineering at Netflix
    @benjchristensen
    http://www.linkedin.com/in/benjchristensen
    !
    !
    !
    !
    !
    http://techblog.netflix.com/
    New York Times - September 2014
    Reactive Programming with Rx

    View full-size slide

  2. Reactive Extensions
    for Async Programming
    RxJava
    http://github.com/ReactiveX/RxJava
    http://reactivex.io

    View full-size slide

  3. Reactive Extensions
    for Async Programming
    RxJava
    http://github.com/ReactiveX/RxJava
    http://reactivex.io
    Single Multiple
    Sync T getData() Iterable getData()
    Async Future getData() Observable getData()

    View full-size slide

  4. Single Multiple
    Sync T getData() Iterable getData()
    Async Future getData() Observable getData()
    Observable.create(subscriber -> {
    subscriber.onNext("Hello world!");
    subscriber.onCompleted();
    }).forEach(System.out::println);
    Synchronous
    single value

    View full-size slide

  5. Single Multiple
    Sync T getData() Iterable getData()
    Async Future getData() Observable getData()
    Observable.create(subscriber -> {
    subscriber.onNext("Hello");
    subscriber.onNext("world");
    subscriber.onNext("!");
    subscriber.onCompleted();
    }).forEach(System.out::println);
    Synchronous
    multi-value

    View full-size slide

  6. Single Multiple
    Sync T getData() Iterable getData()
    Async Future getData() Observable getData()
    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
    with error handling

    View full-size slide

  7. Single Multiple
    Sync T getData() Iterable getData()
    Async Future getData() Observable getData()
    Observable.create(subscriber -> {
    int i = 0;
    while (!subscriber.isUnsubscribed()) {
    subscriber.onNext(i++);
    }
    }).take(10).forEach(System.out::println);
    Synchronous
    multi-value
    with unsubscribe

    View full-size slide

  8. Single Multiple
    Sync T getData() Iterable getData()
    Async Future getData() Observable getData()
    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
    multi-valued source
    that thread-hops
    with “reactive pull”
    backpressure

    View full-size slide

  9. Single Multiple
    Sync T getData() Iterable getData()
    Async Future getData() Observable getData()
    Observable.from(iterable)
    .observeOn(Schedulers.newThread())
    .take(10).forEach(System.out::println);
    Synchronous
    multi-valued source
    that thread-hops
    with “reactive pull”
    backpressure

    View full-size slide

  10. Abstract Concurrency

    View full-size slide

  11. 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)));
    });
    }

    View full-size slide

  12. 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)));
    });
    }

    View full-size slide

  13. 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)));
    });
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  16. 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)));
    });
    }

    View full-size slide

  17. 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)));
    });
    }

    View full-size slide

  18. 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)));
    });
    }

    View full-size slide

  19. 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)));
    });
    }

    View full-size slide

  20. 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)));
    });
    }

    View full-size slide

  21. 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)));
    });
    }

    View full-size slide

  22. 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)));
    });
    }

    View full-size slide

  23. 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)));
    });
    }

    View full-size slide

  24. 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)));
    });
    }

    View full-size slide

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

    View full-size slide

  26. 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)));
    });
    }

    View full-size slide

  27. 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)));
    });
    }

    View full-size slide

  28. 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)));
    });
    }

    View full-size slide

  29. 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)));
    });
    }

    View full-size slide

  30. class  VideoService  {  
         def  VideoList  getPersonalizedListOfMovies(userId);  
         def  VideoBookmark  getBookmark(userId,  videoId);  
         def  VideoRating  getRating(userId,  videoId);  
         def  VideoMetadata  getMetadata(videoId);  
    }
    class  VideoService  {  
         def  Observable  getPersonalizedListOfMovies(userId);  
         def  Observable  getBookmark(userId,  videoId);  
         def  Observable  getRating(userId,  videoId);  
         def  Observable  getMetadata(videoId);  
    }
    ... create an observable api:
    instead of a blocking api ...

    View full-size slide

  31. 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

    View full-size slide

  32. 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

    View full-size slide

  33. Non-Opinionated Concurrency

    View full-size slide

  34. 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)));
    });
    }

    View full-size slide

  35. 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)));
    });
    }

    View full-size slide

  36. Decouples Consumption from Production
    // 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);
    })

    View full-size slide

  37. Decouples Consumption from Production
    // 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);
    })

    View full-size slide

  38. Decouples Consumption from Production
    // 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);
    })

    View full-size slide

  39. 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

    View full-size slide

  40. Decouples Consumption from Production
    // 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);
    })
    1
    2
    3
    4
    5

    View full-size slide

  41. class  VideoService  {  
         def  Observable  getPersonalizedListOfMovies(userId);  
         def  Observable  getBookmark(userId,  videoId);  
         def  Observable  getRating(userId,  videoId);  
         def  Observable  getMetadata(videoId);  
    }
    Clear API Communicates Potential Cost

    View full-size slide

  42. class  VideoService  {  
         def  Observable  getPersonalizedListOfMovies(userId);  
         def  Observable  getBookmark(userId,  videoId);  
         def  Observable  getRating(userId,  videoId);  
         def  Observable  getMetadata(videoId);  
    }
    Implementation Can Differ
    BIO Network Call
    Local Cache Collapsed
    Network Call

    View full-size slide

  43. class  VideoService  {  
         def  Observable  getPersonalizedListOfMovies(userId);  
         def  Observable  getBookmark(userId,  videoId);  
         def  Observable  getRating(userId,  videoId);  
         def  Observable  getMetadata(videoId);  
    }
    Implementation Can Differ and Change
    Local Cache Collapsed
    Network Call
    Collapsed
    Network Call
    BIO NIO Network Call

    View full-size slide

  44. Retrieval, Transformation, Combination
    all done in same declarative manner

    View full-size slide

  45. Front Controller and Web Service Endpoints
    Service Layer Impl & Service Clients
    Observable APIs
    &
    Reactive
    Programming

    View full-size slide

  46. Front Controller and Web Service Endpoints
    Service Layer Impl & Service Clients
    Imperative
    &
    Blocking
    Service Layer Impl & Service Clients

    View full-size slide

  47. Front Controller and Web Service Endpoints
    Service Layer Impl & Service Clients
    Can we do
    better here?
    Service Layer Impl & Service Clients

    View full-size slide

  48. Service Layer Impl & Service Clients
    RxNetty
    +

    View full-size slide

  49. WSPerfLab
    https://github.com/Netflix-Skunkworks/WSPerfLab

    View full-size slide

  50. Latency in Milliseconds
    0
    1,250
    2,500
    3,750
    5,000
    Latency Percentile
    50th 66th 75th 80th 90th 95th 98th 99th 100th
    Tomcat -c200: 969 r/s Tomcat -c400: 1142 r/s
    RxNetty -c200: 1264 r/s RxNetty -c400: 2077 r/s

    View full-size slide

  51. Latency in Milliseconds
    0
    100
    200
    300
    400
    Latency Percentile
    50th 66th 75th 80th 90th 95th 98th 99th 100th
    Tomcat -c200: 969 r/s Tomcat -c400: 1142 r/s
    RxNetty -c200: 1264 r/s RxNetty -c400: 2077 r/s

    View full-size slide

  52. Latency in Milliseconds
    0
    1,250
    2,500
    3,750
    5,000
    Latency Percentile
    50th 66th 75th 80th 90th 95th 98th 99th 100th
    Tomcat -c200: 969 r/s Tomcat -c400: 1142 r/s
    RxNetty -c200: 1264 r/s RxNetty -c400: 2077 r/s

    View full-size slide

  53. Latency in Milliseconds
    0
    1,250
    2,500
    3,750
    5,000
    Latency Percentile
    50th 66th 75th 80th 90th 95th 98th 99th 100th
    Tomcat -c200: 969 r/s Tomcat -c400: 1142 r/s
    RxNetty -c200: 1264 r/s RxNetty -c400: 2077 r/s
    458
    1217

    View full-size slide

  54. Latency in Milliseconds
    0
    150
    300
    450
    600
    Latency Percentile
    50th 66th 75th 80th 90th 95th 98th 99th 100th
    Tomcat -c200: 969 r/s Tomcat -c400: 1142 r/s
    RxNetty -c200: 1264 r/s RxNetty -c400: 2077 r/s
    Tomcat -c400: 1142 r/s
    RxNetty -c200: 1264 r/s
    RxNetty -c400: 2077 r/s
    Tomcat -c200: 969 r/s

    View full-size slide

  55. Latency in Milliseconds
    0
    150
    300
    450
    600
    Latency Percentile
    50th 66th 75th 80th 90th 95th 98th 99th 100th
    Tomcat -c200: 969 r/s Tomcat -c400: 1142 r/s
    RxNetty -c200: 1264 r/s RxNetty -c400: 2077 r/s
    Tomcat -c400: 1142 r/s
    RxNetty -c200: 1264 r/s
    RxNetty -c400: 2077 r/s
    Tomcat -c200: 969 r/s

    View full-size slide

  56. Latency in Milliseconds
    0
    150
    300
    450
    600
    Latency Percentile
    50th 66th 75th 80th 90th 95th 98th 99th 100th
    Tomcat -c200: 969 r/s Tomcat -c400: 1142 r/s
    RxNetty -c200: 1264 r/s RxNetty -c400: 2077 r/s
    Tomcat -c400: 1142 r/s
    Tomcat -c200: 969 r/s
    RxNetty -c200: 1264 r/s
    RxNetty -c400: 2077 r/s
    458
    1217

    View full-size slide

  57. Current status … moving on to
    production testing
    with Netty-based IPC

    View full-size slide

  58. Note: This is a mockup

    View full-size slide

  59. Note: This is a mockup

    View full-size slide

  60. 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));
    });
    }

    View full-size slide

  61. 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));
    });
    }

    View full-size slide

  62. 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));
    });
    }

    View full-size slide

  63. 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));
    });
    }

    View full-size slide

  64. http://ReactiveX.io

    View full-size slide

  65. !
    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
    !
    !
    !
    !
    Ben Christensen
    @benjchristensen
    http://www.linkedin.com/in/benjchristensen
    RxJava
    https://github.com/ReactiveX/RxJava
    @RxJava
    RxJS
    http://reactive-extensions.github.io/RxJS/
    @ReactiveX
    jobs.netflix.com

    View full-size slide