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

Presentation: Reactive: Do. Or do not. There is no try.

Presentation: Reactive: Do. Or do not. There is no try.

Sergei Egorov

February 04, 2020
Tweet

More Decks by Sergei Egorov

Other Decks in Programming

Transcript

  1. Reactive DOs and DONTs
    Sergei Egorov, Pivotal
    @bsideup

    View Slide

  2. • Staff Engineer at Pivotal, Project Reactor
    team

    • Oracle Groundbreakers Ambassador

    • Berlin Spring User Group co-organizer

    • Testcontainers co-maintainer
    About me
    @bsideup

    View Slide

  3. My first experience with
    reactive programming was like…

    View Slide

  4. @bsideup

    View Slide

  5. @bsideup

    View Slide

  6. 1 week later…

    View Slide

  7. I agree with Heinrich Apfelmus that the essence
    of functional reactive programming is to specify
    the dynamic behavior of a value completely at
    the time of declaration.
    “Reactive Sir”
    @bsideup

    View Slide

  8. 1 week of production later…

    View Slide

  9. @bsideup

    View Slide

  10. So I went to the Gods…

    View Slide

  11. Stephane @smaldini Maldini,

    Project Reactor’s team lead @bsideup
    Ex-

    View Slide

  12. He was my manager, lol
    Stephane @smaldini Maldini,

    Project Reactor’s team lead @bsideup
    Ex-

    View Slide

  13. Stephane @smaldini Maldini,

    Project Reactor’s team lead @bsideup
    Ex-

    View Slide

  14. The Reactive Lord has given
    unto you these fifteen…

    View Slide

  15. oy…

    View Slide

  16. Ten! Ten commandments,
    for all to obey.

    View Slide

  17. Learn functional programming
    DO
    @bsideup

    View Slide

  18. @bsideup
    Haskell?

    View Slide

  19. @bsideup
    Haskell?

    View Slide

  20. @bsideup
    https://twitter.com/mariofusco/status/571999216039542784

    View Slide

  21. Lazy evaluation

    View Slide

  22. Mono.fromRunnable()
    @bsideup

    View Slide

  23. Mono.fromRunnable()
    My code
    @bsideup

    View Slide

  24. Mono.fromRunnable()
    My code My code
    @bsideup

    View Slide

  25. Mono.fromRunnable()
    My code My code My code
    Work!
    @bsideup

    View Slide

  26. Mono.fromRunnable().subscribe()
    @bsideup

    View Slide

  27. An ideal app
    subscribes only once
    @bsideup

    View Slide

  28. Immutability

    View Slide

  29. Quiz
    static void addLogging(Flux flux) {
    flux.doOnNext(it -> println("Received " + it))
    .doOnError(e -> e.printStackTrace());
    }
    // ...
    Flux items = getFlux();
    addLogging(items);
    items.subscribe();
    @bsideup

    View Slide

  30. Quiz
    static void addLogging(Flux flux) {
    flux.doOnNext(it -> println("Received " + it))
    .doOnError(e -> e.printStackTrace());
    }
    // ...
    Flux items = getFlux();
    addLogging(items);
    items.subscribe();
    1. Will print both items and errors
    @bsideup

    View Slide

  31. Quiz
    static void addLogging(Flux flux) {
    flux.doOnNext(it -> println("Received " + it))
    .doOnError(e -> e.printStackTrace());
    }
    // ...
    Flux items = getFlux();
    addLogging(items);
    items.subscribe();
    1. Will print both items and errors
    2. Will only print items, not errors
    @bsideup

    View Slide

  32. Quiz
    static void addLogging(Flux flux) {
    flux.doOnNext(it -> println("Received " + it))
    .doOnError(e -> e.printStackTrace());
    }
    // ...
    Flux items = getFlux();
    addLogging(items);
    items.subscribe();
    1. Will print both items and errors
    2. Will only print items, not errors
    3. Will not print anything
    @bsideup

    View Slide

  33. Quiz
    static void addLogging(Flux flux) {
    flux.doOnNext(it -> println("Received " + it))
    .doOnError(e -> e.printStackTrace());
    }
    // ...
    Flux items = getFlux();
    addLogging(items);
    items.subscribe();
    1. Will print both items and errors

    2. Will only print items, not errors

    3. Will not print anything
    @bsideup

    View Slide

  34. public Mono map(Function super T, ? extends R> mapper) {
    return new MonoMap<>(this, mapper);
    }
    Immutability of reactive operators
    @bsideup
    Mono.just("Hello")
    .map(it -> it + " World!")
    .map(String::length)

    View Slide

  35. public Mono map(Function super T, ? extends R> mapper) {
    return new MonoMap<>(this, mapper);
    }
    Immutability of reactive operators
    @bsideup
    Mono.just("Hello")
    .map(it -> it + " World!")
    .map(String::length)

    View Slide

  36. static Flux addLogging(Flux flux) {
    return flux
    .doOnNext(it -> println("Received " + it))
    .doOnError(e -> e.printStackTrace());
    }
    // ...
    getFlux()
    .transform(flux -> addLogging(flux))
    .subscribe();
    @bsideup
    Always return…

    View Slide

  37. static Flux addLogging(Flux flux) {
    return flux
    .doOnNext(it -> println("Received " + it))
    .doOnError(e -> e.printStackTrace());
    }
    // ...
    getFlux()
    .transform(flux -> addLogging(flux))
    .subscribe();
    … and use the returned value!
    @bsideup

    View Slide

  38. static Flux addLogging(Flux flux) {
    return flux
    .doOnNext(it -> println("Received " + it))
    .doOnError(e -> e.printStackTrace());
    }
    // ...
    getFlux()
    .transform(flux -> addLogging(flux))
    .subscribe();
    … and use the returned value!
    @bsideup

    View Slide

  39. Higher-order functions

    View Slide

  40. Mono userIdMono = getUserId();
    userIdMono.subscribe(userId -> {
    Mono userMono = getUser(userId);
    userMono.subscribe(user -> {
    if (isUserValid(user)) {
    println("User: " + user);
    }
    });
    });
    // vs
    getUserId()
    .flatMap(userId -> getUser(userId))
    .filter(user -> isUserValid(user))
    .subscribe(user -> System.out.println("User: " + user));
    Higher-order functions
    @bsideup

    View Slide

  41. Mono userIdMono = getUserId();
    userIdMono.subscribe(userId -> {
    Mono userMono = getUser(userId);
    userMono.subscribe(user -> {
    if (isUserValid(user)) {
    println("User: " + user);
    }
    });
    });
    // vs
    getUserId()
    .flatMap(userId -> getUser(userId))
    .filter(user -> isUserValid(user))
    .subscribe(user -> System.out.println("User: " + user));
    Higher-order functions
    @bsideup

    View Slide

  42. Mono userIdMono = getUserId();
    userIdMono.subscribe(userId -> {
    Mono userMono = getUser(userId);
    userMono.subscribe(user -> {
    if (isUserValid(user)) {
    println("User: " + user);
    }
    });
    });
    // vs
    getUserId()
    .flatMap(userId -> getUser(userId))
    .filter(user -> isUserValid(user))
    .subscribe(user -> System.out.println("User: " + user));
    Higher-order functions
    @bsideup

    View Slide

  43. Mono userIdMono = getUserId();
    userIdMono.subscribe(userId -> {
    Mono userMono = getUser(userId);
    userMono.subscribe(user -> {
    if (isUserValid(user)) {
    println("User: " + user);
    }
    });
    });
    // vs
    getUserId()
    .flatMap(userId -> getUser(userId))
    .filter(user -> isUserValid(user))
    .subscribe(user -> System.out.println("User: " + user));
    Higher-order functions
    @bsideup

    View Slide

  44. Mono userIdMono = getUserId();
    userIdMono.subscribe(userId -> {
    Mono userMono = getUser(userId);
    userMono.subscribe(user -> {
    if (isUserValid(user)) {
    println("User: " + user);
    }
    });
    });
    // vs
    getUserId()
    .flatMap(userId -> getUser(userId))
    .filter(user -> isUserValid(user))
    .subscribe(user -> println("User: " + user));
    Higher-order functions
    @bsideup

    View Slide

  45. Mono userIdMono = getUserId();
    userIdMono.subscribe(userId -> {
    Mono userMono = getUser(userId);
    userMono.subscribe(user -> {
    if (isUserValid(user)) {
    println("User: " + user);
    }
    });
    });
    // vs
    getUserId()
    .flatMap(userId -> getUser(userId))
    .filter(user -> isUserValid(user))
    .subscribe(user -> println("User: " + user));
    Higher-order functions
    Less scopes
    @bsideup

    View Slide

  46. Mono userIdMono = getUserId();
    userIdMono.subscribe(userId -> {
    Mono userMono = getUser(userId);
    userMono.subscribe(user -> {
    if (isUserValid(user)) {
    println("User: " + user);
    }
    });
    });
    // vs
    getUserId()
    .flatMap(userId -> getUser(userId))
    .filter(user -> isUserValid(user))
    .subscribe(user -> println("User: " + user));
    Higher-order functions
    Composition
    @bsideup

    View Slide

  47. Side effects are not welcomed

    View Slide

  48. https:/
    /xkcd.com/1312/
    @bsideup

    View Slide

  49. Avoid side effects
    Flux users = getUsers();
    return users.doOnNext(user -> {
    storeUser(user);
    });
    @bsideup

    View Slide

  50. Avoid side effects
    Flux users = getUsers();
    return users.doOnNext(user -> {
    storeUser(user);
    });
    Side effect!
    void storeUser(User user) {
    //
    }
    @bsideup

    View Slide

  51. Avoid side effects
    Flux users = getUsers();
    return users.concatMap(user -> storeUser(user));
    Mono storeUser(User user) {
    //
    }
    @bsideup

    View Slide

  52. “things you do in your (async) callbacks
    should never take (significant) time”
    Stephane Maldini, 2019
    @bsideup

    View Slide

  53. Errors are side effects too
    return fetchUsers().map(json -> {
    try {
    return decodeJSON(json);
    } catch (IOException e) {
    throw new RuntimeException(e);
    }
    });
    @bsideup

    View Slide

  54. Errors are side effects too
    return fetchUsers().map(json -> {
    try {
    return decodeJSON(json);
    } catch (IOException e) {
    throw new RuntimeException(e);
    }
    });
    @bsideup

    View Slide

  55. Errors are side effects too
    return fetchUsers().map(json -> {
    try {
    return decodeJSON(json);
    } catch (IOException e) {
    throw new RuntimeException(e);
    }
    });
    return fetchUsers().handle((json, sink) -> {
    try {
    sink.next(decodeJSON(json));
    } catch (IOException e) {
    sink.error(e);
    }
    });
    @bsideup

    View Slide

  56. Errors are side effects too
    return fetchUsers().map(json -> {
    try {
    return decodeJSON(json);
    } catch (IOException e) {
    throw new RuntimeException(e);
    }
    });
    return fetchUsers().handle((json, sink) -> {
    try {
    sink.next(decodeJSON(json));
    } catch (IOException e) {
    sink.error(e);
    }
    });
    @bsideup

    View Slide

  57. Errors are side effects too
    return fetchUsers().map(json -> {
    try {
    return decodeJSON(json);
    } catch (IOException e) {
    throw new RuntimeException(e);
    }
    });
    return fetchUsers().handle((json, sink) -> {
    try {
    sink.next(decodeJSON(json));
    } catch (IOException e) {
    sink.error(e);
    }
    }); No need to wrap with RuntimeException
    @bsideup

    View Slide

  58. use it for heavy computations
    DON’T
    @bsideup

    View Slide

  59. ~5x
    Overhead

    View Slide

  60. .flatMap
    .concatMap(this::calculateHash)
    .delayUntil(this::squareRoot)

    View Slide

  61. .flatMap
    .concatMap
    .delayUntil(this::squareRoot)

    View Slide

  62. .flatMap
    .concatMap
    .publishOn

    View Slide

  63. .flatMap
    .concatMap
    .publishOn
    Queue

    Queue

    Queue

    View Slide

  64. View Slide

  65. View Slide

  66. check various operators
    DO
    @bsideup

    View Slide

  67. “Reactive starter pack”
    • map

    • filter

    • flatMap

    • take

    • subscribeOn/publishOn
    @bsideup

    View Slide

  68. @bsideup

    View Slide

  69. @bsideup

    View Slide

  70. “flatMaps”
    • flatMap - transform every item concurrently into a sub-stream, and join
    the current and the sub-stream.
    @bsideup

    View Slide

  71. “flatMaps”
    • flatMap - transform every item concurrently into a sub-stream, and join
    the current and the sub-stream.
    • concatMap - same as flatMap, but one-by-one
    @bsideup

    View Slide

  72. “flatMaps”
    • flatMap - transform every item concurrently into a sub-stream, and join
    the current and the sub-stream.
    • concatMap - same as flatMap, but one-by-one
    • switchMap - same as concatMap, but will cancel the previous sub-
    stream when a new item arrives
    @bsideup

    View Slide

  73. “flatMaps”
    • flatMap - transform every item concurrently into a sub-stream, and join
    the current and the sub-stream.
    • concatMap - same as flatMap, but one-by-one
    • switchMap - same as concatMap, but will cancel the previous sub-
    stream when a new item arrives
    • flatMapSequential - same as flatMap, but preserves the order of sub-
    stream items according to the original stream’s order
    @bsideup

    View Slide

  74. Which operator to use?

    View Slide

  75. View Slide

  76. block non-blocking threads
    DON’T
    @bsideup

    View Slide

  77. block non-blocking threads
    DON’T
    @bsideup

    View Slide

  78. How reactive frameworks
    schedule tasks
    @bsideup

    View Slide

  79. Default thread pools
    Schedulers.parallel() - N threads, where N matches the CPUs count.

    Schedulers.single() - 1 thread handling all submitted tasks

    Schedulers.boundedElastic() - dynamic, thread caching capped pool
    @bsideup

    View Slide

  80. Mono.delay(ofSeconds(1))
    @bsideup

    View Slide

  81. Mono.delay(ofSeconds(1))
    Mono.delay(ofSeconds(1), Schedulers.parallel())
    @bsideup

    View Slide

  82. Mono.delay(ofSeconds(1))
    Mono.delay(ofSeconds(1), Schedulers.parallel())
    Every non-instant operation 

    runs on the parallel scheduler

    by default!
    @bsideup

    View Slide

  83. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Non-blocking execution
    @bsideup

    View Slide

  84. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Non-blocking execution
    @bsideup

    View Slide

  85. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Non-blocking execution
    @bsideup

    View Slide

  86. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Non-blocking execution
    @bsideup

    View Slide

  87. parallel-1
    parallel-2
    parallel-3
    parallel-4

    Non-blocking execution
    @bsideup

    View Slide

  88. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Blocking execution
    @bsideup

    View Slide

  89. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Blocking execution
    @bsideup

    View Slide

  90. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Blocking execution
    @bsideup

    View Slide

  91. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Blocking execution
    @bsideup

    View Slide

  92. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Blocking execution
    @bsideup

    View Slide

  93. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Blocking execution
    @bsideup

    View Slide

  94. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Blocking execution
    @bsideup

    View Slide

  95. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Blocking execution
    @bsideup

    View Slide

  96. parallel-1
    parallel-2
    parallel-3
    parallel-4

    Blocking execution
    @bsideup

    View Slide

  97. parallel-1
    parallel-2
    parallel-3
    parallel-4

    Blocking execution
    ⚠ no more tasks scheduled 

    until this task returns
    @bsideup

    View Slide

  98. How to fix?

    View Slide

  99. Project Loom

    View Slide

  100. @bsideup

    View Slide

  101. @bsideup

    View Slide

  102. Use non-blocking APIs, or…

    View Slide

  103. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Custom thread pool!
    custom-1
    custom-2
    @bsideup

    View Slide

  104. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Custom thread pool!
    custom-1
    custom-2
    @bsideup

    View Slide

  105. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Custom thread pool!
    custom-1
    custom-2
    @bsideup

    View Slide

  106. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Custom thread pool!
    custom-1
    custom-2
    @bsideup

    View Slide

  107. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Custom thread pool!
    custom-1
    custom-2
    @bsideup

    View Slide

  108. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Custom thread pool!
    custom-1
    custom-2
    @bsideup

    View Slide

  109. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Custom thread pool!
    custom-1
    custom-2
    @bsideup

    View Slide

  110. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Custom thread pool!
    custom-1
    custom-2
    @bsideup

    View Slide

  111. parallel-1
    parallel-2
    parallel-3
    parallel-4
    Custom thread pool!
    custom-1
    custom-2

    @bsideup

    View Slide

  112. Demo
    http://eskipaper.com/homer-jay-simpson-cartoon.html

    View Slide

  113. Demo outcomes
    • Blocking calls are bad, m’kay?

    • They may sneak into your production system
    • Use https://github.com/reactor/BlockHound to detect them

    • Supports multiple frameworks (Reactor, RxJava, etc)

    • … and maybe even Kotlin: 

    https://github.com/Kotlin/kotlinx.coroutines/issues/1031

    • Use a dedicated pool for the necessary blocking calls, or schedule them on
    the Schedulers.boundedElastic() built-in pool if they happen rarely
    @bsideup

    View Slide

  114. “I use async APIs and I am safe!”

    View Slide

  115. Yeah… sure.

    View Slide

  116. Or…

    View Slide

  117. Are you sure?

    View Slide

  118. KafkaProducer#send(ProducerRecord,Callback)
    @bsideup

    View Slide

  119. KafkaProducer#send(ProducerRecord,Callback)
    “Asynchronously send a record to a topic and invoke the provided
    callback when the send has been acknowledged.” - Javadoc
    @bsideup

    View Slide

  120. KafkaProducer#send(ProducerRecord,Callback)
    “Asynchronously send a record to a topic and invoke the provided
    callback when the send has been acknowledged.” - Javadoc
    java.lang.Error: Blocking call! java.lang.Object#wait

    at reactor.BlockHound$Builder.lambda$new$0(BlockHound.java:154)

    at reactor.BlockHound$Builder.lambda$install$8(BlockHound.java:254)

    at reactor.BlockHoundRuntime.checkBlocking(BlockHoundRuntime.java:43)

    at java.lang.Object.wait(Object.java)

    at org.apache.kafka.clients.Metadata.awaitUpdate(Metadata.java:181)

    at org.apache.kafka.clients.producer.KafkaProducer.waitOnMetadata(KafkaProducer.java:938)

    at org.apache.kafka.clients.producer.KafkaProducer.doSend(KafkaProducer.java:823)

    at org.apache.kafka.clients.producer.KafkaProducer.send(KafkaProducer.java:803)
    @bsideup

    View Slide

  121. KafkaProducer#send(ProducerRecord,Callback)
    “Asynchronously send a record to a topic and invoke the provided
    callback when the send has been acknowledged.” - Javadoc
    java.lang.Error: Blocking call! java.lang.Object#wait

    at reactor.BlockHound$Builder.lambda$new$0(BlockHound.java:154)

    at reactor.BlockHound$Builder.lambda$install$8(BlockHound.java:254)

    at reactor.BlockHoundRuntime.checkBlocking(BlockHoundRuntime.java:43)

    at java.lang.Object.wait(Object.java)

    at org.apache.kafka.clients.Metadata.awaitUpdate(Metadata.java:181)

    at org.apache.kafka.clients.producer.KafkaProducer.waitOnMetadata(KafkaProducer.java:938)

    at org.apache.kafka.clients.producer.KafkaProducer.doSend(KafkaProducer.java:823)

    at org.apache.kafka.clients.producer.KafkaProducer.send(KafkaProducer.java:803)
    https://issues.apache.org/jira/browse/KAFKA-3539
    @bsideup

    View Slide

  122. long remainingWaitMs = maxWaitMs;
    long elapsed;
    // Issue metadata requests until we have metadata for the topic or maxWaitTimeMs is exceeded.
    // In case we already have cached metadata for the topic, but the requested partition is greater
    // than expected, issue an update request only once. This is necessary in case the metadata
    // is stale and the number of partitions for this topic has increased in the meantime.
    do {
    log.trace("Requesting metadata update for topic {}.", topic);
    metadata.add(topic);
    int version = metadata.requestUpdate();
    sender.wakeup();
    try {
    metadata.awaitUpdate(version, remainingWaitMs);
    } catch (TimeoutException ex) {
    // Rethrow with original maxWaitMs to prevent logging exception with remainingWaitMs
    throw new TimeoutException("Failed to update metadata after " + maxWaitMs + " ms.");
    }
    cluster = metadata.fetch();
    elapsed = time.milliseconds() - begin;
    if (elapsed >= maxWaitMs)
    throw new TimeoutException("Failed to update metadata after " + maxWaitMs + " ms.");
    if (cluster.unauthorizedTopics().contains(topic))
    throw new TopicAuthorizationException(topic);
    remainingWaitMs = maxWaitMs - elapsed;
    partitionsCount = cluster.partitionCountForTopic(topic);
    } while (partitionsCount == null);
    waitOnMetadata(record.topic(), record.partition(), maxBlockTimeMs);
    Default is “60 seconds”
    @bsideup

    View Slide

  123. @bsideup

    View Slide

  124. Start gradually
    DO
    @bsideup

    View Slide

  125. class UsersController {
    UsersService usersService;
    @GetMapping("/users")
    List getUsers() {
    return usersService.getUsers().stream()
    .map(UserDTO::new)
    .collect(Collectors.toList());
    }
    }
    class UsersService {
    UsersRepository usersRepository;
    List getUsers() {
    return usersRepository.findAll();
    }
    }
    interface UsersRepository {
    List findAll();
    }
    @bsideup

    View Slide

  126. class UsersController {
    UsersService usersService;
    @GetMapping("/users")
    List getUsers() {
    return usersService.getUsers().stream()
    .map(UserDTO::new)
    .collect(Collectors.toList());
    }
    }
    class UsersService {
    UsersRepository usersRepository;
    List getUsers() {
    return usersRepository.findAll();
    }
    }
    interface UsersRepository {
    List findAll();
    }
    @bsideup

    View Slide

  127. class UsersController {
    UsersService usersService;
    @GetMapping("/users")
    List getUsers() {
    return usersService.getUsers().stream()
    .map(UserDTO::new)
    .collect(Collectors.toList());
    }
    }
    class UsersService {
    UsersRepository usersRepository;
    List getUsers() {
    return usersRepository.findAll();
    }
    }
    interface UsersRepository {
    Flux findAll();
    }
    @bsideup

    View Slide

  128. class UsersController {
    UsersService usersService;
    @GetMapping("/users")
    List getUsers() {
    return usersService.getUsers().stream()
    .map(UserDTO::new)
    .collect(Collectors.toList());
    }
    }
    class UsersService {
    UsersRepository usersRepository;
    List getUsers() {
    return usersRepository.findAll();
    }
    }
    interface UsersRepository {
    Flux findAll();
    }
    @bsideup

    View Slide

  129. class UsersController {
    UsersService usersService;
    @GetMapping("/users")
    List getUsers() {
    return usersService.getUsers().stream()
    .map(UserDTO::new)
    .collect(Collectors.toList());
    }
    }
    class UsersService {
    UsersRepository usersRepository;
    List getUsers() {
    return usersRepository.findAll()
    .collectList()
    .block();
    }
    }
    interface UsersRepository {
    Flux findAll();
    }
    @bsideup

    View Slide

  130. class UsersController {
    UsersService usersService;
    @GetMapping("/users")
    List getUsers() {
    return usersService.getUsers().stream()
    .map(UserDTO::new)
    .collect(Collectors.toList());
    }
    }
    class UsersService {
    UsersRepository usersRepository;
    List getUsers() {
    return usersRepository.findAll()
    .collectList()
    .timeout(Mono.delay(ofSeconds(10)))
    .retry(5)
    .block();
    }
    }
    interface UsersRepository {
    Flux findAll();
    }
    @bsideup

    View Slide

  131. class UsersController {
    UsersService usersService;
    @GetMapping("/users")
    List getUsers() {
    return usersService.getUsers().stream()
    .map(UserDTO::new)
    .collect(Collectors.toList());
    }
    }
    class UsersService {
    UsersRepository usersRepository;
    Flux getUsers() {
    return usersRepository.findAll()
    .timeout(Mono.delay(ofSeconds(10)))
    .retry(5);
    }
    }
    interface UsersRepository {
    Flux findAll();
    }
    @bsideup

    View Slide

  132. class UsersController {
    UsersService usersService;
    @GetMapping("/users")
    List getUsers() {
    return usersService.getUsers().stream()
    .map(UserDTO::new)
    .collect(Collectors.toList());
    }
    }
    class UsersService {
    UsersRepository usersRepository;
    Flux getUsers() {
    return usersRepository.findAll()
    .timeout(Mono.delay(ofSeconds(10)))
    .retry(5);
    }
    }
    interface UsersRepository {
    Flux findAll();
    }
    @bsideup

    View Slide

  133. class UsersController {
    UsersService usersService;
    @GetMapping("/users")
    Flux getUsers() {
    return usersService.getUsers()
    .map(UserDTO::new);
    }
    }
    class UsersService {
    UsersRepository usersRepository;
    Flux getUsers() {
    return usersRepository.findAll()
    .timeout(Mono.delay(ofSeconds(10)))
    .retry(5);
    }
    }
    interface UsersRepository {
    Flux findAll();
    }
    @bsideup

    View Slide

  134. Before:
    class UsersController {
    UsersService usersService;
    @GetMapping("/users")
    List getUsers() {
    return usersService.getUsers()
    .stream()
    .map(UserDTO::new)
    .collect(toList());
    }
    }
    class UsersService {
    UsersRepository usersRepository;
    List getUsers() {
    return usersRepository.findAll();
    }
    }
    interface UsersRepository {
    List findAll();
    }
    class UsersController {
    UsersService usersService;
    @GetMapping("/users")
    Flux getUsers() {
    return usersService.getUsers()
    .map(UserDTO::new);
    }
    }
    class UsersService {
    UsersRepository usersRepository;
    Flux getUsers() {
    return usersRepository.findAll()
    .timeout(Mono.delay(ofSeconds(10)))
    .retry(5);
    }
    }
    interface UsersRepository {
    Flux findAll();
    }
    After:

    View Slide

  135. use ThreadLocals
    DON’T
    @bsideup

    View Slide

  136. Synchronous programming
    • 1 request == 1 thread

    • Everything is blocking, the execution continues on the same thread

    • ThreadLocals work just fine
    @bsideup

    View Slide

  137. Asynchronous programming
    • 1 request will most probably be handled by multiple threads

    • Everything is non-blocking, an accepted request may be returned to the
    client from another thread

    • ThreadLocals must be propagated from one thread to another
    @bsideup

    View Slide

  138. Solution?

    View Slide

  139. Context!

    View Slide

  140. Mono
    .deferWithContext(ctx -> {
    return this.getUser(ctx.get("userId"));
    })
    // Later in the framework (e.g. Spring Security)
    .subscriberContext(Context.of("userId", "bsideup"))
    .subscribe();
    @bsideup

    View Slide

  141. Mono
    .deferWithContext(ctx -> {
    return this.getUser(ctx.get("userId"));
    })
    // Later in the framework (e.g. Spring Security)
    .subscriberContext(Context.of("userId", "bsideup"))
    .subscribe();
    @bsideup

    View Slide

  142. Mono
    .deferWithContext(ctx -> {
    return this.getUser(ctx.get("userId"));
    })
    // Later in the framework (e.g. Spring Security)
    .subscriberContext(Context.of("userId", "bsideup"))
    .subscribe();
    @bsideup

    View Slide

  143. Legacy?

    View Slide

  144. Schedulers.onScheduleHook(fn)
    @bsideup

    View Slide

  145. Schedulers.onScheduleHook(fn)
    @bsideup
    Schedulers.onScheduleHook("myHook", runnable -> {
    println("Before every scheduled runnable");
    return () -> {
    println("Before execution");
    runnable.run();
    println("After execution");
    };
    });

    View Slide

  146. Schedulers.onScheduleHook(fn)
    @bsideup
    Schedulers.onScheduleHook("mdc", runnable -> {
    String userId = MDC.get("userId");
    return () -> {
    MDC.put("userId", userId);
    try {
    runnable.run();
    } finally {
    MDC.remove("userId");
    }
    };
    });

    View Slide

  147. think about the resiliency
    DO
    @bsideup

    View Slide

  148. “Error… is a signal.”
    Stephane Maldini,
    year unknown
    @bsideup

    View Slide

  149. Resiliency with Reactor
    @bsideup

    View Slide

  150. Resiliency with Reactor
    • .timeout(Duration) - cancel the subscription and fail if no items emitted
    @bsideup

    View Slide

  151. Resiliency with Reactor
    • .timeout(Duration) - cancel the subscription and fail if no items emitted
    • .retry()/retryWithBackoff() - retry the subscription on failure
    @bsideup

    View Slide

  152. Resiliency with Reactor
    • .timeout(Duration) - cancel the subscription and fail if no items emitted
    • .retry()/retryWithBackoff() - retry the subscription on failure
    • .repeatWhenEmpty() - repeat the subscription when it completes without
    values
    @bsideup

    View Slide

  153. Resiliency with Reactor
    • .timeout(Duration) - cancel the subscription and fail if no items emitted
    • .retry()/retryWithBackoff() - retry the subscription on failure
    • .repeatWhenEmpty() - repeat the subscription when it completes without
    values
    • .defaultIfEmpty() - fallback when empty
    @bsideup

    View Slide

  154. Resiliency with Reactor
    • .timeout(Duration) - cancel the subscription and fail if no items emitted
    • .retry()/retryWithBackoff() - retry the subscription on failure
    • .repeatWhenEmpty() - repeat the subscription when it completes without
    values
    • .defaultIfEmpty() - fallback when empty
    • .onErrorResume() - fallback on error
    @bsideup

    View Slide

  155. Distributed?

    View Slide

  156. https://github.com/resilience4j/resilience4j
    RateLimiter rateLimiter = RateLimiter.ofDefaults(“name");
    Mono.fromCallable(backendService::doSomething)
    .transformDeferred(RateLimiterOperator.of(rateLimiter))

    View Slide

  157. care about the threads
    DON’T
    @bsideup

    View Slide

  158. You may ask…
    • “How do I get the current Scheduler?”

    • “Why does flatMap changes the thread?”

    • …”Why it does not?”
    @bsideup

    View Slide

  159. The answer is…

    View Slide

  160. You.

    View Slide

  161. You. Should.

    View Slide

  162. You. Should. Not.

    View Slide

  163. You. Should. Not. Care.

    View Slide

  164. Prepare for day 2
    DO
    @bsideup

    View Slide

  165. Demo
    http://eskipaper.com/homer-jay-simpson-cartoon.html

    View Slide

  166. Demo outcomes
    • Use .checkpoint(“something”) to “mark” reactive “milestones”

    • Read about Hooks.onOperatorDebug()…

    • … but use reactor-tools’ ReactorDebugAgent (works in prod too)

    • https://spring.io/blog/2019/03/06/flight-of-the-flux-1-assembly-vs-
    subscription - great article from Simon Basle about the internals
    @bsideup

    View Slide

  167. Be afraid of reactive programming ;)
    DON’T
    @bsideup

    View Slide

  168. @bsideup

    View Slide

  169. Spring Webflux
    @bsideup

    View Slide

  170. Developer experience
    is one of the main focuses
    in Reactor 3.3…

    View Slide

  171. … and we just started

    View Slide

  172. Summary
    • Learn functional programming
    • Check various operators
    • Think about the resiliency
    • Start gradually
    • Prepare for day 2
    • Use it for heavy computations
    • Care about the threads
    • Block non-blocking threads
    • Use ThreadLocals
    • Be afraid of it ;)
    DO… DON’T…
    @bsideup

    View Slide

  173. @bsideup
    bsideup

    View Slide