Let’s Have Fun with Reactive Programming, Using Reactor and WebFlux

Let’s Have Fun with Reactive Programming, Using Reactor and WebFlux

Oracle Groundbreaker Tour 2019 Tokyo / Taipei

Ad3fbc316f916a341c24035e34913121?s=128

Shin Tanimoto

October 26, 2019
Tweet

Transcript

  1. LET’S HAVE FUN WITH REACTIVE PROGRAMMING, USING REACTOR AND WEBFLUX

    Shin TANIMOTO (@cero_t) Everforth / Acroquest Technology
  2. WHAT’S CONTAINED, AND NOT. ➤ This talk contains ➤ Writing

    Reactive Programming code with Reactor and WebFlux ➤ Operations like Stream API’s stream(), map(), collect() methods ➤ This talk does NOT contain ➤ Reactive Systems ➤ R2DBC, RSocket, Reactive Streams
  3. WHO AM I? ➤ Shin Tanimoto (Twitter: @cero_t) ➤ Senior

    Solution Architect / Troubleshooter ➤ Everforth Co.,LTD. ➤ Acroquest Technology Co.,LTD. ➤ Leader of Japan Java User Group (JJUG) ➤ Java Champion ➤ Oracle Groundbreaker Ambassador ➤ Hobby: Fighting Games, BABYMETAL
  4. AGENDA ➤ What’s Reactive Programming like? ➤ Getting started with

    Reactor ➤ Getting started with WebFlux ➤ Trying N+1 Problem
  5. WHAT’S REACTIVE PROGRAMMING LIKE?

  6. WHAT’S REACTIVE PROGRAMMING LIKE? - SYNCHRONOUS ➤ Synchronous operation

  7. WHAT’S REACTIVE PROGRAMMING LIKE? - ASYNCHRONOUS ➤ Asynchronous operation (blocking

    operation)
  8. WHAT’S REACTIVE PROGRAMMING LIKE? - REACTIVE ➤ Reactive operation (Non-blocking

    operation)
  9. WHAT’S REACTIVE PROGRAMMING LIKE? - REACTIVE ➤ Reactive operation (Non-blocking

    operation)
  10. WHAT’S REACTIVE PROGRAMMING LIKE? - REACTIVE Mono<List<Student>> students = webClient.get()

    .uri("localhost:8081/students/flux") .retrieve() .bodyToFlux(Student.class) .collectList(); Mono<List<StudentScore>> studentScore = students.flatMap(studentList -> webClient.get() .uri("localhost:8081/scores/" + ids(studentList)) .retrieve() .bodyToFlux(Score.class) .collectList() .map(scores -> { Map<Integer, List<Score>> scoreMap = scores.stream() .collect(Collectors.groupingBy(s -> s.id)); return studentList.stream() .map(s -> new StudentScore(s, scoreMap.get(s.id))) .collect(Collectors.toList()); }));
  11. GETTING STARTED WITH REACTOR

  12. GETTING STARTED WITH REACTOR - REACTOR IS ➤ Reactor is

    … ➤ like Stream API monster. ➤ https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html
  13. GETTING STARTED WITH REACTOR - MONO AND FLUX ➤ Mono

    and Flux ➤ Mono ➤ handles single elements ➤ like java.util.Optional<T> ➤ Flux ➤ handles multiple elements ➤ like java.util.stream.Stream<T>
  14. GETTING STARTED WITH REACTOR - FIRST EXAMPLE Mono<String> mono =

    Mono.just("Hello, world!"); mono.subscribe(s -> System.out.println(s)); Flux<String> flux = Flux.just("H", "e", "l", "l", "o"); flux.subscribe(s -> System.out.println(s));
  15. GETTING STARTED WITH REACTOR - FIRST EXAMPLE Mono<String> mono =

    Mono.just("Hello, world!"); mono.subscribe(s -> System.out.println(s)); Flux<String> flux = Flux.just("H", "e", "l", "l", "o"); flux.subscribe(s -> System.out.println(s)); Hello, world! H e l l o
  16. GETTING STARTED WITH REACTOR - FIRST EXAMPLE Mono<String> mono =

    Mono.just("Hello, world!"); mono.subscribe(s -> System.out.println(s)); ↑ Optional<String> optional = Optional.of("Hello, world!"); optional.ifPresent(s -> System.out.println(s)); Flux<String> flux = Flux.just("H", "e", "l", "l", "o"); flux.subscribe(s -> System.out.println(s)); ↑ Stream<String> stream = Stream.of("H", "e", "l", "l", "o"); stream.forEach(s -> System.out.println(s));
  17. GETTING STARTED WITH REACTOR - FIRST EXAMPLE Mono.just("Hello, world!") .subscribe(System.out::println);

    Flux.just("H", "e", "l", "l", "o") .subscribe(System.out::println);
  18. GETTING STARTED WITH REACTOR - FIRST EXAMPLE Mono.just("Hello, world!") .subscribe(System.out::println);

    Flux.just("H", "e", "l", "l", "o") .subscribe(System.out::println); Hello, world! H e l l o
  19. GETTING STARTED WITH REACTOR - FIRST THINGS TO LEARN ➤

    Create Mono, Flux ➤ Mono.just(T), Flux.just(T...) ➤ Creates a Mono or Flux from concrete instance(s) ➤ Flux.interval(Duration) ➤ Create a Flux that emits numbers starting with 0 ➤ Use Mono, Flux ➤ subscribe(Consumer<T>) ➤ Subscribes the Mono or Flux and executes the Consumer method ➤ map(Function<T, V>) ➤ Transforms the item by applying the function to each item
  20. GETTING STARTED WITH REACTOR - MAP AND SUBSCRIBE Flux.interval(Duration.ofMillis(300)) .map(i

    -> i + " " + LocalDateTime.now()) .subscribe(System.out::println);
  21. GETTING STARTED WITH REACTOR - MAP AND SUBSCRIBE Flux.interval(Duration.ofMillis(300)) .map(i

    -> i + " " + LocalDateTime.now()) .subscribe(System.out::println); (Expected) 0 2019-09-18T13:01:16.443446 1 2019-09-18T13:01:16.733289 2 2019-09-18T13:01:17.032832 3 2019-09-18T13:01:17.334665 … 7 2019-09-18T13:01:18.531309 8 2019-09-18T13:01:18.831667 9 2019-09-18T13:01:19.136531
  22. (DEMO)

  23. GETTING STARTED WITH REACTOR - MAP AND SUBSCRIBE Flux.interval(Duration.ofMillis(300)) .map(i

    -> i + " " + LocalDateTime.now()) .subscribe(System.out::println); (Output nothing)
  24. GETTING STARTED WITH REACTOR - SECOND THINGS TO LEARN ➤

    Use Flux ➤ Flux#take(long) ➤ takes only the first given N value from Flux, and then Flux completes ➤ Flux#doOnComplete(Runnable) ➤ executes the given function when Flux completes
  25. GETTING STARTED WITH REACTOR - SECOND THINGS TO LEARN ➤

    java.util.concurrent.CountDownLatch ➤ new CountDownLatch(int) - sets the count to given number ➤ countDown() - decrements the count ➤ await() - waits until the latch has counted down to zero
  26. GETTING STARTED WITH REACTOR - AWAIT UNTIL COMPLETE var latch

    = new CountDownLatch(1); Flux.interval(Duration.ofMillis(300)) .map(i -> i + " " + LocalDateTime.now()) .take(10) .doOnComplete(latch::countDown) .subscribe(System.out::println); latch.await();
  27. GETTING STARTED WITH REACTOR - AWAIT UNTIL COMPLETE var latch

    = new CountDownLatch(1); Flux.interval(Duration.ofMillis(300)) .map(i -> i + " " + LocalDateTime.now()) .take(10) .doOnComplete(latch::countDown) .subscribe(System.out::println); latch.await(); 0 2019-09-18T13:01:16.443446 1 2019-09-18T13:01:16.733289 2 2019-09-18T13:01:17.032832 3 2019-09-18T13:01:17.334665 … 7 2019-09-18T13:01:18.531309 8 2019-09-18T13:01:18.831667 9 2019-09-18T13:01:19.136531
  28. (DEMO)

  29. GETTING STARTED WITH REACTOR - NEXT THINGS TO LEARN ➤

    From Optional to the value ➤ Optional#get() ➤ From Stream to Collection such as List, Set, Map or other values ➤ Stream#collect(Collector<T, A, R>) ➤ From Mono to the value or Optional of the value ➤ Mono#block(), blockOptional() ➤ From Flux to List ➤ Flux#collectList().block() ➤ Flux#collectList() converts Flux<T> to Mono<List<T>> Not recommended to use
  30. GETTING STARTED WITH REACTOR - BLOCK() BREAKS NON-BLOCKING List<String> list

    = Flux.interval(Duration.ofMillis(300)) .map(i -> i + " " + LocalDateTime.now()) .take(10) .collectList() .block(); list.forEach(System.out::println);
  31. (DEMO)

  32. GETTING STARTED WITH REACTOR - BLOCK() BREAKS NON-BLOCKING List<String> list

    = Flux.interval(Duration.ofMillis(300)) .map(i -> i + " " + LocalDateTime.now()) .take(10) .collectList() .block(); list.forEach(System.out::println); (after 3 seconds) 0 2019-09-18T13:59:25.729864 1 2019-09-18T13:59:26.016278 2 2019-09-18T13:59:26.314567 3 2019-09-18T13:59:26.616511 … 7 2019-09-18T13:59:27.816556 8 2019-09-18T13:59:28.117666 9 2019-09-18T13:59:28.414718
  33. GETTING STARTED WITH REACTOR - BLOCK() BREAKS NON-BLOCKING ➤ Blocking

    breaks non-blocking! ➤ Mono#block(), blockOptional() ➤ Flux#blockFirst(), blockLast() ➤ blocks until the next/first/last signal using the thread (blocking operation) ➤ blocks breaks non-bloking!
  34. GETTING STARTED WITH WEBFLUX

  35. GETTING STARTED WITH WEBFLUX - WEBFLUX IS ➤ WebFlux is

    ➤ Non-blocking web framework ➤ Spring MVC + Reactor ➤ Runs on Netty, Undertow, Servlet Container like Tomcat ➤ Reactive WebClient is available instead of RestTemplate
  36. GETTING STARTED WITH WEBFLUX - FIRST EXAMPLE @RestController public class

    WebFluxDemoController { @GetMapping public Flux<String> demo() { return Flux.interval(Duration.ofMillis(300)) .map(i -> i + " " + LocalDateTime.now() + "\n") .take(10); } }
  37. GETTING STARTED WITH WEBFLUX - FIRST EXAMPLE @RestController public class

    WebFluxDemoController { @GetMapping public Flux<String> demo() { return Flux.interval(Duration.ofMillis(300)) .map(i -> i + " " + LocalDateTime.now() + "\n") .take(10); } } Annotations are same as Spring MVC Annotations are same as Spring MVC Arguments and return value can be a Mono and Flux
  38. (DEMO)

  39. GETTING STARTED WITH WEBFLUX - FIRST EXAMPLE @RestController public class

    WebFluxDemoController { @GetMapping public Flux<String> demo() { return Flux.interval(Duration.ofMillis(300)) .map(i -> i + " " + LocalDateTime.now() + "\n") .take(10); } } (after 3 seconds) 0 2019-09-18T13:55:45.969860800 1 2019-09-18T13:55:46.051231 2 2019-09-18T13:55:46.150832200 3 2019-09-18T13:55:46.250867900 … 7 2019-09-18T13:55:46.660427100 8 2019-09-18T13:55:46.760725800| 9 2019-09-18T13:55:46.860524100
  40. GETTING STARTED WITH WEBFLUX - WEBFLUX IS ➤ To make

    ➤ Add Request-Header "Accept: text/event-stream” ➤ curl -H "Accept: text/event-stream" localhost:8080 ➤ Force Response-Header "Content-Type: text/event-stream" ➤ @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
  41. (DEMO)

  42. GETTING STARTED WITH WEBFLUX - FIRST EXAMPLE @RestController public class

    WebFluxDemoController { @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> demo() { return Flux.interval(Duration.ofMillis(300)) .map(i -> i + " " + LocalDateTime.now()) .take(10); } }
  43. GETTING STARTED WITH WEBFLUX - FIRST EXAMPLE @RestController public class

    WebFluxDemoController { @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> demo() { return Flux.interval(Duration.ofMillis(300)) .map(i -> i + " " + LocalDateTime.now()) .take(10); } } data:0 2019-09-18T13:21:47.348753 data:1 2019-09-18T13:21:47.640817 data:2 2019-09-18T13:21:47.941974 data:3 2019-09-18T13:21:48.242554 ... data:7 2019-09-18T13:21:49.439630 data:8 2019-09-18T13:21:49.744733 data:9 2019-09-18T13:21:50.041209
  44. GETTING STARTED WITH WEBFLUX - FIRST EXAMPLE @RestController public class

    WebFluxDemoController { public List<String> demo() { return Flux.interval(Duration.ofMillis(300)) .map(i -> i + " " + LocalDateTime.now()) .take(10) .collectList() .block() } }
  45. (DEMO)

  46. GETTING STARTED WITH WEBFLUX - FIRST EXAMPLE @RestController public class

    WebFluxDemoController { public List<String> demo() { return Flux.interval(Duration.ofMillis(300)) .map(i -> i + " " + LocalDateTime.now()) .take(10) .collectList() .block() } } { "timestamp": "2019-09-18T14:01:39.094+0000", "path": "/", "status": 500, "error": "Internal Server Error", "message": "block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-1" }
  47. GETTING STARTED WITH WEBFLUX - WEBCLIENT ➤ Classic Http client

    ➤ Hard or impossible to handle stream-event ➤ New WebClient has come ➤ Can handle stream-event ➤ Modern style APIs
  48. GETTING STARTED WITH WEBFLUX - CLASSIC RESTTEMPLATE // RestTemplate was

    like … RestTemplate restTemplate = new RestTemplate(); ParameterizedTypeReference<List<Student>> type = new ParameterizedTypeReference<>() {}; List<Student> list = restTemplate .exchange("http://localhost:8081/list", HttpMethod.GET, null, type) .getBody();
  49. GETTING STARTED WITH WEBFLUX - FIRST EXAMPLE var webClient =

    WebClient.builder().build(); Flux<Student> flux = webClient.get() // HTTP GET method .uri("localhost:8081/students/flux") // to this URL .retrieve() // accesses to the server and retrieve .bodyToFlux(Student.class); // get response body as Flux
  50. TRYING N+1 PROBLEM

  51. TRYING N+1 PROBLEM - PATTERN #1 ➤ Overview ➤ First,

    get N data from one microservice (1 access) ➤ Second, get detail data from another microservice for each N data (N access) ➤ Finally, merge them ➤ More concrete ➤ First, get a list of 33 students (List<Student>) ➤ Second, get scores for each student (List<Score>) ➤ Finally merge them (List<StudentScore>)
  52. TRYING N+1 PROBLEM - PATTERN #1 ➤ Services

  53. TRYING N+1 PROBLEM - PATTERN #1 ➤ (Arbitrary) performance limitation

    ➤ Student Resource Service ➤ 33 students ➤ 100ms for each student ➤ Flux - returns one by one every 100ms (total duration: 33 * 100ms = 3.3 secs) ➤ List - returns all after 3.3 secs ➤ Score Resource Service ➤ Only 100ms overhead regardless of the number of data
  54. TRYING N+1 PROBLEM - STUDENT RESOURCE SERVICE Student[] students =

    { new Student(1, "Muto"), new Student(2, "Miyoshi"), … } @GetMapping(value = "/flux", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<Student> getAsFlux() { return Flux.interval(Duration.ofMillis(100)) .map(i -> students[i.intValue()]) .take(students.length); } @GetMapping(value = "/list") public List<Student> getAsList() throws InterruptedException { Thread.sleep(students.length * 100L); return Arrays.asList(students); }
  55. TRYING N+1 PROBLEM - STUDENT RESOURCE SERVICE Student[] students =

    { new Student(1, "Muto"), new Student(2, "Miyoshi"), … } @GetMapping(value = "/flux", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<Student> getAsFlux() { return Flux.interval(Duration.ofMillis(100)) .map(i -> students[i.intValue()]) .take(students.length); } @GetMapping(value = "/list") public List<Student> getAsList() throws InterruptedException { Thread.sleep(students.length * 100L); return Arrays.asList(students); } data:{"id":1,"name":"Muto"} data:{"id":2,"name":"Miyoshi"} data:{"id":3,"name":"Matsui"} data:{"id":4,"name":"Nakamoto"} … [ { "id": 1, "name": "Muto" }, { "id": 2, "name": "Miyoshi" }, { "id": 3, "name": "Matsui" }, …
  56. TRYING N+1 PROBLEM - SCORE RESOURCE SERVICE static Map<Integer, List<Score>>

    scoreStore = new HashMap<>(); // Initialization operation is omitted. @GetMapping("/{ids}") public List<Score> getAsList(@PathVariable List<Integer> ids) throws InterruptedException { Thread.sleep(100); return ids.stream() .flatMap(id -> scoreStore.get(id).stream()) .collect(Collectors.toList()); }
  57. TRYING N+1 PROBLEM - SCORE RESOURCE SERVICE static Map<Integer, List<Score>>

    scoreStore = new HashMap<>(); // Initialization operation is omitted. @GetMapping("/{ids}") public List<Score> getAsList(@PathVariable List<Integer> ids) throws InterruptedException { Thread.sleep(100); return ids.stream() .flatMap(id -> scoreStore.get(id).stream()) .collect(Collectors.toList()); } SELECT * FROM score WHERE ids in (1, 2, 3)
  58. TRYING N+1 PROBLEM - SCORE RESOURCE SERVICE static Map<Integer, List<Score>>

    scoreStore = new HashMap<>(); // Initialization operation is omitted. @GetMapping("/{ids}") public List<Score> getAsList(@PathVariable List<Integer> ids) throws InterruptedException { Thread.sleep(100); return ids.stream() .flatMap(id -> scoreStore.get(id).stream()) .collect(Collectors.toList()); } [ { "id": 1, "type": "Math", "score": 2 }, { "id": 1, "type": "Biology", "score": 3 }, { "id": 1, "type": "English", "score": 4 }, ... { "id": 2, "type": "Math", "score": 3 }, { "id": 2, "type": "Biology", "score": 4 }, ...
  59. TRYING N+1 PROBLEM - BFF SERVICE (RESTTEMPLATE) ParameterizedTypeReference<List<Student>> studentType =

    new ParameterizedTypeReference<>() {}; ParameterizedTypeReference<List<Score>> scoreType = new ParameterizedTypeReference<>() {}; List<Student> students = restTemplate .exchange("http://localhost:8081/students/list", HttpMethod.GET, null, studentType) .getBody(); List<StudentScore> studentScores = students.stream() .map(student -> { String url = "http://localhost:8081/scores/" + student.id; List<Score> scores = restTemplate .exchange(url, HttpMethod.GET, null, scoreType) .getBody(); return new StudentScore(student, scores); }) .collect(Collectors.toList());
  60. TRYING N+1 PROBLEM - BFF SERVICE (RESTTEMPLATE) ParameterizedTypeReference<List<Student>> studentType =

    new ParameterizedTypeReference<>() {}; ParameterizedTypeReference<List<Score>> scoreType = new ParameterizedTypeReference<>() {}; List<Student> students = restTemplate .exchange("http://localhost:8081/students/list", HttpMethod.GET, null, studentType) .getBody(); List<StudentScore> studentScores = students.stream() .map(student -> { String url = "http://localhost:8081/scores/" + student.id; List<Score> scores = restTemplate .exchange(url, HttpMethod.GET, null, scoreType) .getBody(); return new StudentScore(student, scores); }) .collect(Collectors.toList()); Like boilerplates for RestTemplate
  61. TRYING N+1 PROBLEM - BFF SERVICE (RESTTEMPLATE) ParameterizedTypeReference<List<Student>> studentType =

    new ParameterizedTypeReference<>() {}; ParameterizedTypeReference<List<Score>> scoreType = new ParameterizedTypeReference<>() {}; List<Student> students = restTemplate .exchange("http://localhost:8081/students/list", HttpMethod.GET, null, studentType) .getBody(); List<StudentScore> studentScores = students.stream() .map(student -> { String url = "http://localhost:8081/scores/" + student.id; List<Score> scores = restTemplate .exchange(url, HttpMethod.GET, null, scoreType) .getBody(); return new StudentScore(student, scores); }) .collect(Collectors.toList()); Get student list from Student Resource Service
  62. TRYING N+1 PROBLEM - BFF SERVICE (RESTTEMPLATE) ParameterizedTypeReference<List<Student>> studentType =

    new ParameterizedTypeReference<>() {}; ParameterizedTypeReference<List<Score>> scoreType = new ParameterizedTypeReference<>() {}; List<Student> students = restTemplate .exchange("http://localhost:8081/students/list", HttpMethod.GET, null, studentType) .getBody(); List<StudentScore> studentScores = students.stream() .map(student -> { String url = "http://localhost:8081/scores/" + student.id; List<Score> scores = restTemplate .exchange(url, HttpMethod.GET, null, scoreType) .getBody(); return new StudentScore(student, scores); }) .collect(Collectors.toList()); Get score list from Score Resource Service for each student
  63. TRYING N+1 PROBLEM - BFF SERVICE (RESTTEMPLATE) ParameterizedTypeReference<List<Student>> studentType =

    new ParameterizedTypeReference<>() {}; ParameterizedTypeReference<List<Score>> scoreType = new ParameterizedTypeReference<>() {}; List<Student> students = restTemplate .exchange("http://localhost:8081/students/list", HttpMethod.GET, null, studentType) .getBody(); List<StudentScore> studentScores = students.stream() .map(student -> { String url = "http://localhost:8081/scores/" + student.id; List<Score> scores = restTemplate .exchange(url, HttpMethod.GET, null, scoreType) .getBody(); return new StudentScore(student, scores); }) .collect(Collectors.toList()); Merge student and scores into StudentScore. And returns it as a List
  64. TRYING N+1 PROBLEM - BFF SERVICE (WEBCLIENT) Flux<Student> students =

    webClient.get() .uri("localhost:8081/students/flux") .retrieve() .bodyToFlux(Student.class); Flux<StudentScore> studentScore = students.flatMap(student -> webClient.get() .uri("localhost:8081/scores/" + student.id) .retrieve() .bodyToFlux(Score.class) .collectList() .map(scores -> new StudentScore(student, scores)));
  65. TRYING N+1 PROBLEM - BFF SERVICE (WEBCLIENT) Flux<Student> students =

    webClient.get() .uri("localhost:8081/students/flux") .retrieve() .bodyToFlux(Student.class); Flux<StudentScore> studentScore = students.flatMap(student -> webClient.get() .uri("localhost:8081/scores/" + student.id) .retrieve() .bodyToFlux(Score.class) .collectList() .map(scores -> new StudentScore(student, scores))); Get student list from Student Resource Service
  66. TRYING N+1 PROBLEM - BFF SERVICE (WEBCLIENT) Flux<Student> students =

    webClient.get() .uri("localhost:8081/students/flux") .retrieve() .bodyToFlux(Student.class); Flux<StudentScore> studentScore = students.flatMap(student -> webClient.get() .uri("localhost:8081/scores/" + student.id) .retrieve() .bodyToFlux(Score.class) .collectList() .map(scores -> new StudentScore(student, scores))); Get score list from Score Resource Service for each student Merge student and scores into StudentScore.
  67. TRYING N+1 PROBLEM - BFF SERVICE (WEBCLIENT) Flux<Student> students =

    webClient.get() .uri("localhost:8081/students/flux") .retrieve() .bodyToFlux(Student.class); Flux<StudentScore> studentScore = students.flatMap(student -> webClient.get() .uri("localhost:8081/scores/" + student.id) .retrieve() .bodyToFlux(Score.class) .collectList() .map(scores -> new StudentScore(student, scores))); flatMap!? collectList!? map?
  68. TRYING N+1 PROBLEM - BFF SERVICE (WEBCLIENT) Flux<StudentScore> studentScore =

    students.flatMap(student -> { Flux<Score> flux = webClient.get() .uri("localhost:8081/scores/" + student.id) .retrieve() .bodyToFlux(Score.class); Mono<List<Score>> listMono = flux.collectList(); Mono<StudentScore> mono = listMono.map(scores -> new StudentScore(student, scores)); return mono; }); Get scores and keep them as Flux<Score>. But I want List<Score> for StudentScore
  69. TRYING N+1 PROBLEM - BFF SERVICE (WEBCLIENT) Flux<StudentScore> studentScore =

    students.flatMap(student -> { Flux<Score> flux = webClient.get() .uri("localhost:8081/scores/" + student.id) .retrieve() .bodyToFlux(Score.class); Mono<List<Score>> listMono = flux.collectList(); Mono<StudentScore> mono = listMono.map(scores -> new StudentScore(student, scores)); return mono; }); From Flux to List withot block()
  70. TRYING N+1 PROBLEM - BFF SERVICE (WEBCLIENT) Flux<StudentScore> studentScore =

    students.flatMap(student -> { Flux<Score> flux = webClient.get() .uri("localhost:8081/scores/" + student.id) .retrieve() .bodyToFlux(Score.class); Mono<List<Score>> listMono = flux.collectList(); Mono<StudentScore> mono = listMono.map(scores -> new StudentScore(student, scores)); return mono; }); Executes operation for List<Score>
  71. TRYING N+1 PROBLEM - BFF SERVICE (WEBCLIENT) Flux<StudentScore> studentScore =

    students.flatMap(student -> { Flux<Score> flux = webClient.get() .uri("localhost:8081/scores/" + student.id) .retrieve() .bodyToFlux(Score.class); Mono<List<Score>> listMono = flux.collectList(); Mono<StudentScore> mono = listMono.map(scores -> new StudentScore(student, scores)); return mono; }); Mono -> Flux (Flux -> Flux) when use flatMap
  72. TRYING N+1 PROBLEM - BFF SERVICE (WEBCLIENT) Flux<Mono<StudentScore>> studentScore =

    students.map(student -> { Flux<Score> flux = webClient.get() .uri("localhost:8081/scores/" + student.id) .retrieve() .bodyToFlux(Score.class); Mono<List<Score>> listMono = flux.collectList(); Mono<StudentScore> mono = listMono.map(scores -> new StudentScore(student, scores)); return mono; }); Mono -> Flux<Mono> when use map()
  73. (DEMO) curl localhost:8080/rest curl localhost:8080/flux

  74. TRYING N+1 PROBLEM - PERFORMANCE DIFFERENCE ➤ RestTemplate version ➤

    Get all students: 3.3 secs (100ms * 33 data) ➤ Get scores: 3.3 secs (another 100ms * 33 accesses) ➤ More than 6.6 sec ➤ WebClient version ➤ Get each student: 3.3 secs (100ms * 33 data) ➤ Get scores while getting student : 3.3 secs (another 100ms * 33 accesses) ➤ More than 3.3 sec
  75. TRYING N+1 PROBLEM - BE BETTER ➤ To be better

    ➤ When getting score, arguments should be Flux (by HTTP request body), ➤ Not String (by HTTP query string) ➤ webClient.post() .uri("localhost:8081/scores/flux") .body(fluxStudents) ➤ Only access once, not 33 times.
  76. ENJOY REACTIVE PROGRAMMINGS! ➤ Links ➤ Project Reactor - Learn

    ➤ https://projectreactor.io/learn ➤ https://github.com/reactor/lite-rx-api-hands- on/ ➤ Demo application ➤ https://github.com/cero-t/webflux-example/ ➤ My Twitter (@cero_t) ➤ https://twitter.com/cero_t