본문 바로가기
320x100
320x100

Node.js 환경에서는 fetch, ajax, axios를 활용해서 비동기로 특정 api의 경로를 호출할 수 있다

back: express, nest / front: react, vue, angular 등에서 필요한 의존성을 추가하면 사용할 수 있다

 

Spring에서는 전통적으로 RestTemplate를 사용했고(동기 호출만 지원), Reactive한 처리 또는 비동기 처리가 필요한 경우 WebClient를 사용했다

RestTemplate는 Spring 3.x.x 버전부터 도입되었고(스프링 부트 아님), spring-boot-starter-web 패키지에 포함되어 있었다

WebClient는 Spring 5.x.x(다시 한번, 스프링 부트 아님), spring-boot-starter-web 패키지가 아닌 spring-boot-starter-webflux 패키지 의존성을 추가하면 사용할 수 있었다

아래는 의존성을 추가하고 docs를 다운받아서 열어본 내용이다

RestTemplate / WebClient

Since 3.0 / Since 5.0

이 두 개의 비교를 위한 글이 아니므로 자세한 내용은 패쓰한다

 

Spring boot 3.x.x

Spring boot는 3.x.x(3점대)에서 많은 변화가 있었다. 내부적인 SpringFramework버전으로는 6.x.x를 사용하도록 변경됐다

spring boot 3.3.2
6.x.x

 

Spring 6점대에서는 어떤 [구현체 또는 인터페이스]가 추가되었을까?

Rest Clients

내용을 살펴보면 RestClient, WebClient, RestTemplate은 구현체인 모양새고, HTTP interface는 어노테이션 기반의 인터페이스이고 동적 프록시 구현체가 사용된다고 나와있다

 

먼저 여기서 살펴볼 만한 부분이 있다. 어느 테크 블로그 또는 어딘가에서 RestTemplate은 will be deprecated될 것이니까 사용하면 안된다고 보거나 들어봤을 것이다. 이는 틀린 사실이다!!!!

오해 전파 멈춰!!!!!!

멈춰

RestTemplate은 유지보수 상태로 들어가는거지, 삭제되는 것은 아니다. 그 유명한 자바/스프링에서 하위버전까지 호환하기 위해 어떤 노력들을 기울이는데... 이것을 바로 삭제할리는 없다

자세한 내용은 토비님의 영상을 참고해봐도 좋다

https://www.youtube.com/watch?v=S4W3cJOuLrU

토비님의 영상

먼저 RestTemplate에 대하여 짧게 추가정보를 제공하고 본 글을 시작하려 한다

예전에 영한님 강의를 듣기위해 2021년에 만들었던.. 지금은 스프링 이니셜라이저나 IntelliJ의 new project에서 선택할 수 없는 spring boot 2.4.4버전과 최근에 코프링으로 잠깐 작업했던 2024년의 개인프로젝트 spring boot 3.3.2버전을 비교해봤다

 

Spring boot 2.x.x(Spring 5) RestTemplate 설명

boot 2.4.4 버전
spring boot 2점대(spring 5)의 RestTemplate docs

 

Spring boot 3.x.x(Spring 6) RestTemplate 설명

boot 3.3.2 버전
spring boot 3점대(spring 6)의 RestTemplate docs

설명이 풍부해졌다ㅋ. 왜냐하면 6.1버전에 RestClient가 추가되었기 때문이다

요기 이 부분을 해석해보자

NOTE: As of 6.1, RestClient offers a more modern API for synchronous HTTP access. For asynchronous and streaming scenarios, consider the reactive org. springframework. web. reactive. function. client. WebClient. RestTemplate and RestClient share the same infrastructure (i. e. request factories, request interceptors and initializers, message converters, etc.), so any improvements made therein are shared as well. However, RestClient is the focus for new higher-level features.

6.1버전부터 RestClient는 더 현대적인 동기식 HTTP 접근 API를 제공한다고 나와있다. 만약 비동기와 스트리밍 시나리오를 처리하기 위해서는 reactive를 고려해봐라(WebClient)

