Slide 1

Slide 1 text

LET’S HAVE FUN WITH REACTIVE PROGRAMMING, USING REACTOR AND WEBFLUX Shin TANIMOTO (@cero_t) Everforth / Acroquest Technology

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

AGENDA ➤ What’s Reactive Programming like? ➤ Getting started with Reactor ➤ Getting started with WebFlux ➤ Trying N+1 Problem

Slide 5

Slide 5 text

WHAT’S REACTIVE PROGRAMMING LIKE?

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

WHAT’S REACTIVE PROGRAMMING LIKE? - REACTIVE Mono> students = webClient.get() .uri("localhost:8081/students/flux") .retrieve() .bodyToFlux(Student.class) .collectList(); Mono> studentScore = students.flatMap(studentList -> webClient.get() .uri("localhost:8081/scores/" + ids(studentList)) .retrieve() .bodyToFlux(Score.class) .collectList() .map(scores -> { Map> scoreMap = scores.stream() .collect(Collectors.groupingBy(s -> s.id)); return studentList.stream() .map(s -> new StudentScore(s, scoreMap.get(s.id))) .collect(Collectors.toList()); }));

Slide 11

Slide 11 text

GETTING STARTED WITH REACTOR

Slide 12

Slide 12 text

GETTING STARTED WITH REACTOR - REACTOR IS ➤ Reactor is … ➤ like Stream API monster. ➤ https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html

Slide 13

Slide 13 text

GETTING STARTED WITH REACTOR - MONO AND FLUX ➤ Mono and Flux ➤ Mono ➤ handles single elements ➤ like java.util.Optional ➤ Flux ➤ handles multiple elements ➤ like java.util.stream.Stream

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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) ➤ Subscribes the Mono or Flux and executes the Consumer method ➤ map(Function) ➤ Transforms the item by applying the function to each item

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

(DEMO)

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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();

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

(DEMO)

Slide 29

Slide 29 text

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) ➤ From Mono to the value or Optional of the value ➤ Mono#block(), blockOptional() ➤ From Flux to List ➤ Flux#collectList().block() ➤ Flux#collectList() converts Flux to Mono> Not recommended to use

Slide 30

Slide 30 text

GETTING STARTED WITH REACTOR - BLOCK() BREAKS NON-BLOCKING List list = Flux.interval(Duration.ofMillis(300)) .map(i -> i + " " + LocalDateTime.now()) .take(10) .collectList() .block(); list.forEach(System.out::println);

Slide 31

Slide 31 text

(DEMO)

Slide 32

Slide 32 text

GETTING STARTED WITH REACTOR - BLOCK() BREAKS NON-BLOCKING List 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

Slide 33

Slide 33 text

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!

Slide 34

Slide 34 text

GETTING STARTED WITH WEBFLUX

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

GETTING STARTED WITH WEBFLUX - FIRST EXAMPLE @RestController public class WebFluxDemoController { @GetMapping public Flux 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

Slide 38

Slide 38 text

(DEMO)

Slide 39

Slide 39 text

GETTING STARTED WITH WEBFLUX - FIRST EXAMPLE @RestController public class WebFluxDemoController { @GetMapping public Flux 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

Slide 40

Slide 40 text

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)

Slide 41

Slide 41 text

(DEMO)

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

GETTING STARTED WITH WEBFLUX - FIRST EXAMPLE @RestController public class WebFluxDemoController { @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux 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

Slide 44

Slide 44 text

GETTING STARTED WITH WEBFLUX - FIRST EXAMPLE @RestController public class WebFluxDemoController { public List demo() { return Flux.interval(Duration.ofMillis(300)) .map(i -> i + " " + LocalDateTime.now()) .take(10) .collectList() .block() } }

Slide 45

Slide 45 text

(DEMO)

Slide 46

Slide 46 text

GETTING STARTED WITH WEBFLUX - FIRST EXAMPLE @RestController public class WebFluxDemoController { public List 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" }

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

GETTING STARTED WITH WEBFLUX - CLASSIC RESTTEMPLATE // RestTemplate was like … RestTemplate restTemplate = new RestTemplate(); ParameterizedTypeReference> type = new ParameterizedTypeReference<>() {}; List list = restTemplate .exchange("http://localhost:8081/list", HttpMethod.GET, null, type) .getBody();

Slide 49

Slide 49 text

GETTING STARTED WITH WEBFLUX - FIRST EXAMPLE var webClient = WebClient.builder().build(); Flux 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

Slide 50

Slide 50 text

TRYING N+1 PROBLEM

Slide 51

Slide 51 text

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) ➤ Second, get scores for each student (List) ➤ Finally merge them (List)

Slide 52

Slide 52 text

TRYING N+1 PROBLEM - PATTERN #1 ➤ Services

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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 getAsFlux() { return Flux.interval(Duration.ofMillis(100)) .map(i -> students[i.intValue()]) .take(students.length); } @GetMapping(value = "/list") public List getAsList() throws InterruptedException { Thread.sleep(students.length * 100L); return Arrays.asList(students); }

Slide 55

Slide 55 text

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 getAsFlux() { return Flux.interval(Duration.ofMillis(100)) .map(i -> students[i.intValue()]) .take(students.length); } @GetMapping(value = "/list") public List 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" }, …

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

TRYING N+1 PROBLEM - SCORE RESOURCE SERVICE static Map> scoreStore = new HashMap<>(); // Initialization operation is omitted. @GetMapping("/{ids}") public List getAsList(@PathVariable List 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)

Slide 58

Slide 58 text

TRYING N+1 PROBLEM - SCORE RESOURCE SERVICE static Map> scoreStore = new HashMap<>(); // Initialization operation is omitted. @GetMapping("/{ids}") public List getAsList(@PathVariable List 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 }, ...

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

TRYING N+1 PROBLEM - BFF SERVICE (RESTTEMPLATE) ParameterizedTypeReference> studentType = new ParameterizedTypeReference<>() {}; ParameterizedTypeReference> scoreType = new ParameterizedTypeReference<>() {}; List students = restTemplate .exchange("http://localhost:8081/students/list", HttpMethod.GET, null, studentType) .getBody(); List studentScores = students.stream() .map(student -> { String url = "http://localhost:8081/scores/" + student.id; List 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

Slide 63

Slide 63 text

TRYING N+1 PROBLEM - BFF SERVICE (RESTTEMPLATE) ParameterizedTypeReference> studentType = new ParameterizedTypeReference<>() {}; ParameterizedTypeReference> scoreType = new ParameterizedTypeReference<>() {}; List students = restTemplate .exchange("http://localhost:8081/students/list", HttpMethod.GET, null, studentType) .getBody(); List studentScores = students.stream() .map(student -> { String url = "http://localhost:8081/scores/" + student.id; List 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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

TRYING N+1 PROBLEM - BFF SERVICE (WEBCLIENT) Flux students = webClient.get() .uri("localhost:8081/students/flux") .retrieve() .bodyToFlux(Student.class); Flux 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

Slide 66

Slide 66 text

TRYING N+1 PROBLEM - BFF SERVICE (WEBCLIENT) Flux students = webClient.get() .uri("localhost:8081/students/flux") .retrieve() .bodyToFlux(Student.class); Flux 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.

Slide 67

Slide 67 text

TRYING N+1 PROBLEM - BFF SERVICE (WEBCLIENT) Flux students = webClient.get() .uri("localhost:8081/students/flux") .retrieve() .bodyToFlux(Student.class); Flux 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?

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

(DEMO) curl localhost:8080/rest curl localhost:8080/flux

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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.

Slide 76

Slide 76 text

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