코딩하는 개굴이

[Spring] Webflux 웹 클라이언트, R2DBC, Coroutine 변환 본문

Spring

[Spring] Webflux 웹 클라이언트, R2DBC, Coroutine 변환

개굴이모자 2023. 9. 17. 23:40
반응형
해당 포스팅은 Fastcampus 의 실무 프로젝트로 배우는 Kotlin & Spring: 리팩토링부터 서비스 구현까지 강의를 기반으로 작성되었습니다.

 

WebClient

RestTemplate

RestTemplate 는 스프링에서 제공하는 블로킹 방식의 HttpClient 로 스프링 애플리케이션에서 다른 서버와 통신 할 경우 사용된다. 그러나, Spring5 부터 Deprecated 되어 WebFlux 이후에는 WebClient 를 사용하길 권고한다.

package com.fastcampus.springwebflux.webclient
import com.fastcampus.springwebflux.book.Book
import org.slf4j.LoggerFactory
import org.springframework.core.ParameterizedTypeReference
import org.springframework.http.HttpMethod
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.client.RestTemplate

@RestController
class WebClientExample {
    val url = "<http://localhost:8080/books>"
    val log = LoggerFactory.getLogger(javaClass)
    @GetMapping("/books/block")
    fun getBooksBlockingWay(): List<Book> {
        log.info("Start RestTemplate")
        val restTemplate = RestTemplate()
        val response = restTemplate.exchange(url, HttpMethod.GET, null,
            object : ParameterizedTypeReference<List<Book>>() {})
        val result= response.body!!
        log.info("result : {}", result)
        log.info("Finish RestTemplate")
        return result
    }
}

//결과
2023-09-17 14:30:12.552  INFO 31875 --- [ctor-http-nio-4] c.f.s.webclient.WebClientExample         : Start RestTemplate
2023-09-17 14:30:12.593  INFO 31875 --- [ctor-http-nio-4] c.f.s.webclient.WebClientExample         : result: [Book(id=1, name=Kotlin in Action, price=30000), Book(id=2, name=Perfect guide for Http, price=40000)]
2023-09-17 14:30:12.593  INFO 31875 --- [ctor-http-nio-4] c.f.s.webclient.WebClientExample         : Finish RestTemplate

위와 같이 RestTemplate 는 요청을 보낸 서버로부터 응답을 받을 때까지 스레드가 블로킹되어 다른 일을 하지 못하는 문제가 있다.

만일 하나의 API 에서 여러 서버의 응답을 받아야할 경우, 하나씩 처리하므로 응답이 느려지는 문제가 발생할 수 있기에, 복수의 응답을 처리하는 경우는 CompletableFuture 등의 방식을 사용해야한다.

WebClient

WebClient 는 리액티브 기반의 논블로킹 HttpClient로, 논블로킹/블로킹을 둘 다 제공할 수 있기에 스프링5 이후부터 RestTemplate 를 대체하였다. WebClient 를 사용하면 스레드가 응답을 기다릴 필요 없이 처리가 가능하므로, RestTemplate 보다 부하를 줄이고, 여러 서버의 응답을 받아 처리할 경우 동시에 호출이 가능하므로 빠르게 처리가 가능하다.

@GetMapping("/books/nonblock")
    fun getBooksNonBlockingWay(): Flux<Book> {
        log.info("Start WebClient")
        val flux = WebClient.create()
            .get()
            .uri(url)
            .retrieve()
            .bodyToFlux(Book::class.java)
            .map {
                log.info("result : {}", it)
                it
            }
        log.info("Finish WebClient")
        return flux
    }