RestTemplate과 RestClient는 같은 인프라구조를 공유한다(...중략). 하지만, RestClient가 좀 더 고수준의 기능에 집중되어있다고 한다

결국 RestClient가 RestTemplate처럼 RestXXX로 네이밍을 준 이유는 RestTemplate의 뼈대에서 RestClient가 나왔기 때문이다

새로운 것을 도입하거나 알아볼때는 몇몇 프로젝트에서 실제 구버전vs신버전 라이브러리의 내부 구현체나 인터페이스로 가서 java docs를 잘 살펴보기도 하고 버전별 공식문서를 보는 습관을 들이자

 

그럼 이제 무엇을 해볼거냐...하면 RestClient, WebClient, RestTemplate을 사용해보고 추가적으로 잘 사용되는 기타 라이브러리를 알아볼 것이다

이 과정에서 worldtimeapi를 제공하는 엔드포인트로 호출을 해볼 것이다

worldtimeapi

요런 사이트인데.. 

http://worldtimeapi.org/api/timezone/Asia/Seoul

이와 같은 주소로 호출을 하면

아시아/서울 api 호출 결과

위와 같은 결과를 얻을 수 있다

먼저 터미널에서 curl 명령어와 jq를 통해 json을 이쁘게 출력해보고, 이것을 코드로 구현해볼 예정이다

Asia/SeoulAmerica/New_York을 호출해봤다

curl -sX GET http://worldtimeapi.org/api/timezone/Asia/Seoul | jq .

curl -sX GET http://worldtimeapi.org/api/timezone/America/New_York | jq .

curl 명령 호출 결과

 

먼가 한글 해석이 마음에 들지 않지만......명령줄 == CLI를 통해 호출을 해봤으니, 코드로 구현해보자!

근데 아래에서 요청할 해외시간은 New_York이 아니라 Asia/Tokyo를 호출해봤다

언어는 Kotlin, 프레임워크는 Spring boot를 사용했다

먼저 공통적으로 사용할 dto를 만들었다

공통 response

import com.fasterxml.jackson.annotation.JsonProperty

data class WorldTimeCustomResponse(
    @JsonProperty("utc_offset") val utcOffset: String,
    @JsonProperty("timezone") val timezone: String,
    @JsonProperty("datetime") val datetime: String,
    @JsonProperty("utc_datetime") val utcDatetime: String,
    @JsonProperty("unixtime") val unixTime: Long,
    @JsonProperty("abbreviation") val abbreviation: String,
    @JsonProperty("client_ip") val clientIp: String
) {
    constructor() : this("", "", "", "", 0L, "", "")
}

테스트용 기본 생성자도 추가해봤다

 

# RestTemplate

RestTemplate과 RestClient를 사용하기 위해서는 web 패키지를 추가해야 한다

implementation("org.springframework.boot:spring-boot-starter-web")

RestTemplate로 world time api 호출

@Configuration
class RestTemplateConfig {
    @Bean
    fun restTemplate(): RestTemplate {
        return RestTemplate()
    }
}

@Component
class WorldTimeRestTemplateProxy(
    private val restTemplate: RestTemplate,
) {
    private val baseUrl = "http://worldtimeapi.org/api/timezone"

    fun getFullWorldTime(timezone: String): WorldTimeCustomResponse {
        val url = UriComponentsBuilder.fromHttpUrl("$baseUrl/$timezone").toUriString()
        return restTemplate.getForObject(url, WorldTimeCustomResponse::class.java)
            ?: throw RuntimeException("Failed to fetch WorldTime")
    }

    fun getDateTimeOnly(timezone: String): String {
        val url = UriComponentsBuilder.fromHttpUrl("$baseUrl/$timezone").toUriString()
        val response= restTemplate.getForObject(url, Map::class.java)
            ?: throw RuntimeException("Failed to fetch WorldTime")
        return response["datetime"] as String
    }
}

