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

Applying RxJava to Existing Applications at Philly ETE 2015

Applying RxJava to Existing Applications at Philly ETE 2015

Rarely do we have a chance to rewrite an application from scratch with the newest techniques. Generally we must work within our existing architectures evolving and refactoring years of code. This can make it seem reactive programming either can’t be introduced or doesn’t play a role.

Netflix has been doing reactive programming with RxJava in production for several years and only recently embarked on “greenfield” development efforts that are fully async. This talk will leverage the experience of introducing reactive programming into existing imperative, blocking codebases to demonstrate how it can be done and when it can make sense to do so. Not all benefits of reactive programming can be obtained without a greenfield, fully async architecture, but many can. Subjects to be covered will include the mental shift from imperative to declarative, working with blocking IO such as JDBC and RPC, service composition, debugging and unit testing.

Presented at Philly ETE 2015 in Philadelphia http://phillyemergingtech.com/sessions/applying-reactive-programming-to-existing-applications/

Screencast: http://chariotsolutions.com/screencast/philly-ete-2015-3-ben-christensen-applying-reactive-programming-to-existing-applications/

Video: http://www.infoq.com/presentations/reactive-programming

Ben Christensen

April 08, 2015
Tweet

More Decks by Ben Christensen

Other Decks in Programming

Transcript

  1. Observable<Order> getOrders(int customerId) Observable<Product> getProducts(Order order) Observable<ShippingStatus> getShippingStatus(Order o, Product

    p) getOrdersAsync → getProductsAsync → getShippingStatusAsync getOrders → getProducts → getShippingStatus
  2. Observable<Order> getOrders(int customerId) Observable<Product> getProducts(Order order) Observable<ShippingStatus> getShippingStatus(Order o, Product

    p) getOrdersAsync → getProductsAsync → getShippingStatusAsync Observable APIs getOrders → getProducts → getShippingStatus
  3. getOrdersAsync(1) .limit(1) .flatMap(o -> { return getProductsAsync(o) .flatMap(p -> {

    return getShippingStatusAsync(o, p); }); }) .forEach(s -> System.out.println(Thread.currentThread() + " [Async] Shipping Status: " + s));
  4. getOrdersAsync(1) .limit(1) .flatMap(o -> { return getProductsAsync(o) .flatMap(p -> {

    return getShippingStatusAsync(o, p); }); }) .forEach(s -> System.out.println(Thread.currentThread() + " [Async] Shipping Status: " + s)); an async Observable
  5. getOrdersAsync(1) .limit(1) .flatMap(o -> { return getProductsAsync(o) .flatMap(p -> {

    return getShippingStatusAsync(o, p); }); }) .forEach(s -> System.out.println(Thread.currentThread() + " [Async] Shipping Status: " + s)); an async Observable stream
  6. getOrdersAsync(1) .limit(1) .flatMap(o -> { return getProductsAsync(o) .flatMap(p -> {

    return getShippingStatusAsync(o, p); }); }) .forEach(s -> System.out.println(Thread.currentThread() + " [Async] Shipping Status: " + s)); another async Observable stream
  7. getOrdersAsync(1) .limit(1) .flatMap(o -> { return getProductsAsync(o) .flatMap(p -> {

    return getShippingStatusAsync(o, p); }); }) .forEach(s -> System.out.println(Thread.currentThread() + " [Async] Shipping Status: " + s));
  8. Observable<ShippingStatus> getShippingStatusAsync(Order o, Product p) return Observable.defer(() -> { return

    Observable.just(getShippingStatus(o, p)); }).subscribeOn(Schedulers.io());
  9. Observable<ShippingStatus> getShippingStatusAsync(Order o, Product p) return Observable.defer(() -> { return

    Observable.just(getShippingStatus(o, p)); }).subscribeOn(Schedulers.io());
  10. Observable<ShippingStatus> getShippingStatusAsync(Order o, Product p) return Observable.defer(() -> { return

    Observable.just(getShippingStatus(o, p)); }).subscribeOn(Schedulers.io());
  11. Observable<ShippingStatus> getShippingStatusAsync(Order o, Product p) return Observable.defer(() -> { return

    Observable.just(getShippingStatus(o, p)); }).subscribeOn(Schedulers.io());
  12. Observable<ShippingStatus> getShippingStatusAsync(Order o, Product p) return Observable.defer(() -> { return

    Observable.just(getShippingStatus(o, p)); }).subscribeOn(Schedulers.io());
  13. Observable<ShippingStatus> getShippingStatusAsync(Order o, Product p) return Observable.defer(() -> { return

    Observable.just(getShippingStatus(o, p)); }).subscribeOn(Schedulers.io());
  14. List<Order> orders = getOrders(1); try (Connection c = Database.getConnection()) {

    ResultSet rs = c.createStatement() .executeQuery("select * from orders"); ArrayList<Order> orders = new ArrayList<>(); while (rs.next()) { orders.add(new Order(rs.getInt(1))); } rs.close(); return orders; } catch (Exception e) { throw new RuntimeException(e); }
  15. List<Order> orders = getOrders(1); try (Connection c = Database.getConnection()) {

    ResultSet rs = c.createStatement() .executeQuery("select * from orders"); ArrayList<Order> orders = new ArrayList<>(); while (rs.next()) { orders.add(new Order(rs.getInt(1))); } rs.close(); return orders; } catch (Exception e) { throw new RuntimeException(e); }
  16. Observable<Order> orders = getOrdersAsync(1); return Observable.<Order> create(o -> { try

    (Connection c = Database.getConnection()) { ResultSet rs = c.createStatement() .executeQuery("select * from orders"); while (rs.next() && !o.isUnsubscribed()) { o.onNext(new Order(rs.getInt(1))); } rs.close(); o.onCompleted(); } catch (Exception e) { o.onError(e); } }).subscribeOn(Schedulers.io());
  17. Observable<Order> orders = getOrdersAsync(1); return Observable.<Order> create(o -> { try

    (Connection c = Database.getConnection()) { ResultSet rs = c.createStatement() .executeQuery("select * from orders"); while (rs.next() && !o.isUnsubscribed()) { o.onNext(new Order(rs.getInt(1))); } rs.close(); o.onCompleted(); } catch (Exception e) { o.onError(e); } }).subscribeOn(Schedulers.io());
  18. return Observable.<Order> create(o -> { try (Connection c = Database.getConnection())

    { ResultSet rs = c.createStatement() .executeQuery("select * from orders"); while (rs.next() && !o.isUnsubscribed()) { o.onNext(new Order(rs.getInt(1))); } rs.close(); o.onCompleted(); } catch (Exception e) { o.onError(e); } }).subscribeOn(Schedulers.io()); Observable<Order> orders = getOrdersAsync(1);
  19. return Observable.<Order> create(o -> { try (Connection c = Database.getConnection())

    { ResultSet rs = c.createStatement() .executeQuery("select * from orders"); while (rs.next() && !o.isUnsubscribed()) { o.onNext(new Order(rs.getInt(1))); } rs.close(); o.onCompleted(); } catch (Exception e) { o.onError(e); } }).subscribeOn(Schedulers.io()); Observable<Order> orders = getOrdersAsync(1);
  20. return Observable.<Order> create(o -> { try (Connection c = Database.getConnection())

    { ResultSet rs = c.createStatement() .executeQuery("select * from orders"); while (rs.next() && !o.isUnsubscribed()) { o.onNext(new Order(rs.getInt(1))); } rs.close(); o.onCompleted(); } catch (Exception e) { o.onError(e); } }).subscribeOn(Schedulers.io()); Observable<Order> orders = getOrdersAsync(1);
  21. return Observable.<Order> create(o -> { try (Connection c = Database.getConnection())

    { ResultSet rs = c.createStatement() .executeQuery("select * from orders"); while (rs.next() && !o.isUnsubscribed()) { o.onNext(new Order(rs.getInt(1))); } rs.close(); o.onCompleted(); } catch (Exception e) { o.onError(e); } }).subscribeOn(Schedulers.io()); Observable<Order> orders = getOrdersAsync(1);
  22. return Observable.create(AbstractOnSubscribe.<Order, ResultSet> create(s -> { ResultSet rs = s.state();

    try { if (rs.next()) { s.onNext(new Order(rs.getInt(1))); } else { s.onCompleted(); } } catch (SQLException e) { s.onError(e); } }, s -> { Connection c = Database.getConnection(); try { Statement stmt = c.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); return stmt.executeQuery("select * from orders"); } catch (SQLException e) { s.onError(e); return null; } }, rs -> rs.close() } // ignoring try/catch for slide brevity )).subscribeOn(Schedulers.io());
  23. return Observable.create(AbstractOnSubscribe.<Order, ResultSet> create(s -> { ResultSet rs = s.state();

    try { if (rs.next()) { s.onNext(new Order(rs.getInt(1))); } else { s.onCompleted(); } } catch (SQLException e) { s.onError(e); } }, s -> { Connection c = Database.getConnection(); try { Statement stmt = c.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); return stmt.executeQuery("select * from orders"); } catch (SQLException e) { s.onError(e); return null; } }, rs -> rs.close() } // ignoring try/catch for slide brevity )).subscribeOn(Schedulers.io());
  24. return Observable.create(AbstractOnSubscribe.<Order, ResultSet> create(s -> { ResultSet rs = s.state();

    try { if (rs.next()) { s.onNext(new Order(rs.getInt(1))); } else { s.onCompleted(); } } catch (SQLException e) { s.onError(e); } }, s -> { Connection c = Database.getConnection(); try { Statement stmt = c.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); return stmt.executeQuery("select * from orders"); } catch (SQLException e) { s.onError(e); return null; } }, rs -> rs.close() } // ignoring try/catch for slide brevity )).subscribeOn(Schedulers.io());
  25. return Observable.create(AbstractOnSubscribe.<Order, ResultSet> create(s -> { ResultSet rs = s.state();

    try { if (rs.next()) { s.onNext(new Order(rs.getInt(1))); } else { s.onCompleted(); } } catch (SQLException e) { s.onError(e); } }, s -> { Connection c = Database.getConnection(); try { Statement stmt = c.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); return stmt.executeQuery("select * from orders"); } catch (SQLException e) { s.onError(e); return null; } }, rs -> rs.close() } // ignoring try/catch for slide brevity )).subscribeOn(Schedulers.io());
  26. return Observable.create(AbstractOnSubscribe.<Order, ResultSet> create(s -> { ResultSet rs = s.state();

    try { if (rs.next()) { s.onNext(new Order(rs.getInt(1))); } else { s.onCompleted(); } } catch (SQLException e) { s.onError(e); } }, s -> { Connection c = Database.getConnection(); try { Statement stmt = c.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); return stmt.executeQuery("select * from orders"); } catch (SQLException e) { s.onError(e); return null; } }, rs -> rs.close() } // ignoring try/catch for slide brevity )).subscribeOn(Schedulers.io());
  27. return Observable.create(AbstractOnSubscribe.<Order, ResultSet> create(s -> { ResultSet rs = s.state();

    try { if (rs.next()) { s.onNext(new Order(rs.getInt(1))); } else { s.onCompleted(); } } catch (SQLException e) { s.onError(e); } }, s -> { Connection c = Database.getConnection(); try { Statement stmt = c.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); return stmt.executeQuery("select * from orders"); } catch (SQLException e) { s.onError(e); return null; } }, rs -> rs.close() } // ignoring try/catch for slide brevity )).subscribeOn(Schedulers.io());
  28. return Observable.create(AbstractOnSubscribe.<Order, ResultSet> create(s -> { ResultSet rs = s.state();

    try { if (rs.next()) { s.onNext(new Order(rs.getInt(1))); } else { s.onCompleted(); } } catch (SQLException e) { s.onError(e); } }, s -> { Connection c = Database.getConnection(); try { Statement stmt = c.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); return stmt.executeQuery("select * from orders"); } catch (SQLException e) { s.onError(e); return null; } }, rs -> rs.close() } // ignoring try/catch for slide brevity )).subscribeOn(Schedulers.io());
  29. return Observable.create(AbstractOnSubscribe.<Order, ResultSet> create(s -> { ResultSet rs = s.state();

    try { if (rs.next()) { s.onNext(new Order(rs.getInt(1))); } else { s.onCompleted(); } } catch (SQLException e) { s.onError(e); } }, s -> { Connection c = Database.getConnection(); try { Statement stmt = c.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); return stmt.executeQuery("select * from orders"); } catch (SQLException e) { s.onError(e); return null; } }, rs -> rs.close() } // ignoring try/catch for slide brevity )).subscribeOn(Schedulers.io());
  30. return Observable.create(AbstractOnSubscribe.<Order, ResultSet> create(s -> { ResultSet rs = s.state();

    try { if (rs.next()) { s.onNext(new Order(rs.getInt(1))); } else { s.onCompleted(); } } catch (SQLException e) { s.onError(e); } }, s -> { Connection c = Database.getConnection(); try { Statement stmt = c.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); return stmt.executeQuery("select * from orders"); } catch (SQLException e) { s.onError(e); return null; } }, rs -> rs.close() } // ignoring try/catch for slide brevity )).subscribeOn(Schedulers.io());
  31. return Observable.create(AbstractOnSubscribe.<Order, ResultSet> create(s -> { ResultSet rs = s.state();

    try { if (rs.next()) { s.onNext(new Order(rs.getInt(1))); } else { s.onCompleted(); } } catch (SQLException e) { s.onError(e); } }, s -> { Connection c = Database.getConnection(); try { Statement stmt = c.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); return stmt.executeQuery("select * from orders"); } catch (SQLException e) { s.onError(e); return null; } }, rs -> rs.close() } // ignoring try/catch for slide brevity )).subscribeOn(Schedulers.io());
  32. return Observable.create(AbstractOnSubscribe.<Order, ResultSet> create(s -> { ResultSet rs = s.state();

    try { if (rs.next()) { s.onNext(new Order(rs.getInt(1))); } else { s.onCompleted(); } } catch (SQLException e) { s.onError(e); } }, s -> { Connection c = Database.getConnection(); try { Statement stmt = c.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); return stmt.executeQuery("select * from orders"); } catch (SQLException e) { s.onError(e); return null; } }, rs -> rs.close() } // ignoring try/catch for slide brevity )).subscribeOn(Schedulers.io());
  33. return Observable.create(AbstractOnSubscribe.<Order, ResultSet> create(s -> { ResultSet rs = s.state();

    try { if (rs.next()) { s.onNext(new Order(rs.getInt(1))); } else { s.onCompleted(); } } catch (SQLException e) { s.onError(e); } }, s -> { Connection c = Database.getConnection(); try { Statement stmt = c.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); return stmt.executeQuery("select * from orders"); } catch (SQLException e) { s.onError(e); return null; } }, rs -> rs.close() } // ignoring try/catch for slide brevity )).subscribeOn(Schedulers.io());
  34. return Observable.create(AbstractOnSubscribe.<Order, ResultSet> create(s -> { ResultSet rs = s.state();

    try { if (rs.next()) { s.onNext(new Order(rs.getInt(1))); } else { s.onCompleted(); } } catch (SQLException e) { s.onError(e); } }, s -> { Connection c = Database.getConnection(); try { Statement stmt = c.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); return stmt.executeQuery("select * from orders"); } catch (SQLException e) { s.onError(e); return null; } }, rs -> rs.close() } // ignoring try/catch for slide brevity )).subscribeOn(Schedulers.io());
  35. return Observable.create(AbstractOnSubscribe.<Order, ResultSet> create(s -> { ResultSet rs = s.state();

    try { if (rs.next()) { s.onNext(new Order(rs.getInt(1))); } else { s.onCompleted(); } } catch (SQLException e) { s.onError(e); } }, s -> { Connection c = Database.getConnection(); try { Statement stmt = c.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); return stmt.executeQuery("select * from orders"); } catch (SQLException e) { s.onError(e); return null; } }, rs -> rs.close() } // ignoring try/catch for slide brevity )).subscribeOn(Schedulers.io());
  36. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    try { // perform service composition asynchronously getShippingStatusForCustomerOrdersAsync(req.getParameter(“customerId")) .toBlocking() // block on servlet thread .forEach(status -> { try { // write output using blocking IO resp.getWriter().write(status.toString()); } catch (Exception e) { throw new RuntimeException("unable to write", e); } }); } catch (Exception e) { // toBlocking and forEach will convert async onError events into thrown exceptions resp.setStatus(500); // write and log error ... } }
  37. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    try { // perform service composition asynchronously getShippingStatusForCustomerOrdersAsync(req.getParameter("customerId")) .toBlocking() // block on servlet thread .forEach(status -> { try { // write output using blocking IO resp.getWriter().write(status.toString()); } catch (Exception e) { throw new RuntimeException("unable to write", e); } }); } catch (Exception e) { // toBlocking and forEach will convert async onError events into thrown exceptions resp.setStatus(500); // write and log error ... } }
  38. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    try { // perform service composition asynchronously getShippingStatusForCustomerOrdersAsync(req.getParameter("customerId")) .toBlocking() // block on servlet thread .forEach(status -> { try { // write output using blocking IO resp.getWriter().write(status.toString()); } catch (Exception e) { throw new RuntimeException("unable to write", e); } }); } catch (Exception e) { // toBlocking and forEach will convert async onError events into thrown exceptions resp.setStatus(500); // write and log error ... } }
  39. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    try { // perform service composition asynchronously getShippingStatusForCustomerOrdersAsync(req.getParameter("customerId")) .toBlocking() // block on servlet thread .forEach(status -> { try { // write output using blocking IO resp.getWriter().write(status.toString()); } catch (Exception e) { throw new RuntimeException("unable to write", e); } }); } catch (Exception e) { // toBlocking and forEach will convert async onError events into thrown exceptions resp.setStatus(500); // write and log error ... } }
  40. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    try { // perform service composition asynchronously getShippingStatusForCustomerOrdersAsync(req.getParameter("customerId")) .toBlocking() // block on servlet thread .forEach(status -> { try { // write output using blocking IO resp.getWriter().write(status.toString()); } catch (Exception e) { throw new RuntimeException("unable to write", e); } }); } catch (Exception e) { // toBlocking and forEach will convert async onError events into thrown exceptions resp.setStatus(500); // write and log error ... } }
  41. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    try { // perform service composition asynchronously getShippingStatusForCustomerOrdersAsync(req.getParameter("customerId")) .toBlocking() // block on servlet thread .forEach(status -> { try { // write output using blocking IO resp.getWriter().write(status.toString()); } catch (Exception e) { throw new RuntimeException("unable to write", e); } }); } catch (Exception e) { // toBlocking and forEach will convert async onError events into thrown exceptions resp.setStatus(500); // write and log error ... } }
  42. Iterable<String> is = Arrays.asList("a", "b", "c"); ArrayList<String> ts = new

    ArrayList<>(); for(String s : is) { ts.add(s + "-modified"); } // do stuff with ts ... Observable<String> os = Observable.just("a", "b", "c"); os.map(s -> s + "-modified"); // do stuff with os ...
  43. try { for (String s : is) { ts.add(s +

    "-modified"); } } catch (Exception e) { // do something with exception } os.map(s -> s + "-modified") .onErrorResumeNext(exception -> { // do something with exception return Observable.empty(); });
  44. TestSubscriber<String> ts = new TestSubscriber<>(); Observable.interval(200, TimeUnit.MILLISECONDS) .take(5) .map(i ->

    { return i + " value"; }).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList("0 value", "1 value", "2 value", "3 value", "4 value"));
  45. TestSubscriber<String> ts = new TestSubscriber<>(); Observable.interval(200, TimeUnit.MILLISECONDS) .take(5) .map(i ->

    { return i + " value"; }).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList("0 value", "1 value", "2 value", "3 value", "4 value"));
  46. TestSubscriber<String> ts = new TestSubscriber<>(); Observable.interval(200, TimeUnit.MILLISECONDS) .take(5) .map(i ->

    { return i + " value"; }).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList("0 value", "1 value", "2 value", "3 value", "4 value"));
  47. TestSubscriber<String> ts = new TestSubscriber<>(); Observable.interval(200, TimeUnit.MILLISECONDS) .take(5) .map(i ->

    { return i + " value"; }).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList("0 value", "1 value", "2 value", "3 value", "4 value"));
  48. TestSubscriber<String> ts = new TestSubscriber<>(); Observable.interval(200, TimeUnit.MILLISECONDS) .take(5) .map(i ->

    { return i + " value"; }).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList("0 value", "1 value", "2 value", "3 value", "4 value"));
  49. TestSubscriber<String> ts = new TestSubscriber<>(); Observable.interval(200, TimeUnit.MILLISECONDS) .take(5) .map(i ->

    { return i + " value"; }).subscribe(ts); ts.awaitTerminalEvent(); ts.assertNoErrors(); ts.assertReceivedOnNext(Arrays.asList("0 value", "1 value", "2 value", "3 value", "4 value"));
  50. TestScheduler test = Schedulers.test(); TestSubscriber<String> ts = new TestSubscriber<>(); Observable.interval(200,

    TimeUnit.MILLISECONDS, test) .map(i -> { return i + " value"; }).subscribe(ts); test.advanceTimeBy(200, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value")); test.advanceTimeTo(1000, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value", "1 value”, "2 value", "3 value", "4 value"));
  51. TestScheduler test = Schedulers.test(); TestSubscriber<String> ts = new TestSubscriber<>(); Observable.interval(200,

    TimeUnit.MILLISECONDS, test) .map(i -> { return i + " value"; }).subscribe(ts); test.advanceTimeBy(200, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value")); test.advanceTimeTo(1000, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value", "1 value”, "2 value", "3 value", "4 value"));
  52. TestScheduler test = Schedulers.test(); TestSubscriber<String> ts = new TestSubscriber<>(); Observable.interval(200,

    TimeUnit.MILLISECONDS, test) .map(i -> { return i + " value"; }).subscribe(ts); test.advanceTimeBy(200, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value")); test.advanceTimeTo(1000, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value", "1 value”, "2 value", "3 value", "4 value"));
  53. TestScheduler test = Schedulers.test(); TestSubscriber<String> ts = new TestSubscriber<>(); Observable.interval(200,

    TimeUnit.MILLISECONDS, test) .map(i -> { return i + " value"; }).subscribe(ts); test.advanceTimeBy(200, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value")); test.advanceTimeTo(1000, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value", "1 value”, "2 value", "3 value", "4 value"));
  54. TestScheduler test = Schedulers.test(); TestSubscriber<String> ts = new TestSubscriber<>(); Observable.interval(200,

    TimeUnit.MILLISECONDS, test) .map(i -> { return i + " value"; }).subscribe(ts); test.advanceTimeBy(200, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value")); test.advanceTimeTo(1000, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value", "1 value”, "2 value", "3 value", "4 value"));
  55. TestScheduler test = Schedulers.test(); TestSubscriber<String> ts = new TestSubscriber<>(); Observable.interval(200,

    TimeUnit.MILLISECONDS, test) .map(i -> { return i + " value"; }).subscribe(ts); test.advanceTimeBy(200, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value")); test.advanceTimeTo(1000, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value", "1 value”, "2 value", "3 value", "4 value"));
  56. TestScheduler test = Schedulers.test(); TestSubscriber<String> ts = new TestSubscriber<>(); Observable.interval(200,

    TimeUnit.MILLISECONDS, test) .map(i -> { return i + " value"; }).subscribe(ts); test.advanceTimeBy(200, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value")); test.advanceTimeTo(1000, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value", "1 value”, "2 value", "3 value", "4 value"));
  57. TestScheduler test = Schedulers.test(); TestSubscriber<String> ts = new TestSubscriber<>(); Observable.interval(200,

    TimeUnit.MILLISECONDS, test) .map(i -> { return i + " value"; }).subscribe(ts); test.advanceTimeBy(200, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value")); test.advanceTimeTo(1000, TimeUnit.MILLISECONDS); ts.assertReceivedOnNext(Arrays.asList("0 value", "1 value”, "2 value", "3 value", "4 value"));
  58. java.lang.RuntimeException: failure! at jdbc.Test.lambda$19(Test.java:63) at jdbc.Test$$Lambda$8/888452645.call(Unknown Source) at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:55) at

    rx.internal.operators.OperatorSubscribeOn$1$1$1.onNext(OperatorSubscribeOn.java:76) at rx.observables.AbstractOnSubscribe$SubscriptionState.accept(AbstractOnSubscribe.java:533) at rx.observables.AbstractOnSubscribe$SubscriptionProducer.doNext(AbstractOnSubscribe.java:367) at rx.observables.AbstractOnSubscribe$SubscriptionProducer.request(AbstractOnSubscribe.java:345) at rx.internal.operators.OperatorSubscribeOn$1$1$1$1.request(OperatorSubscribeOn.java:88) at rx.Subscriber.setProducer(Subscriber.java:177) at rx.Subscriber.setProducer(Subscriber.java:171) at rx.internal.operators.OperatorSubscribeOn$1$1$1.setProducer(OperatorSubscribeOn.java:81) at rx.observables.AbstractOnSubscribe.call(AbstractOnSubscribe.java:191) at rx.observables.AbstractOnSubscribe$LambdaOnSubscribe.call(AbstractOnSubscribe.java:274) at rx.Observable.unsafeSubscribe(Observable.java:7495) at rx.internal.operators.OperatorSubscribeOn$1$1.call(OperatorSubscribeOn.java:62) at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) Caused by: rx.exceptions.OnErrorThrowable$OnNextValue: OnError while emitting onNext value: jdbc.Product.class at rx.exceptions.OnErrorThrowable.addValueAsLastCause(OnErrorThrowable.java:98) at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:58) ... 20 more Ugly Stacktraces
  59. java.lang.RuntimeException: failure! at jdbc.Test.lambda$19(Test.java:63) at jdbc.Test$$Lambda$8/888452645.call(Unknown Source) at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:55) at

    rx.internal.operators.OperatorSubscribeOn$1$1$1.onNext(OperatorSubscribeOn.java:76) at rx.observables.AbstractOnSubscribe$SubscriptionState.accept(AbstractOnSubscribe.java:533) at rx.observables.AbstractOnSubscribe$SubscriptionProducer.doNext(AbstractOnSubscribe.java:367) at rx.observables.AbstractOnSubscribe$SubscriptionProducer.request(AbstractOnSubscribe.java:345) at rx.internal.operators.OperatorSubscribeOn$1$1$1$1.request(OperatorSubscribeOn.java:88) at rx.Subscriber.setProducer(Subscriber.java:177) at rx.Subscriber.setProducer(Subscriber.java:171) at rx.internal.operators.OperatorSubscribeOn$1$1$1.setProducer(OperatorSubscribeOn.java:81) at rx.observables.AbstractOnSubscribe.call(AbstractOnSubscribe.java:191) at rx.observables.AbstractOnSubscribe$LambdaOnSubscribe.call(AbstractOnSubscribe.java:274) at rx.Observable.unsafeSubscribe(Observable.java:7495) at rx.internal.operators.OperatorSubscribeOn$1$1.call(OperatorSubscribeOn.java:62) at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) Caused by: rx.exceptions.OnErrorThrowable$OnNextValue: OnError while emitting onNext value: jdbc.Product.class at rx.exceptions.OnErrorThrowable.addValueAsLastCause(OnErrorThrowable.java:98) at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:58) ... 20 more Ugly Stacktraces
  60. java.lang.RuntimeException: failure! at jdbc.Test.lambda$19(Test.java:63) at jdbc.Test$$Lambda$8/888452645.call(Unknown Source) at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:55) at

    rx.internal.operators.OperatorSubscribeOn$1$1$1.onNext(OperatorSubscribeOn.java:76) at rx.observables.AbstractOnSubscribe$SubscriptionState.accept(AbstractOnSubscribe.java:533) at rx.observables.AbstractOnSubscribe$SubscriptionProducer.doNext(AbstractOnSubscribe.java:367) at rx.observables.AbstractOnSubscribe$SubscriptionProducer.request(AbstractOnSubscribe.java:345) at rx.internal.operators.OperatorSubscribeOn$1$1$1$1.request(OperatorSubscribeOn.java:88) at rx.Subscriber.setProducer(Subscriber.java:177) at rx.Subscriber.setProducer(Subscriber.java:171) at rx.internal.operators.OperatorSubscribeOn$1$1$1.setProducer(OperatorSubscribeOn.java:81) at rx.observables.AbstractOnSubscribe.call(AbstractOnSubscribe.java:191) at rx.observables.AbstractOnSubscribe$LambdaOnSubscribe.call(AbstractOnSubscribe.java:274) at rx.Observable.unsafeSubscribe(Observable.java:7495) at rx.internal.operators.OperatorSubscribeOn$1$1.call(OperatorSubscribeOn.java:62) at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) Caused by: rx.exceptions.OnErrorThrowable$OnNextValue: OnError while emitting onNext value: jdbc.Product.class at rx.exceptions.OnErrorThrowable.addValueAsLastCause(OnErrorThrowable.java:98) at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:58) ... 20 more Missing Async Call Chain
  61. java.lang.RuntimeException: failure! at jdbc.Test.lambda$19(Test.java:63) at jdbc.Test$$Lambda$8/888452645.call(Unknown Source) at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:55) at

    rx.internal.operators.OperatorSubscribeOn$1$1$1.onNext(OperatorSubscribeOn.java:76) at rx.observables.AbstractOnSubscribe$SubscriptionState.accept(AbstractOnSubscribe.java:533) at rx.observables.AbstractOnSubscribe$SubscriptionProducer.doNext(AbstractOnSubscribe.java:367) at rx.observables.AbstractOnSubscribe$SubscriptionProducer.request(AbstractOnSubscribe.java:345) at rx.internal.operators.OperatorSubscribeOn$1$1$1$1.request(OperatorSubscribeOn.java:88) at rx.Subscriber.setProducer(Subscriber.java:177) at rx.Subscriber.setProducer(Subscriber.java:171) at rx.internal.operators.OperatorSubscribeOn$1$1$1.setProducer(OperatorSubscribeOn.java:81) at rx.observables.AbstractOnSubscribe.call(AbstractOnSubscribe.java:191) at rx.observables.AbstractOnSubscribe$LambdaOnSubscribe.call(AbstractOnSubscribe.java:274) at rx.Observable.unsafeSubscribe(Observable.java:7495) at rx.internal.operators.OperatorSubscribeOn$1$1.call(OperatorSubscribeOn.java:62) at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) Caused by: rx.exceptions.OnErrorThrowable$OnNextValue: OnError while emitting onNext value: jdbc.Product.class at rx.exceptions.OnErrorThrowable.addValueAsLastCause(OnErrorThrowable.java:98) at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:58) ... 20 more Stack Pollution
  62. java.lang.RuntimeException: failure! at jdbc.Test.lambda$19(Test.java:63) at jdbc.Test$$Lambda$8/888452645.call(Unknown Source) at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:55) at

    rx.internal.operators.OperatorSubscribeOn$1$1$1.onNext(OperatorSubscribeOn.java:76) at rx.observables.AbstractOnSubscribe$SubscriptionState.accept(AbstractOnSubscribe.java:533) at rx.observables.AbstractOnSubscribe$SubscriptionProducer.doNext(AbstractOnSubscribe.java:367) at rx.observables.AbstractOnSubscribe$SubscriptionProducer.request(AbstractOnSubscribe.java:345) at rx.internal.operators.OperatorSubscribeOn$1$1$1$1.request(OperatorSubscribeOn.java:88) at rx.Subscriber.setProducer(Subscriber.java:177) at rx.Subscriber.setProducer(Subscriber.java:171) at rx.internal.operators.OperatorSubscribeOn$1$1$1.setProducer(OperatorSubscribeOn.java:81) at rx.observables.AbstractOnSubscribe.call(AbstractOnSubscribe.java:191) at rx.observables.AbstractOnSubscribe$LambdaOnSubscribe.call(AbstractOnSubscribe.java:274) at rx.Observable.unsafeSubscribe(Observable.java:7495) at rx.internal.operators.OperatorSubscribeOn$1$1.call(OperatorSubscribeOn.java:62) at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) Caused by: rx.exceptions.OnErrorThrowable$OnNextValue: OnError while emitting onNext value: jdbc.Product.class at rx.exceptions.OnErrorThrowable.addValueAsLastCause(OnErrorThrowable.java:98) at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:58) ... 20 more RxJava Tries
  63. orders.flatMap(o -> { return getProductsAsync(o) .doOnNext(t -> System.out.println("product: " +

    t)) .flatMap(p -> { return getShippingStatusAsync(o, p) .map(s -> { return s; }); }); }) Debugging via Logging
  64. orders.flatMap(o -> { return getProductsAsync(o) .doOnNext(t -> System.out.println("product: " +

    t)) .flatMap(p -> { return getShippingStatusAsync(o, p) .map(s -> { return s; }); }); }) :-( Debugging via Logging
  65. public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request

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

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

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

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

         Observable<R>  r  =  ...  transform  t  ...          return  r;    }) flatMap
  70. public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request

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

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

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

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

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

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

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

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

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

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

                 ...  operate  on  values  from  both  a  &  b  ...              return  Arrays.asList(a,  b);          }) zip { ( , ) }
  81. public Observable<Void> handle(HttpServerRequest<ByteBuf> request, HttpServerResponse<ByteBuf> response) { // first request

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

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

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

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

         def  VideoBookmark  getBookmark(userId,  videoId);        def  VideoRating  getRating(userId,  videoId);        def  VideoMetadata  getMetadata(videoId);   } class  VideoService  {        def  Observable<VideoList>  getPersonalizedListOfMovies(userId);        def  Observable<VideoBookmark>  getBookmark(userId,  videoId);        def  Observable<VideoRating>  getRating(userId,  videoId);        def  Observable<VideoMetadata>  getMetadata(videoId);   } ... with an observable api: replaced the blocking api ...
  86. getOrdersAsync(1) .flatMap(o -> { return getProductsAsync(o) .flatMap(p -> { return

    getShippingStatusAsync(o, p); }); }); getOrdersAsync(1) .flatMap(o -> { return getProductsAsync(o) .map(p -> { return getShippingStatus(o, p); }); }); concurrent → synchronous →
  87. getOrdersAsync(1) .flatMap(o -> { return getProductsAsync(o) .flatMap(p -> { return

    getShippingStatusAsync(o, p); }); }); getOrdersAsync(1) .flatMap(o -> { return getProductsAsync(o) .map(p -> { return getShippingStatus(o, p); }); }); concurrent → synchronous →
  88. getOrdersAsync(1) .flatMap(o -> { return getProductsAsync(o) .flatMap(p -> { return

    getShippingStatusAsync(o, p); }); }); getOrdersAsync(1) .flatMap(o -> { return getProductsAsync(o) .map(p -> { return getShippingStatus(o, p); }); }); Async Facade only has Observable APIs
  89. Intel(R) Core(TM) i5-2400 CPU @ 3.10GHz: 4 cores, 1 thread

    per core OpenJDK 8 with frame pointer patch RxNetty 0.4.8 with Netty 4.0.25.Final vs Tomcat 7.0.45
  90. 0% 25% 50% 75% 100% 50 100 150 200 250

    300 350 400 450 500 550 600 650 700 750 800 850 900 950 100010501100 RxNetty Tomcat CPU Usage in Percent concurrent clients Testing drove both tests near 100% cpu usage
  91. 0ms 0.225ms 0.45ms 0.675ms 0.9ms 50 100 150 200 250

    300 350 400 450 500 550 600 650 700 750 800 850 900 950 100010501100 RxNetty Tomcat CPU Consumption per Request concurrent clients • Netty has lower CPU consumption per request • Netty keeps getting faster under load, Tomcat gets slower
  92. 0rps 1250rps 2500rps 3750rps 5000rps 50 100 150 200 250

    300 350 400 450 500 550 600 650 700 750 800 850 900 950 100010501100 RxNetty Tomcat Throughput concurrent clients • Netty achieves higher throughput • Mostly due to lower CPU consumption per request
  93. 0ms 70ms 140ms 210ms 280ms 50 100 150 200 250

    300 350 400 450 500 550 600 650 700 750 800 850 900 950 100010501100 RxNetty Tomcat Average Latency concurrent clients Theoretical best possible for test case is 154ms
  94. 0ms 750ms 1500ms 2250ms 3000ms 50 100 150 200 250

    300 350 400 450 500 550 600 650 700 750 800 850 900 950 100010501100 RxNetty Tomcat Max Latency concurrent clients Tomcat latency degradation is far more severe than Netty
  95. 0ipc 0.175ipc 0.35ipc 0.525ipc 0.7ipc 50 100 150 200 250

    300 350 400 450 500 550 600 650 700 750 800 850 900 950 100010501100 RxNetty Tomcat CPU Instructions per Cycle concurrent clients Netty increases instructions per cycle as load increases
  96. 0 400000 800000 1200000 1600000 50 100 150 200 250

    300 350 400 450 500 550 600 650 700 750 800 850 900 950 100010501100 RxNetty Tomcat Thread Migrations concurrent clients significant divergence
  97. 0 400000 800000 1200000 1600000 50 100 150 200 250

    300 350 400 450 500 550 600 650 700 750 800 850 900 950 100010501100 RxNetty Tomcat Thread Migrations concurrent clients Under light load both are similar
  98. 0 400000 800000 1200000 1600000 50 100 150 200 250

    300 350 400 450 500 550 600 650 700 750 800 850 900 950 100010501100 RxNetty Tomcat Thread Migrations concurrent clients Increased load causes less migrations, improving IPC and CPU usage per request with event loop architecture
  99. Why? • More efficient framework code • object allocation rates

    and GC costs • Event loop architecture • reduced thread migrations • cache warmth → memory locality → instructions per cycle → lower cpu consumption per request • Thread pool architecture • lock contention • thread migrations
  100. Why? • More efficient framework code • object allocation rates

    and GC costs • Event loop architecture • reduced thread migrations • cache warmth → memory locality → instructions per cycle → lower cpu consumption per request • Thread pool architecture • lock contention • thread migrations
  101. Why? • More efficient framework code • object allocation rates

    and GC costs • Event loop architecture • reduced thread migrations • cache warmth → memory locality → instructions per cycle → lower cpu consumption per request • Thread pool architecture • lock contention • thread migrations
  102. Why? • More efficient framework code • object allocation rates

    and GC costs • Event loop architecture • reduced thread migrations • cache warmth → memory locality → instructions per cycle → lower cpu consumption per request • Thread pool architecture • lock contention • thread migrations • Many more details …
  103. 0ms 750ms 1500ms 2250ms 3000ms 50 100 150 200 250

    300 350 400 450 500 550 600 650 700 750 800 850 900 950 100010501100 RxNetty Tomcat Simpler But Not Easier
  104. 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 Dragon Image: http://www.clipartpanda.com/clipart_images/you-can-use-this-clip-art-on-2194929 Greenfield Image: https://kvaes.wordpress.com/2013/06/05/lingo-explained-greenfield-vs-brownfield/ NY City Image: https://thesandincalifornia.files.wordpress.com/2012/05/newyorkcity-madness.jpg Futuristic City: http://i.imgur.com/rPTkr9D.jpg