간단한 서버 간 통신 @FeignClient
인턴 기간 중에 새로운 어플리케이션 서버 구축 업무를 받게 되었어요!! 흠 일단는 어느 정도 설계를 잡아가고 있는 중에 개발하는 어플리케이션의 예상 사용자들이 40대 이상이 가장 많을 것 같았어요..! 40대 이상 어르신 분들에게 간단하게 회원가입 시킬 방법을 구상하던 중에 소셜로그인을 통한 회원가입 기능을 구축해서 사용자들이 거부감 없이 회원가입할 수 있게 유도하는 방안을 건의 드렸고 이 부분은 회의에서 금방 채택되었습니다 :) ~!~!~!
근데 이 소셜로그인이 FeignClient와 무슨 상관이 있을까요..??, 카카오 소셜 로그인을 구현하기 위해서는 카카오 인증 서버, 자원 서버와 통신을 해야하는 부분이 생기는데 저는 이 부분을 RestTemplate이 아닌 Feign Client로 구현해볼 생각입니다! 추후에 어플리케이션 사용자가 많아져 서버를 모놀리식에서 MSA에 부분으로 바꿀 때에도 FeignClient가 더 편하니 이 부분을 염두에 두고 개발하는 것도 나쁘지 않아 FeignClient를 선택했습니다 :)
(RestTemplate는 곧 deprecated가 될예정이기도 합니다 ㅠㅠ, WebClient로 대체될 예정)
자 그럼 FeignClient에 먼저 알아보도록 하겠습니다 :)
목차
1. FeignClient란?
2. Fegng Client 사용하는 이유
2. FeignClient 사용법
@FeignClient란
Fein Client는 Client 라이브러리 입니다. OpenFeign는 Spring Cloud 프로젝트의 일부로서, 선언적 웹 서비스 클라이언트로 서비스 간의 HTTP 통신을 자동화하는데 사용됩니다.
FeingClient는 Netflix에서 최초로 개발된 선언적 HTTP 클라이언트입니다. 옛날에는 Spring Cloud Netflix Feign으로 불렸으나 현재 Netflix에서 오픈소스로 공개하면서 OpenFeign으로 변경되었어요! OpenFeign 가장 좋은접은 Spring Cloud OpenFeign으로 통합되면서 SpringMVC에서 제공하는 어노테이션을 그대로 사용할 수 있다는 점과HttpMessageConverters를 사용 할 수 있다는 점이에요!!
FeignClient는 HTTP API 클라이언트를 단순화하는 것으로 목표로 개발되었어요!
따로 RestTemplate처럼 HttpClient나 RestTemplate 객체를 선언하지않고 인터페이스를 만들어 어노테이션을 적용하는 것으로 통신이 가능하게합니다 :)
그럼 FeingClient를 왜 사용할까요?
1. SpringMVC에서 제공하는 어노테이션을 그대로 사용가능
2. RestTemplate보다 간편한 사용 및 좋은 가독성
3. 간단한 사용법으로 인한 테스트또한 간편
4. 요청에 대한 커스텀이 간편
안 좋은 것은 뭔가요..? 단점
- 동기적인 동작, 즉 하나의 요청을 완료해야지 다음 요청을 보낼 수 있어요 ㅠㅠ
FeignClient 사용법
1. 의존성 추가 ( build.gradle)
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.14'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
ext {
set('springCloudVersion', "2022.0.1")
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
dependencies {
//json parsing
implementation 'com.google.code.gson:gson:2.8.7'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
//http-client
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}
관련 버전 호환성은 아래 링크에서 참고하실 수 있습니다 :)
https://spring.io/projects/spring-cloud
Spring Cloud
Spring Cloud provides tools for developers to quickly build some of the common patterns in distributed systems (e.g. configuration management, service discovery, circuit breakers, intelligent routing, micro-proxy, control bus, one-time tokens, global locks
spring.io
2.@EnableFeignClients
FeignClient를 사용할려면, Application Class에 @EnableFeignClient를 적용시켜야합니다!
이 친구의 역할은 하위 클래스들을 돌아다니면서 @FeignClient가 있는 지 확인하는 친구에요!
@SpringBootApplication
@EnableFeignClients
public class FeignclientStudyApplication {
public static void main(String[] args) {
SpringApplication.run(FeignclientStudyApplication.class, args);
}
}
3. 인터페이스 선언
저희는 실제 통신할 서버를 구현하지 않고 테스트 서버를 사용할거에요!!
https://reqres.in해당 URL을 이용하시면됩니다!

HTTP Method : GET
URL :https://reqres.in/api/users/2
로 FeignClient를 사용해서 통신해볼 것입니다.
@FeignClient(name = "myFeignClient", url = "${spring.feign.url}", configuration = FeignClientsConfiguration.class)
public interface MyFeignClient {
@GetMapping("/api/users/{id}")
UserDto getUser(@PathVariable(name = "id") Long id);
}
인터페이스를 다음과 같이 선언한뒤
@FeignClient를 등록해 이 인터페이스가 FeignClient를 라고 등록을 해야합니다. 이렇게 되면 저희가 아까 @EnableFeignClient를 작성한 녀석이 @FeignClient를 찾으러 다니게 되는거죠
추가적인 설명으로
name : 내가 등록할 FeingClient의 이름을 설정하는 부분입니다. ( 자신의 취향이나 팀의 개발문화에 맞춰 작성해주시면 될것 같아요)
url :호출할 api의 url을 설정하는 부분입니다. 저희가 연습하는 부분에서는 https://reqres.in 이 부분이 되겠네여 저 같은 경우에는 yml 파일에 따로 작성해서 사용하고 있습니다.
@GetMapping이나 Long id, @PathVariable : 요 녀석들은 통신할 서버에서 요구하는 정보들입니다. 기존에 Controller를 만들었던 것과 비슷하게 사용하시면 됩니다. Get은 HttpMethod를 Long id 는 통신하는 서버에서 요구하는 데이터라고 보시면됩니다.
configuration : FeignClient의 설정정보를 담고있는 녀석이라고 보면됩니다. 제가 사용한 녀석인FeignClientsConfiguration.class는 기본적으로 설정되어있는 녀석입니다.
예를들어FeignClientsConfiguration.class의 content-type이 기본적으로 application/json으로 되어있습니다. 하지만 추후 통신할 서버의 content-type이 다른 값이라면 따로 Configuration 클래스를 만들어 설정해줘야합니다. 다음 부분에서 설명해 드리죠.
4.Configuration 설정
Configuration을 설정할 때 알아야하는 것들이 있습니다. 바로 Feign에서 기본적으로 설정해주는 Bean들입니다.
https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/
Spring Cloud OpenFeign
Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable
docs.spring.io

위 녀석들이 FeignClient에서 기본적으로 설정해주는 Bean들입니다.

그리고 이 녀석들은 따로 제공되지 않는 Bean들이므로 사용환경에 맞게 추가하시면 될 것같습니다.
아마 가장 많이 사용하는 Bean들이 ErrorDecoder, Logger.Level 정도로 압축되지 않을 까 싶네요.
현재는 따로 Configuration을 등록해서 사용하지 않았지만, 카카오 소셜로그인을 구현할 때, 사용했던 설정을 보여드리곘습니다.
public class KakaoFeignConfiguration {
@Bean
public RequestInterceptor requestInterceptor() {
return template -> template.header("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
}
@Bean
public ErrorDecoder errorDecoder() {
return new KakaoFeignClientErrorDecoder();
}
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
RequestInterceptor Bean : RequestInterceptor의 경우에는 요청에 Header에 값을 넣을 때 사용됩니다. 카카오서버와 통신하기 위해서는 Content-Type을 application/x-www-form-urlencoded;charset=utf-8로 요청을 보내야하기 하기때문에 위와 같이 설정했습니다.
ErrorDecoder Bean : ErrorDecoder는 FeignClient에서 서버에 요청을 보낼 때, 발생한 에러에 대해서 커스텀할 때 사용합니다. 저 같은 경우는 커스텀에러를 만들어 다음과 같이 처리해줬습니다.
public class KakaoFeignClientErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
switch (response.status()){
case 400:
return new ApplicationException(ApplicationErrorMessage.KAKAO_BADREQUEST);
case 404:
return new ApplicationException(ApplicationErrorMessage.KAKAO_UNAUTHORIZED);
default:
return new ApplicationException(ApplicationErrorMessage.KAKAO_EXCEPTION);
}
}
}
Logger Bean : FeignClient에서 기본 Logger는 기록되지 앟ㄴ기 때문에 Logger.Level을 설정해줘야합니다.
이를 위해서는 한가지 더 설정해줘야 하는데 application.yml에 logging.level을 정해줘야합니다.
logging:
level:
com.****.****.module.user.client : DEBUG
저의 경우에는 feignclient를 client폴더에 모아놓았기 때문에 이렇게 됩니다 :)
Service
@RequiredArgsConstructor
@Service
public class MyFeignService {
private final MyFeignClient myFeignClient;
public UserDto testMethod(){
return myFeignClient.getUser(2L);
}
}
이제 Service 계층에서 저희가 선언한 client 인터페이스를 호출하면됩니다.
Controller
@RestController
@RequiredArgsConstructor
public class MyFeignController {
private final MyFeignService myFeignService;
@GetMapping("/user")
public ResponseEntity<?> getUser(){
var result = myFeignService.testMethod();
return ResponseEntity.ok(result);
}
}
그 다음 Controller 계층에서 서비스를 부르고 해당 API를 호출하게되면
아래와 같은 결과값을 받아볼 수 있습니다
{
"data": {
"id": "2",
"email": "janet.weaver@reqres.in",
"first_name": "Janet",
"last_name": "Weaver",
"avatar": "https://reqres.in/img/faces/2-image.jpg"
},
"support": {
"url": "https://reqres.in/#support-heading",
"text": "To keep ReqRes free, contributions towards server costs are appreciated!"
}
}
긴 글 읽어주셔서 감사합니다 😁