@RestController
@RequestMapping("/api/rest-template/time")
class WorldTimeRestTemplateController(
    private val restTemplateProxy: WorldTimeRestTemplateProxy
) {
    private val log = LoggerFactory.getLogger(WorldTimeRestTemplateController::class.java)

    @GetMapping("/full/{*timezone}")
    fun getFullWorldTime(@PathVariable timezone: String): WorldTimeCustomResponse {
        val apiResponse = restTemplateProxy.getFullWorldTime(timezone.drop(1))
        log.info("response: $apiResponse")
        return apiResponse
    }

    @GetMapping("/datetime/{*timezone}")
    fun getDateTimeOnly(@PathVariable timezone: String): String {
        val apiResponse = restTemplateProxy.getDateTimeOnly(timezone.drop(1))
        log.info("datetime: $apiResponse")
        return apiResponse
    }
}

import 문은 제외했다

RestTemplate을 사용하기 위한 Bean 설정, 내부 구현체를 사용할 Proxy 클래스, 실제 api를 호출해볼 RestController를 만들었다

사실 가장 중요한 부분은 config를 하는 Bean 설정이라고 생각하는데.. timeout이나 retry 횟수, 예외(에러)처리 등을 하는 부분은 일단 이 글에서 자세히 다루지는 않기 때문에 거의 비어있는 구현체로 사용했다

api는 총 2개를 만들었다

1. Timezone/Area로 받아서 그대로 넘기기. 내부적으로 이 과정을 처리하기 위해 {*timezone}을 사용했다

2. datetime 필드만을 추출해서 String으로 리턴하기

 

1번 테스트 시작

Spring 서버로 GET /api/rest-template/time/full/Asia/Seoul 요청
=> 
내부적으로 GET http://worldtimeapi.org/api/timezone/Asia/Seoul 호출
<=
클라이언트에게 응답

rest-template 요청&응답
스프링 서버 로그

Controller의 DispatcherServlet이 먼저 받아서 RequestMapping 관련 Bean인 RequestMappingHandlerMapping에게 넘겨서 경로에 맞는 요청을 처리한다

내부적으로는 RestTemplate이 호출을 하고 응답을 만들어서 컨트롤러로 전달했다는 로그를 볼 수 있었다

 

2번 테스트 시작

Spring 서버로 GET /api/rest-template/time/datetime/Asia/Tokyo 요청
=> 
내부적으로 GET http://worldtimeapi.org/api/timezone/Asia/Tokyo 호출
<=
클라이언트에게 응답

rest-template 요청&응답
스프링 서버 로그

두 개 다 호출을 성공한 모습을 볼 수 있다! 이제 RestClient를 살펴보자

 

# RestClient

RestClient

@Configuration
class RestClientConfig {
    @Bean
    fun restClient(): RestClient {
        return RestClient.create()
    }
}

@Component
class WorldTimeRestClientProxy(
    private val restClient: RestClient,
) {
    private val baseUrl = "http://worldtimeapi.org/api/timezone"

    fun getFullWorldTime(timezone: String): WorldTimeCustomResponse {
        return restClient.get()
            .uri("$baseUrl/$timezone")
            .retrieve()
            .body<WorldTimeCustomResponse>()
            ?: throw RuntimeException("Failed to fetch WorldTime")
    }

    fun getDateTimeOnly(timezone: String): String {
        val response = restClient.get()
            .uri("$baseUrl/$timezone")
            .retrieve()
            .body<JsonNode>()

        return response?.get("datetime")?.asText()
            ?: throw RuntimeException("Failed to extract datetime from response")
    }
}

@RestController
@RequestMapping("/api/rest-client/time")
class WorldTimeRestClientController(
    private val restClientProxy: WorldTimeRestClientProxy,
) {
    private val log = LoggerFactory.getLogger(WorldTimeRestClientController::class.java)

    @GetMapping("/full/{*timezone}")
    fun getFullWorldTime(@PathVariable timezone: String): WorldTimeCustomResponse {
        val apiResponse = restClientProxy.getFullWorldTime(timezone)
        log.info("response: $apiResponse")
        return apiResponse
    }

    @GetMapping("/datetime/{*timezone}")
    fun getDateTimeOnly(@PathVariable timezone: String): String {
        val apiResponse = restClientProxy.getDateTimeOnly(timezone)
        log.info("datetime: $apiResponse")
        return apiResponse
    }
}

 

 

