- Today
- Total
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- blog
- PR
- KotlinInAction
- 안드로이드
- 진짜학습지
- n3문법
- jlpt
- 진짜학습지후기
- coroutine
- 일본어문법
- Kotlin
- rxjava
- 진짜일본어
- ai
- CustomTab
- 코틀린
- GIT
- 일본어기초
- 책추천
- 학습지
- 인공지능
- webflux
- 책리뷰
- posting
- pullrequest
- github
- errorhandling
- androidstudio
- Android
- suspend
코딩하는 개굴이
[Spring] Webflux 웹 클라이언트, R2DBC, Coroutine 변환 본문
해당 포스팅은 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 |