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 full-size slide

  2. async + callbacks

    View full-size slide

  3. service composition
    +
    error handling
    developer productivity

    View full-size slide

  4. async + callbacks

    View full-size slide

  5. Future
    Future>

    View full-size slide

  6. Future
    Future>

    View full-size slide

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

    View full-size slide

  8. Future
    Future>
    Future>>

    View full-size slide

  9. Observable
    Future
    Future>
    Future>>

    View full-size slide

  10. Everything is a Stream

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  13. getOrders → getProducts → getShippingStatus

    View full-size slide

  14. IPC via Apache HTTP
    Memcached
    Cassandra
    Tomcat and Servlets

    View full-size slide

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

    View full-size slide

  16. Many things could not
    realistically change

    View full-size slide

  17. We could change our service layer

    View full-size slide

  18. Async Facade

    View full-size slide

  19. Async Facade
    Blocking Code
    Servlet Sync/Async Bridge

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  22. 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 full-size slide

  23. 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 full-size slide

  24. 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 full-size slide

  25. 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 full-size slide

  26. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  42. Wrapped with Threads
    using subscribeOn

    View full-size slide

  43. Wrapped with Threads
    JDBC?

    View full-size slide

  44. 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 full-size slide

  45. 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 full-size slide

  46. 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 full-size slide

  47. 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 full-size slide

  48. 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 full-size slide

  49. 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 full-size slide

  50. 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 full-size slide

  51. 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 full-size slide

  52. or with “reactive pull” backpressure …

    View full-size slide

  53. 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 full-size slide

  54. 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 full-size slide

  55. 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 full-size slide

  56. 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 full-size slide

  57. 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size slide

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

    View full-size slide

  68. Async Facade
    Blocking Code
    Servlet Sync/Async Bridge

    View full-size slide

  69. 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 full-size slide

  70. 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 full-size slide

  71. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size 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 full-size slide

  77. 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 full-size slide

  78. Observable.toBlocking

    View full-size slide

  79. Observable.toBlocking
    for bridging between async and sync

    View full-size slide

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

    View full-size slide

  81. First attempt took 3 tries

    View full-size slide

  82. What the *&!@#$^!!!

    View full-size slide

  83. Tech Worked …

    View full-size slide

  84. Needed to relearn idiomatic solutions

    View full-size slide

  85. 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 full-size slide

  86. 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 full-size slide

  87. Needed to invest in documentation

    View full-size slide

  88. Needed to allow for time and mistakes

    View full-size slide

  89. Also … Unit Testing & Debugging

    View full-size slide

  90. Async Unit Tests

    View full-size slide

  91. 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 full-size slide

  92. 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 full-size slide

  93. 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 full-size slide

  94. 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 full-size slide

  95. 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 full-size slide

  96. 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 full-size slide

  97. 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 full-size slide

  98. 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 full-size slide

  99. 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 full-size slide

  100. 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 full-size slide

  101. 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 full-size slide

  102. 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 full-size slide

  103. 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 full-size slide

  104. 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 full-size slide

  105. Async Debugging is Hard

    View full-size slide

  106. 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 full-size slide

  107. 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 full-size slide

  108. 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 full-size slide

  109. 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 full-size slide

  110. 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 full-size slide

  111. Stepping Severely Limited

    View full-size slide

  112. Stepping Severely Limited

    View full-size slide

  113. Stepping Severely Limited

    View full-size slide

  114. 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 full-size slide

  115. 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 full-size slide

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

    View full-size slide

  117. How about once all done?

    View full-size slide

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

    View full-size slide

  119. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  124. 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 full-size slide

  125. 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 full-size slide

  126. 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 full-size slide

  127. So what about going
    “fully reactive”?

    View full-size slide

  128. “cause it’s better”

    View full-size slide

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

    View full-size slide

  130. “it’s worse”

    View full-size slide

  131. “it doesn’t matter”

    View full-size slide

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

    View full-size slide

  133. C10k and beyond

    View full-size slide

  134. What to bet future on?

    View full-size slide

  135. What to bet future on?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  142. 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 full-size slide

  143. 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 full-size slide

  144. 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 full-size slide

  145. 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 full-size slide

  146. 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 full-size slide

  147. 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 full-size slide

  148. 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 full-size slide

  149. 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 full-size slide

  150. 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 full-size slide

  151. 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 full-size slide

  152. 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 full-size slide

  153. 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 full-size slide

  154. 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 full-size slide

  155. 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 full-size slide

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

    View full-size slide

  157. Simpler
    But Not Easier

    View full-size slide

  158. 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 full-size slide

  159. A fully async architecture
    does have benefits.

    View full-size slide

  160. Reactive programming
    can be incrementally applied.

    View full-size slide

  161. Mixed Codebases

    View full-size slide

  162. Concurrency via
    Async Service Composition

    View full-size slide

  163. Leverage RxJava Schedulers
    and Threads

    View full-size slide

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

    View full-size slide

  165. Concurrency is still non-trivial

    View full-size slide

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

    View full-size slide

  167. 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 full-size slide