proxy쪽 코드가 살짝 다른걸 볼 수 있다. 이런 형태를 선언형 프로그래밍 방식이라고 한다

api는 똑같이 2개를 만들었다

이번에는 스프링으로 호출하는 경로를 /api/rest-template/time에서 /api/rest-client/time으로 변경했다

테스트1
로그
테스트2
로그

 

# WebClient

이번에는 WebClient를 사용해본다. 기본적으로 starter-web 패키지에는 존재하지 않으므로 webflux의존성이 필요하다

implementation("org.springframework.boot:spring-boot-starter-webflux")

WebClient

@Configuration
class WebClientConfig {
    @Bean
    fun webclient(): WebClient {
        return WebClient
            .builder()
            .build()
    }
}

@Component
class WorldTimeWebClientProxy(
    private val webClient: WebClient
) {
    private val baseUrl = "http://worldtimeapi.org/api/timezone"

    fun getWorldTime(timezone: String): Mono<WorldTimeCustomResponse> {
        return webClient.get()
            .uri("$baseUrl/$timezone")
            .retrieve()
            .bodyToMono(WorldTimeCustomResponse::class.java)
    }

    fun getDateTimeOnly(timezone: String): Mono<String> {
        return webClient.get()
            .uri("$baseUrl/$timezone")
            .retrieve()
            .bodyToMono(JsonNode::class.java)
            .map { it["datetime"].asText() }
    }
}

@RestController
@RequestMapping("/api/webclient/time")
class WorldTimeWebClientController(
    private val webclientProxy: WorldTimeWebClientProxy,
) {
    private val log = LoggerFactory.getLogger(WorldTimeWebClientController::class.java)

    @GetMapping("/full/{*timezone}")
    fun getFullWorldTime(@PathVariable timezone: String): Mono<WorldTimeCustomResponse> {
        val apiResponse = webclientProxy.getWorldTime(timezone)
        log.info("response: $apiResponse")
        return apiResponse
    }

    @GetMapping("/datetime/{*timezone}")
    fun getDateTimeOnly(@PathVariable timezone: String): Mono<String> {
        val apiResponse = webclientProxy.getDateTimeOnly(timezone)
        log.info("datetime: $apiResponse")
        return apiResponse
    }
}

코드에서 반환형을 보면 Mono를 볼 수 있는데, WebFlux는 Mono또는 Flux를 반환형으로 사용한다

Mono는 0..1 / Flux는 0..N...

내부적인 core 지식으로는 Reactor가 사용되고 Reactive Stream을 다루기 위한 공부가 필요하다(대부분 API)

RxJava까지 학습하면 좋지만, 비즈니스에서 겪는 문제를 해결하기 위한 목적으로 Reactor API만을 공부하는것도 괜찮은 선택이라고 본다

저자님의 직강
내가 쓴 인프런의 Kevin님 강의 수강후기

관련 강의로 인프런의 Kevin의 알기 쉬운 Spring Reactive Web Applications: Reactor 1/2부 강의를 추천한다

뭐 말이 길었지만... WebClient로도 api 테스트를 해본다

netty나 undertow등으로 서버를 바꾸지 않고, 내장 기본 설정 서버인 tomcat을 사용했다. 하지만 서블릿 3.1부터 비동기요청을 지원하기 때문에 tomcat이 비동기 요청을 처리하고, webflux가 mono나 flux와 같은 리액티브 스트림으로 변환하여 처리를 하기 때문에 개발자는 편하게 라이브러리를 추가하고 사용하면 된다

테스트1
로그

로그에서 중요하게 볼 부분을 빨간 박스로 나타내봤다. 일단 ExchangeFunctions가 WebClient가 작동하는 부분이라고 보면 된다

그리고 WebAsyncManager가 비동기를 처리하고.. 아래쪽을 보면 Exiting from "ASYNC" dispatch 라고 쓰여진 부분을 볼 수 있다

테스트2
로그

 

# HttpInterface

HTTP Interface

