생계유지형 개발자/Reactive Programming

[리액티브 프로그래밍] About Webflux, Mono

이 가을 2021. 3. 19. 16:15

WebFlux에서 Controller의 요청처리 과정 (Mono 실행 시점)

WebFlux에서 Mono.just() 를 사용해 기본적인 Controller를 구현했다.

    @GetMapping("/mono")
    fun mono(): Mono<String> {
    
        logger.log("print 1")

        val m: Mono<String> = Mono.just(generateHello())
	        .doOnNext { logger.log("doOnNext() => $it") }
        	.log()

    	logger.log("print 2")

        return m
    }

   fun generateHello(): String {
        logger.log("method generateHello()")
        return "Hello Mono"
    }

* 위 소스에서 logger.log()는 Slf4j Logger를 재정의한 출력 함수이다.

* Mono.log() Publisher Subscriber의 기본 동작인 onSubscribe(), onNext(), onComplete() 등의 과정들을 출력하는 함수(Operator)이다.
* Mono 는 기본적으로 데이터를 1개 가지고 있기 때문에 onNext()를 한번만 호출, 그 다음 바로 onComplete()를 호출한다.

 

WebFlux가 아닌 Web MVC 개념으로 생각하면 위의 mono() 함수가 실행될 때 콘솔에 출력되는 형태를 아래와 같이 예상할 것이다.

print 1
method generateHello()

doOnNext() => Hello Webflux
print 2

하지만 실제 출력 결과는 아래와 같다. ( | 로 시작하는 라인은 log()함수에서 출력함)

즉, Mono 로직은 어떤 라인에 작성되어 있는지 무관하게 실제로 가장 마지막에 실행이 되었다는 것을 알 수 있다.

WebFlux는 Controller에서 반환된 Mono 객체로 subscribe() 를 실행하기 때문에, 함수가 끝난 뒤 Mono 로직이 실행된다.

만약 generateHello() 함수를 Mono가 실행되는 시점에 실행하고 싶다면 fromSupplier() 사용할 수 있다. (Mono 로직은 여전히 메소드가 끝난 후 실행된다.)

	// Mono.just 수정
	val m: Mono<String> = Mono.fromSupplier {generateHello()}
	        .doOnNext { logger.log("doOnNext() => $it") }
        	.log()

코드를 위와 같이 수정하면 method generateHello() 메세지 출력 위치가 변경된 것을 확인할 수 있다.

불필요한 출력 함수를 제거하고 맨 위의 소스코드에서 m.subscribe()를 추가해보았다.

    @GetMapping("/mono")
    fun mono(): Mono<String> {
        val m: Mono<String> = Mono.just("Hello Mono")
        	.doOnNext { logger.log("doOnNext() => $it") }
        m.subscribe()

        return m
    }

실행 결과는 doOnNext() => Hello Mono 가 2번 출력될 것이다.
첫번째는 함수 내에서 m.subscribe()를 실행할 때, 두번째는 return m 이후 webflux 라이브러리에서 subscribe()를 실행했을 때.

Mono가 담고있는 객체 꺼내기

block() 함수를 사용하면 Mono가 담고있는 객체를 꺼낼 수 있다.

요청을 처리하고 나서 단순히 결과 객체만 필요하다 싶을 때 사용할 수 있고,  block()을 사용하면 위에서 m.subscribe()를 명시한것처럼 함수 내에서 Mono 로직을 수행한다.

아래 예제가 있다.

    @GetMapping("/mono")
    fun mono(): Mono<String> {
        val msg = "Hello Mono"
        val m: Mono<String> = Mono.just(msg)
            .doOnNext { logger.log("doOnNext() => $it") }

        val data = m.block() as String	// 위의 Mono chain 실행
        logger.log("data => $data")	
        logger.log("data obj is same with msg ? => ${data.equals(msg)}")
        
        // return 후에 Mono chain 한번 더 실행
        return m
    }

doOnNext() => Hello Mono 가 총 두번 실행되었고 중간에 logger.log()로 출력한 메세지가 있다.

이를 통해 block() 함수를 요청하면 Mono chain이 실행된다는 것을 확인할 수 있다. 

* 가능한 block()은 직접 호출하지 않는 것이 좋다고 한다.

* WebFlux 내부에서 이미 정의되어 있을테니 가능한 Mono에 데이터를 담아,  Mono만 사용해서 return 하도록 한다.