개인 프로젝트를 수행하던 도중 Kakao에 위경도를 전달하여 주소로 변환해야 하는 과정이 필요했다.
맨 처음에는 RestTemplate를 생각했지만, deprecated 되었다고 한다.
따라서 WebFlux의 WebClient 사용을 권장하고 있지만, 사실 우리의 애플리케이션에서는 비동기 처리가 필요 없다 생각되었다.
https://stackoverflow.com/questions/47974757/webclient-vs-resttemplate
따라서 이에 대한 대용으로 사용되는 Netfilx의 FeignClient를 우리의 프로젝트에 적용시켜볼까 한다.
1. Feign Client란?
Feign Client란 Netflix에서 개발한 Http Client다.
(HttpClient는 Http 요청을 간편하게 만들어서 보낼 수 있도록 돕는 객체라고 생각하면 될 것 같다.)
처음에는 Netflix에서 자체적으로 개발을 진행했지만 현재는 오픈소스로 전환했으며 SpringCloud 프레임워크의 프로젝트 중 하나로 들어가 있다.
1 - 1) 장점
- SpringMvc에서 제공되는 어노테이션을 그대로 사용할 수 있다.(Spring Cloud의 starter-openfeign을 사용할 경우)
- RestTemplate 보다 간편하게 사용할 수 있으며 가독성이 좋다.
- Feign Client를 사용한 통합 테스트가 비교적 간편하다. (테스트가 편하다는 점은 진짜 장점인 것 같다)
- 요청에 대한 커스텀이 간편하다.
- ex) 요청이 실패했을 때 몇 초 간격으로 몇 번 재요청을 보낼 것인지를 구체적으로 정할 수 있다.
1 - 2) 단점
- 동기적으로 동작한다. 즉, 하나의 요청이 끝나야 다음 동작이 가능하다.
하지만 우리 술술 정도의 애플리케이션은 사실상 동기 처리로 충분하다 생각되었다.
참고해볼만한 우아한 형제들의 코드
https://github.com/woowabros/feign-apply-experience-sample
2. 의존성 추가
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:3.1.3'
이거 말고 진자 딱 FeignClient만 의존성을 추가하는 방식도 있기는 한데, 보통 starter들이 더 간편하니...
이걸 사용하도록 하자 ㅎㅎ
3. Main에 애너테이션 추가해주기
@EnableFeignClients // 추가된 부분
@SpringBootApplication
public class SoolsulServerApplication {
public static void main(String[]args) {
SpringApplication.run(SoolsulServerApplication.class,args);
}
}
메인 애플리케이션 위에 @EnableFeignClients를 추가해주면 된다!
위 애너테이션에서 처리하는 방식을 다음과 같이 수동으로 할 수도 있다고 한다. (슬쩍 살펴만 보자 ㅎㅎ)
7. Declarative REST Client: Feign
4. API 호출을 담당할 클라이언트 인터페이스를 만들기
클라이언트를 인터페이스로 만들고 내부에 호출할 메서드만 작성하면 된다. 생각보다 간편하다.
인터페이스만 작성해주면 된다는 점에서 편리한 것 같다. 또한 기존의 Spring의 에너테이션들을 사용할 수 있다.
4 - 1) 예시 코드
// name: FeignClient의 이름을 설정한다.
// url="${...}": yml에 설정되어 있는 URL 정보를 불러온다.
// (@FeignClient 어노테이션 내 url로 설정한 URL로 API를 쏜다.)
@FeignClient(name="test-api", url="${external-api.test.api-url}")
public interface TestFeignClient {
// headers: Request Header에 포함 할 정보를 추가 (yml 에서 불러옴)
@PostMapping(
value = "/rest/test",
headers = "Authorization=${external-api.test.authorization}"
)
xxxResponse test(final xxxRequest request);
// Request DTO 와 Response DTO는 송/수신 형식 및 타입에 맞게 설정한다.
}
- @FeignClient : 앱이 런타임 시 해당 어노테이션이 붙은 인터페이스를 토대로 실제 구현체를 만든다.
- name : 실제 구현체가 Application Context에 빈으로 등록될 때 이름으로 사용된다.
- url : 요청을 보낼 엔드포인트를 의미한다.
- @PostMapping : 해당 HttpMethod로 요청을 전송한다.
- @RequestParam : 요청 시 함께 보낼 파라미터들 설정한다.
- 메서드의 파라미터에 @RequestParam, @RequestHeader 등의 어노테이션을 사용하지 않으면 기본적으로 요청의 Body에 파라미터의 값들이 들어간다.
applicaion.yml
# Feign Client Settings
feign:
client:
config:
test-api: # Feign Client 명
decode404:falseloggerLevel: full
connect-timeout: 3000
readTimeout: 60000
external-api:
test:
api-url: '<https://xxx.yyy.com>'
authorization: 'ABC'
Request Header Authorization:ABC
POST : https://xxx.yyy.com/rest/test 로 API가 요청된다.
4 - 2) Feign을 통해 GET Request 보내기
이번에는 예시 코드가 아니라, 실제 코드로 적용시켜 보자. 우선 주소를 찾는 ThirdParty로 Kakao의 API를 사용할 것이다.
https://developers.kakao.com/docs/latest/ko/local/dev-guide#coord-to-address
좌표(위도, 경도)를 기반으로 주소로 변환하는데 필요한 문서가 잘 설명되어 있다.
이를 사용하여 요청을 보내보면 다음과 같이 위경도를 통해 주소를 반환받을 수 있다.
쿼리 파라미터를 통해 x, y로 logitude, latitude를 전달하고 있다.
postman으로 응답을 확인해 봤으니, 실제 사용할 Client를 만들어보자.
우선 다음과 같이 interface를 하나 정의해주자.
@FeignClient(name = "local-address-client", url = "${api.kakao.api-url}",
configuration = {KakaoClientAuthHeaderConfiguration.class})
public interface KakaoAddressSearchClient {
@GetMapping("/v2/local/geo/coord2address.json")
KakaoAddressResponse convertAddress(@RequestParam("x") double logitude, @RequestParam("y") double latitude);
}
사실상 끝이다 ㅎㅎ, 복잡한 부분을 사실 KakaoAddressResponse라는 Dto를 통해 어떤 필드를 받아둘지 결정하는 부분이 더 어려웠다.
또한 Dto에서 역직렬화를 위해 필드와 json의 이름을 잘 확인하면서 작업해야 null값이 들어오는 일을 방지할 수 있었다.
이후 서비스의 로직에서는 만든 클라이언트 객체를 빈으로 주입받아 사용했다.
@Service
@RequiredArgsConstructor
public class KakaoPlaceApiService {
private final KakaoAddressSearchClient addressSearchClient;
public AddressConvertResponse convertAddress(double longitude, double latitude) {
KakaoAddressResponse convertResponse = addressSearchClient.convertAddress(longitude, latitude);
return new AddressConvertResponse(buildAddressResponseList(convertResponse));
}
// 일부 생략...
}
5. SpringMvc의 어노테이션을 사용할 수 있는 이유
찾아보니 FeignClient는 빈으로 생성될 때 설정된 configuration을 읽어서 생성되는데 configuration 내부에는 Client 생성 시 사용할 Decoder, Encoder, Logger, Contract 등을 빈으로 등록하는 코드가 담겨있었다.
이때 Client에 따로 Configuration 설정을 해주지 않으면 디폴트인 FeignClientsConfiguration를 사용해서 생성하는데 default로 적용된 Contract는 SpringMvcContract였고 덕분에 SpringMvc의 어노테이션을 사용할 수 있었다.
FeignClientsConfiguration
- decoder, encoder, logger도 모두 Spring이 사용하는 객체들을 사용하도록 되어있다.
참고
https://kafcamus.tistory.com/54
'BackEnd > Spring' 카테고리의 다른 글
[Spring] Spring Security에서 @WithMockUser를 커스텀하기 (0) | 2023.01.02 |
---|---|
[Spring] SpringSecurity에서 @AuthenticationPrincipal 대신 DTO로 받기 (0) | 2023.01.02 |
[Spring] @Configuration 이란? (0) | 2022.09.12 |
[Spring] @Profile과 @ActiveProfiles 를 통한 활성 프로파일(Profile)의 관리 (0) | 2022.08.23 |
[Spring] 스프링 AOP - 실전 예제 (0) | 2022.08.20 |
댓글