RestTemplate, RestClient, WebClient를 사용하는 예제를 살펴봤다.. 그럼 이제 남은건 HTTP Interface를 사용하는 방법인데.. 이건 대체 뭘까!?라고 생각할 수 있다

HTTP Interface

스프링 프레임워크는 @HttpExchange 메서드들을 사용해서 HTTP 관련 서비스를 Java 인터페이스로 정의할 수 있게 만든다고 한다

이러한 인터페이스를 HttpServiceProxyFactory에 전달하여 RestClient/WebClient와 같은 HTTP 클라이언트를 통해 요청을 수행하는 프록시를 생성할 수 있고, 서버 요청을 처리하기 위해 컨트롤러에서 인터페이스를 구현할 수도 있다고 한다

 

언제사용하면 좋을까?

만약 RestTemplate를 사용해서 A서버, B서버로 호출하는 Bean을 두개 만들었다고 하자. 빈 이름or변수명과 메서드명으로만으로 행위를 추측해야 하고, 관심사를 컴포넌트로밖에 묶을 수가 없다. 또한 어느정도의 코딩이 필요로 하는 명령형 방식이다.

하지만 이 HTTP Interface는 최대한 선언형으로 작성할 수 있다. 그리고 프록시가 행위를 대체하기 때문에 다른 빈이 들어와도 동일한 메서드명을 사용할 수 있다(인터페이스). 코드로 살펴보자

HttpInterfaceConfig
인터페이스(@HttpExchange)
어노테이션
알맹이만 다르게 처리할 수 있음

@Configuration
class HttpInterfaceConfig {

    @Bean
    fun restTemplateHttpProxy(restTemplate: RestTemplate): WorldTimeHttpService {
        val adapter = RestTemplateAdapter.create(restTemplate)
        val factory = HttpServiceProxyFactory.builderFor(adapter).build()
        return factory.createClient(WorldTimeHttpService::class.java)
    }

    @Bean
    fun webClientHttpProxy(webclient: WebClient): WorldTimeHttpService {
        val adapter = WebClientAdapter.create(webclient)
        val factory = HttpServiceProxyFactory.builderFor(adapter).build()
        return factory.createClient(WorldTimeHttpService::class.java)
    }

    @Bean
    fun restClientHttpProxy(restClient: RestClient): WorldTimeHttpService {
        val adapter = RestClientAdapter.create(restClient)
        val factory = HttpServiceProxyFactory.builderFor(adapter).build()
        return factory.createClient(WorldTimeHttpService::class.java)
    }

}

@HttpExchange
interface WorldTimeHttpService {
    // 슬래시 인코딩 방지: URI를 받아 직접 처리
    @GetExchange
    fun getFullWorldTimeFromUri(uri: URI): WorldTimeCustomResponse

    @GetExchange
    fun getDateTimeOnlyFromUri(uri: URI): Map<String, Any>

    @GetExchange
    fun getAsyncDateTimeOnlyFromUri(uri: URI): Mono<WorldTimeCustomResponse>

}

