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

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

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  5. WHAT’S REACTIVE PROGRAMMING LIKE?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  10. 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());
    }));

    View Slide

  11. GETTING STARTED WITH REACTOR

    View Slide

  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

    View Slide

  13. 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

    View Slide

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

    View Slide

  15. 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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  22. (DEMO)

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  28. (DEMO)

    View Slide

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

    View Slide

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

    View Slide

  31. (DEMO)

    View Slide

  32. 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

    View Slide

  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!

    View Slide

  34. GETTING STARTED WITH WEBFLUX

    View Slide

  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

    View Slide

  36. 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);
    }
    }

    View Slide

  37. 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

    View Slide

  38. (DEMO)

    View Slide

  39. 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

    View Slide

  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)

    View Slide

  41. (DEMO)

    View Slide

  42. 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);
    }
    }

    View Slide

  43. 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

    View Slide

  44. 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()
    }
    }

    View Slide

  45. (DEMO)

    View Slide

  46. 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"
    }

    View Slide

  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

    View Slide

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

    View Slide

  49. 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

    View Slide

  50. TRYING N+1 PROBLEM

    View Slide

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

    View Slide

  52. TRYING N+1 PROBLEM - PATTERN #1
    ➤ Services

    View Slide

  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

    View Slide

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

    View Slide

  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 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"
    },

    View Slide

  56. 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());
    }

    View Slide

  57. 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)

    View Slide

  58. 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
    },
    ...

    View Slide

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

    View Slide

  60. 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

    View Slide

  61. 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

    View Slide

  62. 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

    View Slide

  63. 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

    View Slide

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

    View Slide

  65. 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

    View Slide

  66. 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.

    View Slide

  67. 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?

    View Slide

  68. 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

    View Slide

  69. 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()

    View Slide

  70. 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

    View Slide

  71. 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

    View Slide

  72. 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()

    View Slide

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

    View Slide

  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

    View Slide

  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.

    View Slide

  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

    View Slide