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 Slide

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

    View 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 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 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 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 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 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 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 Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. Abstract Concurrency

    View Slide

  14. 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 Slide

  15. 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 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 Slide

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

    View Slide

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

    View 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 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 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 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 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 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 Slide

  25. 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 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 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 Slide

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

    View 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 Slide

  30. 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 Slide

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

    View Slide

  33. 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 Slide

  34. 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 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)));
    });
    }
    1
    2
    3
    4
    5
    6

    View Slide

  36. 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 Slide

  37. Non-Opinionated Concurrency

    View Slide

  38. View Slide

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

  40. View Slide

  41. View Slide

  42. View Slide

  43. View Slide

  44. 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 Slide

  45. View Slide

  46. 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 Slide

  47. 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 Slide

  48. 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 Slide

  49. 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 Slide

  50. 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 Slide

  51. 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 Slide

  52. 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 Slide

  53. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  58. Service Layer Impl & Service Clients
    RxNetty
    +

    View Slide

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

    View Slide

  60. 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 Slide

  61. 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 Slide

  62. 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 Slide

  63. 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 Slide

  64. 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 Slide

  65. 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 Slide

  66. 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 Slide

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

    View Slide

  68. View Slide

  69. Note: This is a mockup

    View Slide

  70. Note: This is a mockup

    View Slide

  71. 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 Slide

  72. 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 Slide

  73. 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 Slide

  74. 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 Slide

  75. http://ReactiveX.io

    View Slide

  76. !
    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 Slide