@RestController
@RequestMapping("/api/http-interface/time")
class WorldTimeHttpController(
    // restTemplate
    @Qualifier("restTemplateHttpProxy")
    private val httpProxy1: WorldTimeHttpService,
    // webClient
    @Qualifier("webClientHttpProxy")
    private val httpProxy2: WorldTimeHttpService,
    // restClient
    @Qualifier("restClientHttpProxy")
    private val httpProxy3: WorldTimeHttpService,
) {
    private val log = LoggerFactory.getLogger(WorldTimeHttpController::class.java)

    @GetMapping("/full/{*timezone}")
    fun getFullWorldTime(@PathVariable timezone: String): WorldTimeCustomResponse {
//        val apiResponse = httpProxy.getFullWorldTime(timezone) // SLASH(/) 인코딩
        val uri = URI("http://worldtimeapi.org/api/timezone/${timezone.drop(1)}")
        val apiResponse1 = httpProxy1.getFullWorldTimeFromUri(uri)
        log.info("response1: $apiResponse1")
        val apiResponse2 = httpProxy2.getFullWorldTimeFromUri(uri)
        log.info("response2: $apiResponse2")
        val apiResponse3 = httpProxy3.getFullWorldTimeFromUri(uri)
        log.info("response3: $apiResponse3")
        return apiResponse3
    }

    @GetMapping("/full2/{*timezone}")
    fun getFullWorldTime2(@PathVariable timezone: String): Mono<WorldTimeCustomResponse> {
        val uri = URI("http://worldtimeapi.org/api/timezone/${timezone.drop(1)}")
        val apiResponse = httpProxy2.getAsyncDateTimeOnlyFromUri(uri)
        return apiResponse
    }

    @GetMapping("/datetime/{*timezone}")
    fun getDateTimeOnly(@PathVariable timezone: String): String {
        val uri = URI("http://worldtimeapi.org/api/timezone/${timezone.drop(1)}")
        val apiResponse = httpProxy1.getDateTimeOnlyFromUri(uri)
//        val apiResponse = httpProxy2.getDateTimeOnlyFromUri(uri)
//        val apiResponse = httpProxy3.getDateTimeOnlyFromUri(uri)
        val result = apiResponse["datetime"] as String
        log.info("datetime: $result")
        return result
    }
}

기존에 사용중이던 빈을 전달받아서 Adaptor를 만들고 그 어댑터를 프록시팩토리에 전달 후, 클라이언트를 만들어서 인터페이스인 서비스를 반환한다

RestTemplate / WebClient / RestClient를 다시 재작성 할 필요가 없다

Controller와 비슷하게 어노테이션 방식으로 HttpMethod를 처리할 수 있다!

비동기 응답도 처리가 가능하다

api분기는 full요청에 RestTemplate, WebClient, RestClient 3개가 다 작동하도록 하고, 두번째 full2는 비동기응답을, 마지막으로 datetime만을 주는 응답은 3개중 하나에서 응답하도록 햇다

테스트 꼬우

테스트1
로그
테스트2
로그
테스트3
로그

여기서 핵심은 Interface로 대신하는 방법을 택했다는 것이다. 그동안은 클래스, 구현체가 직접 일을 처리했는데, 이 HTTP Interface는 기존에 만들어진 것들을 인터페이스로 추상화하고 스프링 친화적인 방법을 제공한다는 것이다

 

# OpenFeign

여기까지 잘 봤다면... 제목에서 언급한 openFeign을 사용하는 예제도 하나 더 보여주려고 한다

내가 아는 어떤 지인은 보통 openFeign은 MSA에서 쓴다고하는데.. 나는 반은 맞고 반은 틀린 말이라 생각한다

Spring Cloud 관련 프로젝트들이 분산시스템에서 사용되기 좋게 나온것일 뿐 무조건 MSA!! openFeign은 아니라고 생각한다

은행권이나 MSA프로젝트에서 종종 자주 사용되는건 맞는 말이다

 

openFeign을 사용하기 위해서는 cloud하위의 의존성을 추가해야된다

implementation("org.springframework.cloud:spring-cloud-starter-openfeign")

OpenFeign

@Configuration
@EnableFeignClients(basePackageClasses = [WorldTimeOpenFeignController::class])
class FeignConfig

@FeignClient(name = "worldTimeApi", url = "http://worldtimeapi.org/api")
interface WorldTimeOpenFeignProxy {
    @GetMapping("/timezone/{timezone}")
    fun getWorldTime(@PathVariable("timezone") timezone: String): WorldTimeCustomResponse

    @GetMapping("/timezone/{timezone}")
    fun getDateTimeOnly(@PathVariable("timezone") timezone: String): String
}

