Toby의 ReactiveProgramming

Mono의 동작방식과 block()

webmaster 2022. 8. 28. 21:42
728x90

의존성이 webflux만 있다(web은 같이 있으면 x)
서버 실행시 톰캣이 아닌 Netty가 실행된다.

@SpringBootApplication
public class Chapter13Application {
  public static void main(String[] args) {
    //Tomcat을 기본으로 사용하는 것이 아닌, netty가 실행이된다.
    //비동기 non-blocking 에 사용되는 서버인 netty가 기본적으로 실행되는 것을 확인 할 수 있다.
    SpringApplication.run(Chapter13Application.class, args);
  }
}
  • dependency를 추가할 때는 spring-web과 spring-webflux를 같이 import 하지 말자(webflux에 web을 포함하고 있음)
  • 기본적으로 spring-webflux의 dependency만 있다면, 서버는 톰캣이 아닌 netty가 실행되는 것을 알 수 있다.
    • web도 같이 dependency에 추가하면, 톰캣이 뜬다(톰캣 우선순위가 더 높다)

Hello Mono

@RestController
public static class MyController{
  @GetMapping("/")
  Mono<String> hello(){
    return Mono.just("Hello WebFlux"); //new로 생성하면 안되고, static 패턴인 just로 생성해야한다.
  }
}
  • Mono를 반환해 비동기 적으로 Hello Webflux를 출력할 수 있다.

Mono에 대해 자세히 살펴보자!

log() 찍기

@GetMapping("/")
Mono<String> hello(){
  return Mono.just("Hello WebFlux")
      // Publisher -> (Publisher) -> (Publisher) -> Subscriber 이런 패턴으로 구현된 것이 Reactive 방식이다.
      .log()
      ;
}

로그 출력

  • onSubscribe를 먼저 실행( 누가 할까 -> Spring)
  • onNext()로 Mono로 생성한 데이터를 전달하고, onComplete를 실행하는 것을 확인할 수 있다.

비동기식으로 동작하는 Mono 확인하기

@GetMapping("/")
Mono<String> hello(){
  log.info("pos1");
  Mono m = Mono.just("Hello WebFlux").doOnNext(c -> log.info(c)).log(); //이전에 동기식으로 동작한다면, 해당 코드 먼저 실행되야 하지만, log가 찍히고 해당 로그가 찍힌다.
  String msg = generateHello();
  Mono m = Mono.just(msg).doOnNext(c -> log.info(c)).log(); //pos1 -> method generateHello() -> pos2 실행후 비동기실행 로그가 출력된다.
  log.info("pos2");
  return m;
}

 private String generateHello() {
  log.info("method generateHello()");
  return "Hello Mono";
}

  • 코드는 동기적으로 동작하는 것이지만, 실행되는 메서드들은 비동기적으로(데이터가 전달을 받게 되면) 실행되는 것이다.
  • 만약 어떤 값을 반환하는 메서드가 just안에 들어가게 된다면 어떤 식으로 코드가 실행될까?
    • just는 이미 준비된 Publishing 한 데이터를 가지고, 비동기로 실행되기 때문에 어떤 값을 반환하는 메서드가 호출되고, 결과를 얻은 다음 그 값을 reactive 방식으로 처리할 것이다 -> 어떤 값을 반환하는 메서드가 먼저 실행

callback을 통해 호출되는 Mono

@GetMapping("/")
Mono<String> hello() {
  log.info("pos1");
  //fromSupplier = 파라미터는X 리턴값만 있다.
  Mono m = Mono.fromSupplier(() -> generateHello()).doOnNext(c -> log.info(c)).log();
  log.info("pos2");
  return m;
}

결과

  • fromSupplier 메서드는 파라미터가 없고, 리턴 값만 받는 함수이다.
  • 비동기로, 함수를 실행시키고 싶다면(함수를 callback에서 호출하고 싶을 경우)는 위와 같이 fromSupplier를 사용하면 된다.
  • 람다로 전달된 함수는 Publisher로 Subscribe를 호출하게 되었을 때, 실행된다.

상황: subscribe를 메서드에서 실행 후, 리턴하면 어떻게 될까?

  1. subscribe를 한번 했으니 리턴시 오류가 난다
  2. subscribe를 한번 했으니 실행 시 오류는 없지만 실행 x
  3. subscribe를 2번 호출이 된다.
@GetMapping("/")
Mono<String> hello() {
  log.info("pos1");
  Mono m = Mono.fromSupplier(() -> generateHello()).doOnNext(c -> log.info(c)).log();
  m.subscribe();
  log.info("pos2");
  return m;
}

결과

  • 3번 결과가 실행이 된다.
  • pos1 로그를 출력 후, Mono의 실행을 진행하고(m.subcribe에서 진행) 후, pos2 를 출력 후, 스프링이 다시 리턴 받은 Mono의 subscribe를 호출한다
  • Cold / Hot 소스코드에 따라 다르게 동작된다.
    • Cold 소스 코드(DB, 메서드를 통해 넘어오는 데이터) Publisher : Mono의 체인을 처음부터 호출한다.
    • Hot 소스 코드(실시간으로 일어나는 이벤트)  Publisher: 구독하는 데이터의 시점부터 시작해서, 체인닝을 시작한다

block() : 현재의 Webflux에서 지원하지 않는 기능

@GetMapping("/")
Mono<String> hello() {
  log.info("pos1");
  String msg = generateHello();
  Mono<String> m = Mono.just(msg).doOnNext(c -> log.info(c)).log();
  String msg2 = m.block(); //Mono 체인으로 동작시킬 것이 아닌 String 으로 받을 때 사용한다.
  log.info("pos2");
   //return m;
  return Mono.just(msg2);
}

결과

  • Mono에서 전달받은 데이터를 출력하기 위해 m.block()을 사용하였다.
  • 이럴 경우 Mono에서 실행시킬 함수를 실행시켜, block()에 값을 전달
    • subscribe를 실행시키기도 전에 m.block()을 호출한 순간, 값을 가지고 와야 하기 때문에 함수를 실행 할 수 밖에 없다
    • 비동기 함수에서 get()을 사용한 것과 같다 
    • callback과 같이 비동기를 사용한 것이 아니기 때문에 더 이상 지원하지 않는 것으로 보인다.(오류가 발생한다)
  • block()을 통해 값을 읽어 온 뒤, 해당 다시 mono를 리턴하면, subscribe가 한번 더 호출되어 코드가 또 실행될 수 있는데, Mono.just()로 block()된 데이터를 반환하여, 한 번만 실행하도록 해야 한다.
  • 비동기 작업은 모두 Mono로 연결해서 동작시켜야 하기 때문에 block() 기능은 웬만하면 사용하지 말자!!

Mono를 사용하면, 많은 비동기 작업을 조합시킬 수 있고, 오류를 쉽게 처리하고 복구할 수 있기 때문에 Mono를 적극적으로 활용하자! 

728x90