이 게시글은 Feign Client를 공부하고 프로젝트에 적용한 경험에 대한 회고록을 작성했습니다.
Feign Client이란?
⇒ 선언적으로 사용할 수 있는 Http Client
Feign Client의 소개
Netflix에 의해 처음 만들어진 선언적인 HTTP Client 도구로써, 외부 API 호출을 쉽게할 수 있도록 도와준다. 여기서 “선언적인” 이란 어노테이션 사용을 의미하는데, Open Feign은 인터페이스에 어노테이션들만 붙여주면 구현이 된다. 이러한 방식은 Spring Data JPA와 유사하며, 상당히 편리하게 개발을 할 수 있도록 도와준다.
⇒ 외부 컴포넌트와 통신을 하는데 있어서, 여러 개의 클라이언트가 존재! 보통은 restTemplete 많이 사용하지만 요즘은 Feign Client도 많이 사용함
기존의 프로젝트에서 GitHub API 통신을 해서 해당 유저의 닉네임을 조회하고 프로필 이미지를 가져오는 API가 있었다.
프로젝트를 일정에 쫓겨 급하게 만들 때는 기존에 사용해봤던 적이 있어 알고 있는 HttpURLConnection 을 사용했지만, 나중에 RestTemplete로 리팩토링 해야겠다는 생각을 가지고 있었다.
스프링 공부를 하던 와중에 Feign Client를 알게되었고, Test 과정에서 둘다 구현을 해봤지만 RestTemplete 보다 코드가 간결하고 가독성이 좋아서 Feign Client 리팩토링을 하기로 결정했다.
( 넷플릭스에서 만든 오픈 소스 라이브러리라는 것도 신기하고 Netflix OSS에 얼마나 좋은 기술들이 많으면 Spring Cloud Netflix 까지 나왔나 싶어서 나중에 넷플릭스 오픈 소스에 대해서 공부 해봐야겠음 )
가장 큰 차이는 선언적 방식이라는 점이다!!
이 말이 무엇인고 하니 인터페이스를 정의하고, 해당 인터페이스에 대한 구현체를 자동으로 생성하여 REST API 호출을 처리한다는 말씀
RestTemplate은 각 API를 호출하기 위한 코드를 반복해서 작성해야 하지만, Feign Client는 인터페이스를 정의하고, 해당 인터페이스에 대한 구현체를 자동으로 생성하므로, 개발자는 클라이언트 코드를 간결하게 유지할 수 있다.
@FeignClient(
name="githubClient",
url= "https://api.github.com",
configuration= FeignConfig.class
)
public interface GithubClient {
@GetMapping("/users/{githubNickname}")
FeignResponseInfo getGithubUser(@PathVariable("githubNickname") String githubNickname);
}
이런식으로 인터페이스를 정의하고 메서드를 사용해 바로 통신이 가능하다! 겁나 간편하고 코드도 간결하다 밑에서 제대로 설명할 예정
뿐만 아니라
RestTemplate은 로드 밸런싱과 서비스 디스커버리를 위해 Netflix OSS의 Eureka나 Ribbon과 같은 라이브러리와 함께 사용될 수 있지만 Feign Client는 Ribbon을 기본적으로 내장하고 있어, 로드 밸런싱과 서비스 디스커버리를 자동으로 처리합니다.
로드밸런싱 부분에 있어서도 이득!
public GithubNicknameResponseDto getGithubUser(String githubNickname) {
String githubUrl = "https://api.github.com/users/" + githubNickname;
try {
URL url = new URL(githubUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("charset", "utf-8");
conn.setDoOutput(true); // 출력 가능 상태로 변경
conn.connect();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder sb = new StringBuilder();
String line = "";
while ((line = br.readLine()) != null) {
sb.append(line);
}
// JSON Parsing
JSONObject jsonObj = (JSONObject) new JSONParser().parse(sb.toString());
String nickName = (String) jsonObj.get("login");
String img = (String) jsonObj.get("avatar_url");
return GithubNicknameResponseDto.of(nickName, img);
} catch (FileNotFoundException e) {
throw new NotFoundException(NOT_FOUND_GITHUB_NICKNAME);
} catch (Exception e) {
throw new BadRequestException(GITHUB_SERVER_ERROR);
}
}
깃허브 닉네임을 보내면 해당 닉네임이 존재하면 닉네임과 이미지를 전달해주고 닉네임이 존재하지 않으면 NOT_FOUND_GITHUB_NICKNAME 에러를 발생시키는 로직이다!
먼저 gradle을 import 해준다!
ext {
set('springCloudVersion', '2021.0.3')
}
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}
@EnableFeignClients 어노테이션을 메인 클래스 위에 달아준다.
=> Feign Client를 사용하기 위해서 필수
@EnableFeignClients
@SpringBootApplication
public class GromitApplication {
public static void main(String[] args) {
SpringApplication.run(GromitApplication.class, args);
}
}
그 다음 인터페이스 클래스를 하나 만든다.
@FeignClient(
name="githubClient",
url= "https://api.github.com",
configuration= FeignConfig.class
)
public interface GithubClient {
@GetMapping("/users/{githubNickname}")
FeignResponseInfo getGithubUser(@PathVariable("githubNickname") String githubNickname);
}
깃허브 닉네임과 이미지를 가져올 객체 관련한 클래스를 만들어주고
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class FeignResponseInfo {
@JsonProperty("login")
private String nickname;
@JsonProperty("avatar_url")
private String img;
}
이렇게 서비스에서 호출해주면 끝!
public GithubNicknameResponseDto getGithubUserVersion2(String githubNickname) {
FeignResponseInfo githubUser = githubClient.getGithubUser(githubNickname);
return GithubNicknameResponseDto.of(githubUser.getNickname(),githubUser.getImg());
}
public GithubNicknameResponseDto getGithubUserVersion1(String githubNickname) {
String githubUrl = "https://api.github.com/users/" + githubNickname;
try {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.set("charset", "utf-8");
HttpEntity<String> entity = new HttpEntity<>("parameters", headers);
ResponseEntity<String> response = restTemplate.exchange(githubUrl, HttpMethod.GET, entity, String.class);
String responseBody = response.getBody();
JSONObject jsonObj = (JSONObject) new JSONParser().parse(responseBody);
String nickName = (String) jsonObj.get("login");
String img = (String) jsonObj.get("avatar_url");
return GithubNicknameResponseDto.of(nickName, img);
} catch (Exception e) {
throw new NotFoundException(NOT_FOUND_GITHUB_NICKNAME);
}
}
HttpURLConnection, RestTemplete 코드는 json 파싱 과정이 있는데 Feign Client 이러한 과정도 없어서 코드가 엄청 간결해보이고 통신 부분을 인터페이스로 나눠서 가독성이 훨씬 좋아졌다. Feign Client의 인터페이스도 RESTful API의 구현 방식을 기반으로 하고 있어 익숙한 Spring MVC 어노테이션으로 개발이 가능해서 SpringBoot에 더 적합해 보임!
물론 단점도 있다
기본 Http Client가 Http2를 지원하지 않음 → Http Client에 대한 추가 설정 필요
공식적으로 Reactive 모델을 지원하지 않음 → 비공식 오픈소스 라이브러리로 사용 가능
경우에 따라서 애플리케이션이 뜰 대 초기화 에러가 발생할 수 있음 → Object Provider로 대응 필요
2편에서 Feign Interceptor 와 Feign ErrorDecoder 를 이용해서 프로젝트에 적용해보겠다
Reference
https://mangkyu.tistory.com/278