//결과 - finish 후 result 가 반환되는 것을 볼 수 있다.
2023-09-17 14:29:42.321  INFO 31875 --- [ctor-http-nio-2] c.f.s.webclient.WebClientExample         : Start WebClient
2023-09-17 14:29:42.388  INFO 31875 --- [ctor-http-nio-2] c.f.s.webclient.WebClientExample         : Finish WebClient
2023-09-17 14:29:42.964  INFO 31875 --- [ctor-http-nio-2] c.f.s.webclient.WebClientExample         : result: Book(id=1, name=Kotlin in Action, price=30000)
2023-09-17 14:29:42.968  INFO 31875 --- [ctor-http-nio-2] c.f.s.webclient.WebClientExample         : result: Book(id=2, name=Perfect guide for Http, price=40000)

R2DBC

JDBC

스프링에서 데이터에 접근하는 방법 중 전통적인 방식으로는 JDBC (Java Database Connectivity) 가 존재한다. 드라이버는 하나의 커넥션에 하나의 스레드를 사용하는 Thread per Connection 방식으로 DB 로부터 응답을 받기 전까지 스레드를 블로킹하는 단점이 있었는데, 대규모 애플리케이션에서는 이점은 치명적이었다. 따라서 점차 지원이 종료되었고, R2DBC 가 등장하였다.

R2DBC

R2DBC(Reactive Relational Database Connectivity) 는 리액티브 기반의 비동기-논블로킹 데이터베이스 드라이버로, 다양한 데이터베이스를 지원한다. Oracle, Postgres, H2, MSSQL … 또한 리액티브 스트림의 구현체인 Project Reactor, RxJava 등을 지원한다.

스프링 데이터 R2DBC

R2DBC 기반의 스프링 데이터 프로젝트로, 스프링 애플리케이션에 쉽게 통합할 수 있으며, 스프링 데이터 JPA, 스프링 데이터 몽고DB 처럼 뛰어난 추상화를 제공한다.

스프링 WebFlux 와 스프링 데이터 R2DBC 를 함께 사용하면 전 구간의 비동기-논블로킹 애플리케이션을 구현할 수 있다.

LazyLoading, Dirty-Checking, Cache 등은 지원하지 않는다.

Coroutine

코루틴은 코틀린에서 비동기-논블로킹 프로그래밍을 명령형 스타일로 작성할 수 있게 도와주는 라이브러리로, 멀티플랫폼 지원으로 여러 환경에서 사용할 수 있다. suspend function 을 통해 스레드가 실행을 다시 중단했다가 그 시점부터 다시 재개할 수 있는 특징이 있다.

아래의 두가지 dependency 를 추가해 사용할 수 있다.

dependencies {
	implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${version}")
	implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:${version}")
}

리액티브 코루틴으로 변환하기

//Mono → suspend
fun handler(): Mono<Void> -> suspend fun handler()

//Flux → Flow
fun handler(): Flux<T> -> fun handler(): Flow<T>

위와 같이 Mono 는 suspend 로, Flux 는 Flow 로 변환해 사용할 수 있다.

@RestController
class UserController(
    private val userService : UserService,
    private val userDetailService: UserDetailService
) {
    @GetMapping("/{id}")
    suspend fun get(@PathVariable id: Long) : User {
        return userService.getById(id)
    }
    @GetMapping("/users")
    suspend fun gets() = withContext(Dispatchers.IO) {
        val usersDeffered = async { userService.gets() }
        val userDetailsDeffered = async { userDetailService.gets() }
        return UserList(usersDeffered.await(), userDetailsDeffered.await())
    }
}
  • 컨트롤러에서는 getmapping 어노테이션들을 지닌 메서드들은 suspend 함수로 변경하고, 각 서비스의 호출은 async 와 await 으로 대응한 것을 볼 수 있다.
val client = WebClient.create("<https://example.org>")
val result = client.get()
    .uri("/persons/{id}", id)
    .retrieve()
    .awaitBody()
  • WebClient 의 경우 await… 의 메서드로 결과를 기다렸다가 반환하도록 한다.
반응형

'Spring' 카테고리의 다른 글

[Spring] 스프링 WebFlux 이해하기  (0) 2023.09.09
[Spring] 리액티브 프로그래밍 기초 (with Kotlin)  (1) 2023.09.02
Comments