๊ด€๋ฆฌ ๋ฉ”๋‰ด

๐‘†๐‘ข๐‘›๐‘ โ„Ž๐‘–๐‘›๐‘’ ๐‘Ž๐‘“๐‘ก๐‘’๐‘Ÿ ๐‘Ÿ๐‘Ž๐‘–๐‘›โœง

[Spring] RestTemplate ๋ณธ๋ฌธ

RestTemplate๋ž€?

  • Spring์—์„œ ์ง€์›ํ•˜๋Š” ๊ฐ์ฒด๋กœ ๊ฐ„ํŽธํ•˜๊ฒŒ Rest ๋ฐฉ์‹ API๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” Spring ๋‚ด์žฅ ํด๋ž˜์Šค
  • Spring 3.0๋ถ€ํ„ฐ ์ง€์›๋˜์—ˆ๊ณ , json, xml ์‘๋‹ต์„ ๋ชจ๋‘ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.
  • Rest API ์„œ๋น„์Šค๋ฅผ ์š”์ฒญ ํ›„ ์‘๋‹ต ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋˜์–ด์žˆ์œผ๋ฉฐ HTTP ํ”„๋กœํ† ์ฝœ์˜ ๋ฉ”์†Œ๋“œ(ex. GET, POST, DELETE, PUT)๋“ค์— ์ ํ•ฉํ•œ ์—ฌ๋Ÿฌ ๋ฉ”์†Œ๋“œ๋“ค์„ ์ œ๊ณตํ•œ๋‹ค.

 

 

RestTemplate์˜ ํŠน์ง•

 

  • Spring 3.0 ๋ถ€ํ„ฐ ์ง€์›ํ•˜๋Š” Spring์˜ HTTP ํ†ต์‹  ํ…œํ”Œ๋ฆฟ
  •  HTTP ์š”์ฒญ ํ›„ JSON, XML, String ๊ณผ ๊ฐ™์€ ์‘๋‹ต์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ํ…œํ”Œ๋ฆฟ
  •  Blocking I/O ๊ธฐ๋ฐ˜์˜ ๋™๊ธฐ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋Š” ํ…œํ”Œ๋ฆฟ
  •  RESTful ํ˜•์‹์— ๋งž์ถ”์–ด์ง„ ํ…œํ”Œ๋ฆฟ
  •  Header, Content-Tpye๋“ฑ์„ ์„ค์ •ํ•˜์—ฌ ์™ธ๋ถ€ API ํ˜ธ์ถœ
  •  Server to Server ํ†ต์‹ ์— ์‚ฌ์šฉ

 

 

API ํ˜ธ์ถœ ํด๋ž˜์Šค ์ข…๋ฅ˜

 

  • RestTemplate
    • Spring 3๋ถ€ํ„ฐ ์ง€์› ๋˜์—ˆ๊ณ  REST API ํ˜ธ์ถœ์ดํ›„ ์‘๋‹ต์„ ๋ฐ›์„ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™๊ธฐ๋ฐฉ์‹
  • WebClient
    • Spring 5์— ์ถ”๊ฐ€๋œ ๋…ผ๋ธ”๋Ÿญ, ๋ฆฌ์—‘ํ‹ฐ๋ธŒ ์›น ํด๋ฆฌ์ด์–ธํŠธ๋กœ ๋™๊ธฐ, ๋น„๋™๊ธฐ ๋ฐฉ์‹์„ ์ง€์›ํ•œ๋‹ค.
  • RestTempalte ๊ณผ WebClient์˜ ์ฐจ์ด
    • Non-Blocking๊ณผ ๋น„๋™๊ธฐํ™” ๊ฐ€๋Šฅ ์—ฌ๋ถ€
    • ์Šคํ”„๋ง 4.0์—์„œ RestTemplate์˜ ๋น„๋™๊ธฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด AsyncRestTemplate์„ ์ž ๊น ์‚ฌ์šฉํ•˜๊ธด ํ–ˆ์ง€๋งŒ, ํ˜„์žฌ deprecated ๋œ ์ƒํƒœ์ด๋ฏ€๋กœ WebClient์˜ ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•œ๋‹ค.
    • Non-Blocking?
      ์‹œ์Šคํ…œ์„ ํ˜ธ์ถœํ•œ ์งํ›„์— ํ”„๋กœ๊ทธ๋žจ์œผ๋กœ ์ œ์–ด๊ฐ€ ๋‹ค์‹œ ๋Œ์•„์™€์„œ ์‹œ์Šคํ…œ ํ˜ธ์ถœ์˜ ์ข…๋ฃŒ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋‹ค์Œ ๋™์ž‘์„ ์ง„ํ–‰ํ•œ๋‹ค. ํ˜ธ์ถœํ•œ ์‹œ์Šคํ…œ์˜ ๋™์ž‘์„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋™์‹œ์— ๋‹ค๋ฅธ ์ž‘์—…์„ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ž‘์—…์˜ ์†๋„๊ฐ€ ๋นจ๋ผ์ง„๋‹ค๋Š” ์žฅ์ ์ด ์žˆ๋‹ค.

 

 

 

