RestClient와 coroutine 경계
최신 안정판 Bluetape4k 1.11.0 릴리스 기준
RestClient는 blocking client다
섹션 제목: “RestClient는 blocking client다”RestClient coroutine DSL은 Spring의 blocking HTTP client를 runInterruptible(Dispatchers.IO)에서 실행합니다. 호출하는 코드는 suspend 함수가 되지만 I/O 구현이 reactive로 바뀌지는 않습니다.
val client = restClientOf("https://api.example.com") { defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer $token")}
suspend fun loadUser(id: Long): User = client.suspendGet("/users/$id", MediaType.APPLICATION_JSON)WebFlux event-loop thread에서 직접 RestClient.retrieve().body()를 호출하는 것보다 안전하지만, 높은 동시성에서 blocking connection pool과 Dispatchers.IO capacity를 함께 봐야 합니다.
두 API 층을 구분한다
섹션 제목: “두 API 층을 구분한다”httpGet, httpPost, httpPut, httpPatch, httpDelete, httpHead, httpOptions는 RestClient.ResponseSpec을 반환합니다. 호출자가 status handler와 body decoding을 이어서 구성할 수 있습니다.
val response = client.httpGet("/users/1", MediaType.APPLICATION_JSON)val user = response.body<User>()suspendGet, suspendPost, suspendPut, suspendPatch, suspendDelete는 요청 실행과 body decoding까지 한 번에 수행합니다. 세밀한 status mapping이 필요하면 ResponseSpec 층을 사용하거나 RestClient builder에 공통 status handler를 등록합니다.
빈 body 계약을 고른다
섹션 제목: “빈 body 계약을 고른다”Non-null 함수는 body(T::class.java)!!를 사용합니다. HTTP 2xx라도 body가 없으면 실패합니다.
val required: User = client.suspendGet("/users/1")val optional: User? = client.suspendGetOrNull("/users/1")404를 null로 바꾸는 기능은 아닙니다. OrNull은 성공 응답의 body가 없을 때만 null을 허용하며, retrieve()가 만든 HTTP status 예외는 그대로 전파됩니다.
cancellation은 interrupt 신호다
섹션 제목: “cancellation은 interrupt 신호다”runInterruptible은 coroutine 취소 시 실행 thread를 interrupt합니다. 1.11.0 테스트는 blocking request factory가 InterruptedException을 처리할 때 실제로 interrupt가 전달되는지 검증합니다.
요청 factory가 interrupt를 무시하거나 native I/O가 다른 중단 규칙을 사용하면 즉시 끊긴다고 보장할 수 없습니다. connect/read timeout과 connection pool 제한은 여전히 client configuration에 둬야 합니다.
converter와 직렬화는 애플리케이션 소유다
섹션 제목: “converter와 직렬화는 애플리케이션 소유다”suspendPost는 contentType, accept, 선택적 body를 설정하고 Spring RestClient converter로 직렬화합니다. 이 모듈은 ObjectMapper, converter, authentication, retry를 설치하지 않습니다.
Spring Boot 4와 Jackson 3을 쓴다면 애플리케이션 starter와 JSON 설정이 runtime에 있어야 합니다. 외부 API DTO와 domain model을 분리하고, decoding 오류를 빈 결과로 바꾸지 않습니다.
Source와 tests
섹션 제목: “Source와 tests”RestClientBuilderDsl.ktRestClientExtensions.ktRestClientCoroutinesDsl.ktRestClientExtensionsTest.ktRestClientCoroutinesDslTest.kt
다음 읽을 장
섹션 제목: “다음 읽을 장”WebFlux 요청 컨텍스트와 DataBuffer에서 reactive server의 요청과 buffer 수명주기를 다룹니다.