@RestController
@RequestMapping("/api/openfeign/time")
class WorldTimeOpenFeignController(
    private val openFeignProxy: WorldTimeOpenFeignProxy,
    private val objectMapper: ObjectMapper,
) {
    private val log = LoggerFactory.getLogger(WorldTimeOpenFeignController::class.java)

    @GetMapping("/full/{*timezone}")
    fun getFullWorldTime(@PathVariable timezone: String): WorldTimeCustomResponse {
        val apiResponse = openFeignProxy.getWorldTime(timezone)
        log.info("response: $apiResponse")
        return apiResponse
    }

    @GetMapping("/datetime/{*timezone}")
    fun getDateTimeOnly(@PathVariable timezone: String): String {
        val apiResponse = openFeignProxy.getDateTimeOnly(timezone)
        val jsonNode = objectMapper.readTree(apiResponse)
        val result = jsonNode["datetime"].asText()
        log.info("result: $result")
        return result
    }
}

테스트1

OpenFeign 전체 데이터 받아오기

테스트 2

이번에는 드디어 일본이 아닌 뉴욕시간대를 받아와봤다

로그
테스트2
로그

 

결론

테스트..

정리해보자

- RestTemplate

RestTemplate 설명
RestTemplate 사용

  • since v3.0
  • getForObject, postForObject등 메서드명으로 행위를 구분할 수 있지만 exchange/execute같은 메서드는 유연한 사용을 위한 설계 목적과 별개로 메서드명을 보고 행위를 유추하기 어려운 단점도 있다
  • 기본적으로 new로 인스턴스화하고 setter를 통해 내부 객체들을 변경해 사용하기 때문에 주의가 필요한 오브젝트이다

 

- WebClient

WebClient 설명
WebClient 사용
after retrive

  • since v5.0
  • 선언형 프로그래밍을 위한 Fluent API를 제공하기때문에 좀 더 가독성 있는 프로그래밍이 가능하다
  • 비동기 처리를 지원한다
  • Kotlin의 코루틴에서 사용 가능한 메서드들도 지원한다(awaitXXX - suspend fun)

 

- RestClient

RestClient 설명
RestClient 사용

  • since v6.1
  • WebClient와 유사한(아마 참고를 하지 않았나..) Fluent API(chaining method) 지원
  • RestTemplate의 고도화된 버전

 

- Http Interface

HttpInterface 설명
Http Interface 사용
OpenFeign 설명

위에 Config 설정은 첨부했기때문에 사용 부분만 첨부했다

어노테이션을 통한 함수행동을 메타데이터로 전달하기 때문에 더 가독성이 좋아졌다

하지만 기존에 만들어져 있는 WebClient/RestTemplate/RestClient를 어댑터/프록시 팩토리로 처리하고 인터페이스로 추상화된 행동을 제공하는 방식이다

 

- OpenFeign

OpenFeign 사용
HttpInterface 설명
어노테이션 시그니쳐(어노테이션 파라미터)

에러발생시 404로 대체하는 기능, fallback 메서드.. 등등 Cloud생태계에서 왜 많이 쓰이는지 볼 수 있다

사용처가 완전 다르지만 API Gateway로도 이용할 수 있을것같네?하는 생각이 들었다(사실 그렇지는 않음)

Spring Cloud API Gateway + Resilience4j's fallback()

물론 용도가 다르다면 그에 맞는 도구를 선택해서 사용해야겠지만 말이다!

 

위에서 찾은 라이브러리 이외에도 몇몇 라이브러리가 존재한다. 예를 들어 HttpURLConnection, Apache HttpClient, OKHttp, Retrofit 등이 있다

 

항상 현재 상황에 맞는 해결방법을 선택해서 문제를 풀어나가야 한다!

개인프로젝트를 진행한다면 비동기처리가 필요하지 않다면 RestClient나 OpenFeign을 사용할 것이고, Spring Cloud 환경을 적극 사용중이라면 OpenFeign을 사용할 것 같다

비동기처리가 필요하거나 WebClient를 도입해서 좀 더 다양한 처리를 하고 싶다면 WebClient를 선택할 것이다(WebClient도 동기호출을 지원함. 오해 노우)

 

 

 

참고

https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-restclient

 

REST Clients :: Spring Framework

WebClient is a non-blocking, reactive client to perform HTTP requests. It was introduced in 5.0 and offers an alternative to the RestTemplate, with support for synchronous, asynchronous, and streaming scenarios. WebClient supports the following: Non-blocki

docs.spring.io

 

댓글