์˜์กด์„ฑ ์„ค์ •

 

๊ธฐ๋ณธ ์Šคํ”„๋ง ๋ถ€ํŠธ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋ฉด RestTemplate ๊ด€๋ จ ์˜์กด์„ฑ์€ ์ž๋™์œผ๋กœ ์ถ”๊ฐ€๋œ๋‹ค

implementation 'org.springframework.boot:spring-boot-starter-web'

 

 

RestTemplate ๋ฉ”์„œ๋“œ

 

 

 

์š”์ฒญ URI ์„ค์ • ๋ฐฉ๋ฒ•

 

  • String ๋ณ€์ˆ˜ ์‚ฌ์šฉ,  URI ๊ฐ์ฒด ์‚ฌ์šฉ, UriComponents ๊ฐ์ฒด ์‚ฌ์šฉ

 

package com.example.dockerphamarcyproject.api.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

@Slf4j
@Service
public class KakaoUriBuilderService {//uri๋ฅผ ๋งŒ๋“œ๋Š” ์„œ๋น„์Šค

    private static final String KAKAO_LOCAL_SEARCH_ADDRESS_URL = "https://dapi.kakao.com/v2/local/search/address.json";

    private static final String KAKAO_LOCAL_CATEGORY_SEARCH_URL = "https://dapi.kakao.com/v2/local/search/category.json";

    public URI buildUriByAddressSearch(String address) {
        //URI๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” Components๋“ค์„ ํšจ๊ณผ์ ์œผ๋กœ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ํด๋ž˜์Šค
        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(KAKAO_LOCAL_SEARCH_ADDRESS_URL);
        uriBuilder.queryParam("query", address);

        URI uri = uriBuilder.build().encode().toUri();
        log.info("[KakaoAddressSearchService buildUriByAddressSearch] address: {}, uri: {}", address, uri);

        return uri;
    }
}

 

 

 

RestTemplate ์‚ฌ์šฉ ์ˆœ์„œ

 

  1. ๊ฒฐ๊ณผ๊ฐ’์„ ๋‹ด์„ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  2. ํƒ€์ž„์•„์›ƒ ์„ค์ •์‹œ HttpComponentsClientHttpRequestFactory ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  3. RestTemplate ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  4. header ์„ค์ •์„ ์œ„ํ•ด HttpHeader ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•œ ํ›„ HttpEntity ๊ฐ์ฒด์— ๋„ฃ๋Š”๋‹ค.
  5. ์š”์ฒญ URL์„ ์ •์˜ํ•œ๋‹ค.
  6. exchange() ๋ฉ”์†Œ๋“œ๋กœ api๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.
  7. ์š”์ฒญํ•œ ๊ฒฐ๊ณผ๋ฅผ HashMap์— ์ถ”๊ฐ€ํ•œ๋‹ค.

 

