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

Let’s Have Fun with Reactive Programming, Using...

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

Oracle Groundbreaker Tour 2019 Tokyo / Taipei

Avatar for Shin Tanimoto

Shin Tanimoto

October 26, 2019
Tweet

More Decks by Shin Tanimoto

Other Decks in Programming

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? - 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()); }));
  6. GETTING STARTED WITH REACTOR - REACTOR IS ➤ Reactor is

    … ➤ like Stream API monster. ➤ https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html
  7. 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>
  8. 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));
  9. 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
  10. 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));
  11. 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);
  12. 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
  13. 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
  14. GETTING STARTED WITH REACTOR - MAP AND SUBSCRIBE Flux.interval(Duration.ofMillis(300)) .map(i

    -> i + " " + LocalDateTime.now()) .subscribe(System.out::println);
  15. 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
  16. GETTING STARTED WITH REACTOR - MAP AND SUBSCRIBE Flux.interval(Duration.ofMillis(300)) .map(i

    -> i + " " + LocalDateTime.now()) .subscribe(System.out::println); (Output nothing)
  17. 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
  18. 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
  19. 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();
  20. 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
  21. 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
  22. 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);
  23. 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
  24. 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!
  25. 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
  26. 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); } }
  27. 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
  28. 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
  29. 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)
  30. 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); } }
  31. 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
  32. 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() } }
  33. 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" }
  34. 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
  35. 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();
  36. 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
  37. 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>)
  38. 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
  39. 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); }
  40. 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" }, …
  41. 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()); }
  42. 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)
  43. 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 }, ...
  44. 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());
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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)));
  50. 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
  51. 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.
  52. 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?
  53. 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
  54. 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()
  55. 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>
  56. 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
  57. 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()
  58. 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
  59. 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.
  60. 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