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

Applying Reactive Programming with RxJava at GOTO Chicago 2015

Applying Reactive Programming with RxJava at GOTO Chicago 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 GOTO Chicago 2015 http://gotocon.com/chicago-2015/presentation/Applying%20Reactive%20Programming%20with%20Rx

Video: https://www.youtube.com/watch?v=8OcCSQS0tug

Ben Christensen

May 11, 2015
Tweet

More Decks by Ben Christensen

Other Decks in Programming

Transcript

  1. Ben Christensen
    Edge Engineering at Netflix
    @benjchristensen
    http://techblog.netflix.com/
    GOTO Chicago - May 2015
    Applying Reactive Programming with RxJava

    View Slide

  2. View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. async + callbacks

    View Slide

  8. service composition
    +
    error handling
    developer productivity

    View Slide

  9. async + callbacks

    View Slide

  10. Future
    Future>

    View Slide

  11. Future
    Future>

    View Slide

  12. → getListOfListOfMovies
    → forEachList
    → forEachMovie
    → getBookmark
    → getRating
    → getMetadata
    → getSimilars
    → etc

    View Slide

  13. Future
    Future>
    Future>>

    View Slide

  14. Observable
    Future
    Future>
    Future>>

    View Slide

  15. Everything is a Stream

    View Slide

  16. List getOrders(int customerId)
    List getProducts(Order order)
    ShippingStatus getShippingStatus(Order o, Product p)
    getOrders → getProducts → getShippingStatus

    View Slide

  17. List getOrders(int customerId)
    List getProducts(Order order)
    ShippingStatus getShippingStatus(Order o, Product p)
    getOrders → getProducts → getShippingStatus
    Blocking APIs

    View Slide

  18. getOrders → getProducts → getShippingStatus

    View Slide

  19. IPC via Apache HTTP
    Memcached
    Cassandra
    Tomcat and Servlets

    View Slide

  20. IPC via Apache HTTP
    Memcached
    Cassandra
    Tomcat and Servlets
    All Blocking

    View Slide

  21. Many things could not
    realistically change

    View Slide

  22. We could change our service layer

    View Slide

  23. Async Facade

    View Slide

  24. Async Facade
    Blocking Code
    Servlet Sync/Async Bridge

    View Slide

  25. Observable getOrders(int customerId)
    Observable getProducts(Order order)
    Observable getShippingStatus(Order o, Product p)
    getOrdersAsync → getProductsAsync → getShippingStatusAsync
    getOrders → getProducts → getShippingStatus

    View Slide

  26. Observable getOrders(int customerId)
    Observable getProducts(Order order)
    Observable getShippingStatus(Order o, Product p)
    getOrdersAsync → getProductsAsync → getShippingStatusAsync
    Observable APIs
    getOrders → getProducts → getShippingStatus

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  32. Observable getShippingStatusAsync(Order o, Product p)
    ShippingStatus getShippingStatus(Order o, Product p)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  39. List orders = getOrders(1);
    Observable orders = getOrdersAsync(1);

    View Slide

  40. List orders = getOrders(1);
    Observable orders = getOrdersAsync(1);

    View Slide

  41. List orders = getOrders(1);
    Observable orders = getOrdersAsync(1);

    View Slide

  42. return Observable.defer(() -> {
    return Observable.from(getOrders(customerId));
    }).subscribeOn(Schedulers.io());
    Observable orders = getOrdersAsync(1);

    View Slide

  43. return Observable.defer(() -> {
    return Observable.from(getOrders(customerId));
    }).subscribeOn(Schedulers.io());
    Observable orders = getOrdersAsync(1);

    View Slide

  44. Observable orders = getOrdersAsync(1);
    return Observable.defer(() -> {
    return Observable.from(getOrders(customerId));
    }).subscribeOn(Schedulers.io());

    View Slide

  45. return Observable.defer(() -> {
    return Observable.from(getOrders(customerId));
    }).subscribeOn(Schedulers.io());
    Observable orders = getOrdersAsync(1);

    View Slide

  46. return Observable.defer(() -> {
    return Observable.from(getOrders(customerId));
    }).subscribeOn(Schedulers.io());
    Observable orders = getOrdersAsync(1);

    View Slide

  47. Wrapped with Threads
    using subscribeOn

    View Slide

  48. Wrapped with Threads
    JDBC?

    View Slide

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

    View Slide

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

    View Slide

  51. Observable orders = getOrdersAsync(1);
    return Observable. 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());

    View Slide

  52. Observable orders = getOrdersAsync(1);
    return Observable. 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());

    View Slide

  53. return Observable. 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 orders = getOrdersAsync(1);

    View Slide

  54. return Observable. 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 orders = getOrdersAsync(1);

    View Slide

  55. return Observable. 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 orders = getOrdersAsync(1);

    View Slide

  56. return Observable. 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 orders = getOrdersAsync(1);

    View Slide

  57. or with “reactive pull” backpressure …

    View Slide

  58. return Observable.create(AbstractOnSubscribe. 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());

    View Slide

  59. return Observable.create(AbstractOnSubscribe. 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());

    View Slide

  60. return Observable.create(AbstractOnSubscribe. 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());

    View Slide

  61. return Observable.create(AbstractOnSubscribe. 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());

    View Slide

  62. return Observable.create(AbstractOnSubscribe. 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());

    View Slide

  63. return Observable.create(AbstractOnSubscribe. 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());

    View Slide

  64. return Observable.create(AbstractOnSubscribe. 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());

    View Slide

  65. return Observable.create(AbstractOnSubscribe. 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());

    View Slide

  66. return Observable.create(AbstractOnSubscribe. 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());

    View Slide

  67. return Observable.create(AbstractOnSubscribe. 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());

    View Slide

  68. return Observable.create(AbstractOnSubscribe. 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());

    View Slide

  69. return Observable.create(AbstractOnSubscribe. 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());

    View Slide

  70. return Observable.create(AbstractOnSubscribe. 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());

    View Slide

  71. return Observable.create(AbstractOnSubscribe. 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());

    View Slide

  72. Observable.defer
    Observable.from/just
    Observable.subscribeOn
    Observable.create

    View Slide

  73. Async Facade
    Blocking Code
    Servlet Sync/Async Bridge

    View Slide

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

    View Slide

  75. 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 ...
    }
    }

    View Slide

  76. 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 ...
    }
    }

    View Slide

  77. Observable getShippingStatusForCustomerOrdersAsync(String customerId) {
    return getOrdersAsync(Integer.parseInt(customerId))
    .limit(1)
    .flatMap(o -> {
    return getProductsAsync(o)
    .flatMap(p -> {
    return getShippingStatusAsync(o, p);
    });
    });
    }

    View Slide

  78. Observable getShippingStatusForCustomerOrdersAsync(String customerId) {
    return getOrdersAsync(Integer.parseInt(customerId))
    .limit(1)
    .flatMap(o -> {
    return getProductsAsync(o)
    .flatMap(p -> {
    return getShippingStatusAsync(o, p);
    });
    });
    }

    View Slide

  79. Observable getShippingStatusForCustomerOrdersAsync(String customerId) {
    return getOrdersAsync(Integer.parseInt(customerId))
    .limit(1)
    .flatMap(o -> {
    return getProductsAsync(o)
    .flatMap(p -> {
    return getShippingStatusAsync(o, p);
    });
    });
    }

    View Slide

  80. 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 ...
    }
    }

    View Slide

  81. 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 ...
    }
    }

    View Slide

  82. 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 ...
    }
    }

    View Slide

  83. Observable.toBlocking

    View Slide

  84. Observable.toBlocking
    for bridging between async and sync

    View Slide

  85. So how did it go adding reactive
    to an existing codebase?

    View Slide

  86. First attempt took 3 tries

    View Slide

  87. What the *&[email protected]#$^!!!

    View Slide

  88. Tech Worked …

    View Slide

  89. … but …

    View Slide

  90. Needed to relearn idiomatic solutions

    View Slide

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

    View Slide

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

    View Slide

  93. Needed to invest in documentation

    View Slide

  94. View Slide

  95. View Slide

  96. Needed to allow for time and mistakes

    View Slide

  97. Also … Unit Testing & Debugging

    View Slide

  98. Async Unit Tests

    View Slide

  99. TestSubscriber 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"));

    View Slide

  100. TestSubscriber 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"));

    View Slide

  101. TestSubscriber 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"));

    View Slide

  102. TestSubscriber 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"));

    View Slide

  103. TestSubscriber 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"));

    View Slide

  104. TestSubscriber 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"));

    View Slide

  105. TestScheduler test = Schedulers.test();
    TestSubscriber 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"));

    View Slide

  106. TestScheduler test = Schedulers.test();
    TestSubscriber 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"));

    View Slide

  107. TestScheduler test = Schedulers.test();
    TestSubscriber 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"));

    View Slide

  108. TestScheduler test = Schedulers.test();
    TestSubscriber 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"));

    View Slide

  109. TestScheduler test = Schedulers.test();
    TestSubscriber 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"));

    View Slide

  110. TestScheduler test = Schedulers.test();
    TestSubscriber 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"));

    View Slide

  111. TestScheduler test = Schedulers.test();
    TestSubscriber 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"));

    View Slide

  112. TestScheduler test = Schedulers.test();
    TestSubscriber 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"));

    View Slide

  113. Async Debugging is Hard

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  119. Stepping Severely Limited

    View Slide

  120. Stepping Severely Limited

    View Slide

  121. Stepping Severely Limited

    View Slide

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

    View Slide

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

    View Slide

  124. Async Debugging is Hard
    … a work in progress …

    View Slide

  125. How about once all done?

    View Slide

  126. Async Facade
    Blocking Code
    Servlet Sync/Async Bridge
    Observable APIs Worked Well

    View Slide

  127. View Slide

  128. 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);  
    }
    ... with an observable api:
    replaced the blocking api ...

    View Slide

  129. Async Facade
    Blocking Code
    Servlet Sync/Async Bridge
    Challenges Mixing Approaches

    View Slide

  130. Async Facade
    Blocking Code
    Servlet Sync/Async Bridge
    too many threads

    View Slide

  131. Async Facade
    Blocking Code
    Servlet Sync/Async Bridge
    hard to tune
    &
    shed load

    View Slide

  132. Async Facade
    Blocking Code
    Servlet Sync/Async Bridge
    easy to block
    async code

    View Slide

  133. 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 →
    sequential →

    View Slide

  134. 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 →
    sequential →

    View Slide

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

    View Slide

  136. So what about going
    “fully reactive”?

    View Slide

  137. View Slide

  138. “cause it’s better”

    View Slide

  139. “cause it’s better”
    :-/

    View Slide

  140. “it’s worse”

    View Slide

  141. “it doesn’t matter”

    View Slide

  142. green threads, native threads,
    fibers, actors, event loops,
    theoretically equivalent, OS
    kernels, etc, etc, etc …

    View Slide

  143. C10k and beyond

    View Slide

  144. What to bet future on?

    View Slide

  145. What to bet future on?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  151. WSPerfLab: https://github.com/Netflix-Skunkworks/WSPerfLab
    154ms

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  165. 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 …

    View Slide

  166. With current JVM and Linux,
    event loops have efficiency benefits.

    View Slide

  167. Simpler
    But Not Easier

    View Slide

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

    View Slide

  169. A fully async architecture
    does have benefits.

    View Slide

  170. Reactive programming
    can be incrementally applied.

    View Slide

  171. Mixed Codebases

    View Slide

  172. Concurrency via
    Async Service Composition

    View Slide

  173. Leverage RxJava Schedulers
    and Threads

    View Slide

  174. Leverage RxJava Schedulers
    and Threads
    (or use Hystrix)

    View Slide

  175. Concurrency is still non-trivial

    View Slide

  176. Rx doesn’t trivialize it
    Concurrency is still non-trivial

    View Slide

  177. View Slide

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

    View Slide