package com.example.dockerphamarcyproject.api.service;

import com.example.dockerphamarcyproject.api.dto.KakaoApiResponseDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.RestTemplate;

import java.net.URI;


@Slf4j
@Service
@RequiredArgsConstructor
public class KakaoAddressSearchService {// ์‹ค์ œ๋กœ api๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์‘๋‹ต๊ฐ’์„ ์ž˜ ๋ฐ›์•„์˜ค๋Š”์ง€ ํ™•์ธ

    private final RestTemplate restTemplate; // ์˜์กด์„ฑ ์ฃผ์ž…
    private final KakaoUriBuilderService kakaoUriBuilderService;

    @Value("${kakao.rest.api.key}")
    private String kakaoRestApiKey;

    @Retryable(
            value = {RuntimeException.class},
            maxAttempts = 2,
            backoff =  @Backoff(delay = 2000)
    )
    public KakaoApiResponseDto requestAddressSearch(String address) {
        //๊ณ ๊ฐ์ด ๋ฌธ์ž ๊ธฐ๋ฐ˜ ์ฃผ์†Œ๋กœ ์š”์ฒญํ•˜๊ฒŒ ๋˜๋ฉด ์ด ๋ฉ”์„œ๋“œ๊ฐ€ ์œ„์น˜ ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ(KakaoApiResponseDto)๋กœ ๋ฐ˜ํ™˜ํ•จ

        if(ObjectUtils.isEmpty(address)) return null;

        URI uri = kakaoUriBuilderService.buildUriByAddressSearch(address); //์‹ค์ œ ์นด์นด์˜ค uri๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.AUTHORIZATION, "KakaoAK " + kakaoRestApiKey);

        // HttpEntity = HTTP์š”์ฒญ๋˜๋Š” ์‘๋‹ต์— ํ•ด๋‹นํ•˜๋Š” HttpHeader์™€ HttpBody๋ฅผ ํฌํ•จํ•˜๋Š” ํด๋ž˜์Šค
        // HttpEntity ํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›์•„ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค๊ฐ€ RequestEntity, ResponseEntity ํด๋ž˜์Šค์ด๋‹ค.
        HttpEntity httpEntity = new HttpEntity<>(headers);

    // restTemplate.exchange(): ์ง€์ •๋œ HTTP ๋ฉ”์„œ๋“œ๋ฅผ URL์— ๋Œ€ํ•ด ์‹คํ–‰ํ•˜๋ฉฐ, Responsebody์™€ ์—ฐ๊ฒฐ๋˜๋Š” ๊ฐ์ฒด๋ฅผ ํฌํ•จํ•œ responseEntity๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
        return restTemplate.exchange(uri, HttpMethod.GET, httpEntity, KakaoApiResponseDto.class).getBody();//body๋งŒ ํ•„์š”ํ•ด์„œ body return
        // exchange(uri, ๋ฉ”์„œ๋“œ ๋ฐฉ์‹, httpEntity, ์ „๋‹ฌ๋ฐ›์€ ํด๋ž˜์Šค)
    }

    //์žฌ์ฒ˜๋ฆฌ๋ฅผ ๋ชจ๋‘ ๋‹ค ์‹คํŒจํ–ˆ์„ ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ๋ฐฉ๋ฒ• ์ •์˜
    //์ฃผ์˜ํ•ด์•ผํ• ์ ์€ ์›๋ž˜ ๋ฉ”์„œ๋“œ์˜ return ํƒ€์ž…์„ ๋งž์ถฐ์ค˜์•ผํ•œ๋‹ค.
    @Recover
    public KakaoApiResponseDto recover(RuntimeException e, String address){
        log.error("All the retries failed. address : {}, error : {}", address, e.getMessage());
        return null;
    }

}