📌 Feign Client 란?
본래 Netflix (그 넷플릭스 맞음) 에서
오픈 소스 일부로 개발되어 사용중인 경량 REST 클라이언트
현재는 Spring Cloud 프레임워크의 일부가 되었다.
인터페이스로 정의된 API를 기반으로 RESTful 서비스를 호출한다.
이를 통해 코드 가독성과 유지 보수성이 늘어남을 기대할 수 있다.
📚 Dependency
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign', version: '3.1.1'
implementation 'io.github.openfeign:feign-jackson:12.1'
🔨 @EnableFeignClient
Feign 을 사용하기 위한 어노테이션
class 를 가져오는 방식과 basePackage 를 기반으로 scan 하는 방식이 있다.
📝 FeignCommonConfig
@Configuration
@EnableFeignClients(basePackageClasses = BaseFeignClientPackage.class)
public class FeignCommonConfig {}
이 프로젝트에서는 basePackage 를 스캔하는 방식을 사용했다.
📝 BaseFeignClientPackage
public interface BaseFeignClientPackage {}
Feign Client 들이 있는 패키지에 이 인터페이스 파일을 넣어주면
해당 패키지 하위의 모든 FeignClient 들을 스캔해준다.
🔨 @FeignClient
Feign 에서 본격적으로 외부 서버와 통신하게 해주는 Client
인터페이스 기반으로 구현되기 때문에 가독성이 훨씬 높다.
📝 RestTemplete 예시
public CommonResponse addUser() {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("userId", "lim");
map.add("userName", "sua");
map.add("age", "21");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
ResponseEntity<CommonResponse> response =
restTemplate.postForEntity(USER_URL + "/form", request, CommonResponse.class);
return response.getBody();
}
만약 RestTemplete 이나 Webclient 로 구현되었으면
위처럼 서비스 레이어에서 http 요청이 수행됐을 거다
📝 AppleOAuthClient
@FeignClient(
name = "AppleOAuthClient",
url = "https://appleid.apple.com",
configuration = AppleOAuthConfig.class)
public interface AppleOAuthClient {
@PostMapping("/auth/token?grant_type=authorization_code")
AppleTokenResponse appleAuth(
@RequestParam("client_id") String clientId,
@RequestParam("redirect_uri") String redirectUri,
@RequestParam("code") String code,
@RequestParam("client_secret") String clientSecret);
}
애플 oauth 서버에서 받은 인가 코드를 넘겨서 인증하는 Client 예시
반면 Feign Client 같은 경우는
마치 컨트롤러에서 수행되는 것 처럼 인터페이스가 구성되어 있어
관심도 분리 측면에서 훨씬 좋은 것 같다.
의견 차이가 있을 수 있겠지만 Feign Client 가 가독성이 더 높아 보인다.
🔨 추가 설정
설정 파일을 통해 Feign Client 에 대한 추가 설정을 해줄 수 있다.
📝 AppleOAuthConfig
@Import(AppleOAuthErrorDecoder.class)
public class AppleOAuthConfig {
@Bean
@ConditionalOnMissingBean(value = ErrorDecoder.class)
public AppleOAuthErrorDecoder commonFeignErrorDecoder() {
return new AppleOAuthErrorDecoder();
}
@Bean
Encoder formEncoder() {
return new feign.form.FormEncoder();
}
}
@ConditionalOnMissingBean 어노테이션은
기존 ErrorDecoder 가 존재하면 무시,
존재하지 않을 경우 AppleOAuthErrorDecoder 를 등록하도록 한다.
FeignClient 같은 경우 200 이외 값에 대해서 에러를 처리 해주는데
이 설정을 통해 에러 디코더에 대한 커스터마이징을 도와준다.
Encoder 는 객체를 Key - Value 의 폼 데이터로 변환시켜준다.
이를 통해 Java 객체를 HTTP POST 요청에 대한 폼 데이터로 보낼 수 있도록 한다.
Content-Type 헤더는 application/x-www-form-urlencoded 로 설정된다.
📝 AppleOAuthDecoder
public class AppleOAuthErrorDecoder implements ErrorDecoder {
@Override
@SneakyThrows
public Exception decode(String methodKey, Response response) {
InputStream inputStream = response.body().asInputStream();
byte[] byteArray = IOUtils.toByteArray(inputStream);
String responseBody = new String(byteArray);
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(responseBody);
String error = jsonNode.get("error") == null ? null : jsonNode.get("error").asText();
String errorDescription =
jsonNode.get("error_description") == null
? null
: jsonNode.get("error_description").asText();
System.out.println(jsonNode);
throw new BaseDynamicException(response.status(), error, errorDescription);
}
}
ErrorDecoder 를 implements 한다.
이를 통해 에러 코드에 대한 처리를 커스터마이징 할 수 있다.
OAuth 인증을 예시로 들면
구글, 카카오, 네이버, 애플 등등 모두 에러 처리에 대한 응답이 다르다.
그렇기 때문에 각 서비스 별 에러 처리에 대해 커스터마이징 해주어 처리할 필요가 있었다.
📝 KauthErrorDecoder
public class KauthErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
KakaoKauthErrorResponse body = KakaoKauthErrorResponse.from(response);
try {
KakaoKauthErrorCode kakaoKauthErrorCode =
KakaoKauthErrorCode.valueOf(body.getErrorCode());
throw kakaoKauthErrorCode.getException();
} catch (IllegalArgumentException e) {
KakaoKauthErrorCode koeInvalidRequest = KakaoKauthErrorCode.KOE_INVALID_REQUEST;
throw koeInvalidRequest.getException();
}
}
}
예시로 카카오 oauth 같은 경우에는
KOE303 과 같이 카카오 자체 에러코드를 제공해준다.
이를 커스터마이징하여 에러 메세지를 자체적으로 내 서버에 띄우도록 할 수 있다.
🚀 그렇다면 FeignClient 호출은?
public AppleTokenResponse getOAuthTokenTest(String code) {
return appleOAuthClient.appleAuth(
appleOAuthProperties.getClientId(),
appleOAuthProperties.getRedirectUrl(),
code,
getClientSecret());
}
마치 메소드를 호출하는 것 처럼
서비스 레이어에서 파라미터를 넣어줌으로써 호출할 수 있다.
로직 분리에 정말 탁월하고 편리하다.
처음 설정하는 부분이 어려울 수도 있는데
적응하면 이 만큼 가독성 높고 편리한 것도 없다.
특히 외부 서버의 API 를 호출하는 코드가 많다면
Feign Client 의 도입을 적극적으로 고려해봐도 좋을 것 같다!
'📡 백엔드 > 🌱 Spring Boot' 카테고리의 다른 글
[Spring] 스프링 소셜 로그인 OIDC 방식으로 구현하기 (OAuth with OpenID Connect) (1) | 2023.03.25 |
---|---|
[Spring] 스프링 애플 로그인 구현하기 (Sign in with Apple OIDC) (1) | 2023.03.25 |
[Spring] 스프링 시큐리티 + JWT로 카카오 로그인 구현하기, 프론트엔드와 연결 (0) | 2022.09.04 |
[Spring] OAuth2Service + 스프링 시큐리티 + JWT로 카카오 로그인 구현하기 (0) | 2022.08.27 |
[Spring] Swagger 연동 시 Unable to infer base url 접속 에러 (ResponseBodyAdvice) (0) | 2022.08.11 |