Feign 사용기

✨Feign 이란 ?
Feign이란 SpringBoot 애플리케이션을 위한 선언형 REST 클라이언트입니다. 여기서 "선언형"이란 API 호출을 위해 복잡한 URL 설정, 파라미터 처리, 헤더 설정 등을 직접 코드로 작성할 필요가 없고 인터페이스를 선언하는 것만으로 API호출을 처리해줍니다. Feign은 API 호출 과정을 단순화해주고 인터페이스만 정의하면 자동으로 HTTP 요청을 관리 해줍니다.
Feign 로 리팩토링 전 코드입니다. 자바 기본코드로 스프링부트 프레임워크 이점을 활용하지 못한채 URL설정이나 헤더설정을 하나하나 직접 코드로 작성한것을 볼 수 있습니다.
public class OCRAPIDemo {
public static void main(String[] args) {
String apiURL = "YOUR_API_URL";
String secretKey = "YOUR_SECRET_KEY";
String imageFile = "YOUR_IMAGE_FILE";
try {
URL url = new URL(apiURL);
HttpURLConnection con = (HttpURLConnection)url.openConnection();
con.setUseCaches(false);
con.setDoInput(true);
con.setDoOutput(true);
con.setReadTimeout(30000);
con.setRequestMethod("POST");
String boundary = "----" + UUID.randomUUID().toString().replaceAll("-", "");
con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
con.setRequestProperty("X-OCR-SECRET", secretKey);
JSONObject json = new JSONObject();
json.put("version", "V2");
json.put("requestId", UUID.randomUUID().toString());
json.put("timestamp", System.currentTimeMillis());
JSONObject image = new JSONObject();
image.put("format", "jpg");
image.put("name", "demo");
JSONArray images = new JSONArray();
images.put(image);
json.put("images", images);
String postParams = json.toString();
con.connect();
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
long start = System.currentTimeMillis();
File file = new File(imageFile);
writeMultiPart(wr, postParams, file, boundary);
wr.close();
int responseCode = con.getResponseCode();
BufferedReader br;
if (responseCode == 200) {
br = new BufferedReader(new InputStreamReader(con.getInputStream()));
} else {
br = new BufferedReader(new InputStreamReader(con.getErrorStream()));
}
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = br.readLine()) != null) {
response.append(inputLine);
}
br.close();
System.out.println(response);
} catch (Exception e) {
System.out.println(e);
}
}
private static void writeMultiPart(OutputStream out, String jsonMessage, File file, String boundary) throws
IOException {
StringBuilder sb = new StringBuilder();
sb.append("--").append(boundary).append("\r\n");
sb.append("Content-Disposition:form-data; name=\"message\"\r\n\r\n");
sb.append(jsonMessage);
sb.append("\r\n");
out.write(sb.toString().getBytes("UTF-8"));
out.flush();
if (file != null && file.isFile()) {
out.write(("--" + boundary + "\r\n").getBytes("UTF-8"));
StringBuilder fileString = new StringBuilder();
fileString
.append("Content-Disposition:form-data; name=\"file\"; filename=");
fileString.append("\"" + file.getName() + "\"\r\n");
fileString.append("Content-Type: application/octet-stream\r\n\r\n");
out.write(fileString.toString().getBytes("UTF-8"));
out.flush();
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
int count;
while ((count = fis.read(buffer)) != -1) {
out.write(buffer, 0, count);
}
out.write("\r\n".getBytes());
}
out.write(("--" + boundary + "--\r\n").getBytes("UTF-8"));
}
out.flush();
}
}
이제 Feign을 사용해보겠습니다!
✨Dependencies 설정
의존성에 spring-cloud-starter-openfeign을 추가해줍니다. 공식 문서에 있는 내용을 그대로 사용했는데 처음 보는 용어가 있어서 찾아봤습니다.
- dependencyManagement는 모든 모듈에서 의존성 버전을 중앙에서 관리하도록 한다.
- dependencies에서는 각 모듈의 라이브러리 의존성과 버전을 직접 정의 한다
dependencyManagement를 통해 일관된 버전을 유지할 수 있어 관리가 간편해진다고 합니다!
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}
ext {
set('springCloudVersion', "2023.0.2")
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
✨@EnableFeignClients 추가
메인 클래스에 @EnableFeignClients를 추가합니다. 이 어노테이션을 사용하면 Spring Boot가 자동으로 Feign 클라이언트 인터페이스를 찾아 등록합니다.
@SpringBootApplication
@EnableScheduling
@EnableMethodSecurity
@EnableFeignClients
public class EgginhealthApplication {
public static void main(String[] args) {
SpringApplication.run(EgginhealthApplication.class, args);
}
}
✨환경설정 파일 설정하기
각 Feign 클라이언트는 다양한 구성 가능한 컴포넌트로 이루어져 있습니다. Spring Cloud는 클라이언트가 필요할 때 기본 설정을 자동으로 생성하며, 이 설정은 FeignClientsConfiguration 클래스를 통해 관리됩니다.
저는 Feign 클라이언트의 모든 요청에 X-OCR-SECRET 헤더를 추가하고 API 키를 설정했습니다.추가로 Feign 클라이언트의 요청 본문을 폼 데이터 형식으로 인코딩할 수 있게 설정을 추가했습니다.
public class NaverOcrClientConfig {
@Bean
public RequestInterceptor requestInterceptor(@Value("${ocr.api.key}") String apiKey) {
return requestTemplate -> requestTemplate.header("X-OCR-SECRET", apiKey);
}
@Bean
public Encoder feignEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
✨Feign Client 설정
@FeignClient(name = "naverOcrClient", configuration = NaverOcrClientConfig.class) : Feign 클라이언트를 정의하고 name 속성으로 클라이언트를 식별합니다. configuration 속성으로 NaverOcrClientConfig 설정 클래스를 지정하여 아까 작성한 환경설정 파일을 지정해줍니다.
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) : HTTP POST 요청을 정의했고 멀티파트 폼 데이터를 처리하여 파일 업로드와 추가 데이터를 전송할 수 있게 설정해줬습니다.
매개변수로는 보낼 데이터를 전송합니다. @RequestPart(value = "message") String message로 메시지를, @RequestPart(value = "file") MultipartFile file로 파일을 전송했습니다.
💢 트러블 슈팅
CLOVA OCR API의 form data 중 message의 형식은 아래와 같습니다.
{"images":[{"format":"jpg","name":"demo","data":"your image base64 bytes"}],"requestId":"guide-json-demo","version":"V2","timestamp":1584062336793}'
처음에는 @RequestPart(value = "message") MessageDto message로 객체를 전송했으나 제대로 전송되지 않는 오류가 발생했습니다. 기본적으로 multipart/form-data로 객체를 전송할 때 콘텐츠 유형이 text/plain으로 설정되기 때문에 발생한 문제였습니다. 이후, 객체를 문자열로 변환하여 전송함으로써 문제를 해결했습니다!
@FeignClient(name = "naverOcrClient", configuration = NaverOcrClientConfig.class)
public interface NaverOcrClientController {
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String getOcrResult(@RequestPart(value = "message") String message,
@RequestPart(value = "file") MultipartFile file
);
}
✨후기
Feign을 도입하면서 자동으로 요청과 응답을 직렬화하고 역직렬화해 주기 때문에 데이터 포맷에 신경 쓸 필요가 없어졌습니다. 또한, 인터페이스를 통해 REST API 호출을 손쉽게 구현할 수 있어 코드가 간결하고 가독성이 크게 향상되었다고 느껴졌습니다.
✨참고 자료
https://techblog.woowahan.com/2630/
우아한 feign 적용기 | 우아한형제들 기술블로그
안녕하세요. 저는 비즈인프라개발팀에서 개발하고 있는 고정섭입니다. 이 글에서는 배달의민족 광고시스템 백엔드에서 feign 을 적용하면서 겪었던 것들에 대해서 공유 하고자 합니다. 소개 Feign
techblog.woowahan.com
https://spring.io/projects/spring-cloud-openfeign
Spring Cloud OpenFeign
spring.io
https://api.ncloud-docs.com/docs/ai-application-service-ocr-ocrdocumentocr
CLOVA OCR Document API
api.ncloud-docs.com