[ISSUE] Spring Boot Feign API로 File 전송하기

jinvicky·2024년 7월 4일
0

Spring & Java

목록 보기
21/23

Intro


사용자 시스템 A가 있고, 관리자 시스템 B가 있다. 둘다 백엔드 어플리케이션이다.
A는 B의 설정값을 feign으로 호출하면 B가 DB로 조회해서 응답한다.
결과적으로 A의 프론트가 이를 받아서 A 시스템에 적용한다.

Issue


문제는 성능 테스트에서 A는 접속 수가 짧은 시간에 폭발적으로 늘어나고,
따라서 A가 B를 호출하는 횟수가 증가하면 B에게 부담이 갈텐데,
짊어질 부담에 비해 pod의 사이즈가 너무 작다는 것이다;;
(현재 설계가 MSA 구조로 되어 있다)

갑자기 A와 B의 관계를 반대로 뒤집으라고 한다;;

이제 A-FRONTA-BACK에서 호출하고 A-BACKB-BACK에 있던 DB 테이블을 가져와 관리하게 된다.

이제 관리자 시스템인 B-BACK이 설정을 조회/수정시 반대로 A-BACK을 호출해서 처리해야만 한다.

Feign의 GET


get은 쉽게 해결했다. (feign 설정은 별도로 포스팅했으니 생략)

@GetMapping("/theme")
ResponseResult selectTheme(@RequestParam("sche") String sche);

service단에서 처리할 때 return type을 Object로 설정할 수도 있다.
다만 Object는 너무 모호해서 웬만해서는 특정 VO로 타입을 지정하려고 한다.
json 결과 반환은 ObjectMapper가 가장 편했다.
(현재 ResponseResult는 status, message, data를 가지며, data가 결과물이다)

public ResSettingVO selectTheme(String sche) {
        ResponseResult resp = themeApi.selectTheme(sche);
        ObjectMapper mapper = new ObjectMapper();
        return mapper.convertValue(resp.getData(), new TypeReference<ResSettingVO>(){});
}

Feign의 POST


post로 값을 전송할 때 vo 전송을 하는데 자꾸만 null이 들어왔다.
*지금부터는 개인적으로 테스트한 내용을 적은 것이며, 틀릴 수 있습니다.

내가 현재 내린 결론은 feign으로 통신할 때는
1. 어노테이션이 필수다. (보통 스프링부트는 requestBody할 거 아니면 requestParam 생략 가능함)
2. vo 전송이 requestbody할 거 아니면 안된다.
여기서 그냥 엄청~ 헤맸는데, 첨부파일도 전송하려면 우리가 front단에서 formData 객체를 생성해서 백단으로 보내는데
그냥 보내려는 값들을 파일 타입 여부 상관없이 @RequestPart 해서 보내버리는 게 시간절약이라는 결론이다.

RequestPart 어노테이션을 vo 앞에 추가하면 missingServletException~ 예외가 뜬다.
@RequestBody면 제대로 받는데 첨부파일 때문에 프론트에서 백으로 받을 때 requestBody가 아님;

설마 클래스 타입 다르다고 그러나? 생각도 들어서

  • 클래스명도 맞춰봤다. (개인적으로 iv명 일치가 중요하지 이건 의미 없어보였지만)
  • Serializable을 구현해서 인텔리제이로 serialize_id 생성했다.
    Serializable을 사용했더니 서버를 죽여버렸다. 개인적으로 이건 아닌 것 같다

결과적으로 값들을 개별로 @RequestParam 처리했다. 이제 String 값들을 가져온다.

 @PostMapping(value = "/theme")
 ResponseResult insertUpdateTheme(@RequestPart("sche") String sche,
                                     @RequestPart("mainColorCd") String mainColorCd,
                                     @RequestPart("themeNo") String themeNo);

왜 File은 또 Null인데;;

이제 설정에서 이미지 파일을 업로드했을 경우 같이 전송해야 하는데
이 과정에서 참 서버를....많이 죽였다.

이번 포스트의 메인, 들어가보자.

이렇게 해봤다;;

일단 vo로 전송했을 때 file도 마찬가지로 null이 나왔다.

  • String 값들은 @RequestParam으로 처리하고 MultipartFile만 @RequestPart로 처리해보았다.
    Body Parameter와 Form Parameter?는 섞어쓸 수 없다는 식의 에러가 나온다
    즉, 어노테이션을 통일해야 한다.

  • @Headers를 추가하고, 메서드의 consumes 타입도 설정해본다.
    부족하다. 여전히 안된다.

  • MultipartFile 객체를 @RequestParam으로 처리했더니

    Failed to convert property value of type 'java.lang.String' to required type 'org.springframework.web.multipart.MultipartFile' for property 'file'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'org.springframework.web.multipart.MultipartFile' for property 'file': no matching editors or conversion strategy found

대충 string을 multipartfile로 바꾸는게 힘들다고 한다;

  • spring.servlet.multipart.enabled 설정을 true로 하면 되지 않을까?
    얘는 기본값이 true다;

해결

  • 중요한 설정 파일을 하나 추가해야 한다.
    이걸 추가하면서 비로소 요청이 성공이 뜨기 시작한다.
    feign에서 file 보내려면 converter 해줘야 하는 구나~ 한다.
    아무리 헤더 추가하고 파라미터 해도 얘가 없으면 도루묵인 듯 하다.
import feign.form.spring.SpringFormEncoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignFileUploadConfig {

    private final ObjectFactory<HttpMessageConverters> messageConverters;

    public FeignFileUploadConfig(ObjectFactory<HttpMessageConverters> messageConverters) {
        this.messageConverters = messageConverters;
    }

    @Bean
    public SpringFormEncoder feignFormEncoder() {
        return new SpringFormEncoder(new SpringEncoder(messageConverters));
    }
}
  • 다른 설정들도 추가하고, 어노테이션은 @RequestPart로 통일한다.
@PostMapping(value = "/theme", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Headers("Content-Type: multipart/form-data")
ResponseResult insertUpdateTheme(@RequestPart("sche") String sche,
                                     @RequestPart(value = "mainColorCd", required = false) String mainColorCd,
                                     @RequestPart(value = "themeNo", required = false) String themeNo,
                                     @RequestPart(value = "fileByFeign", required = false) MultipartFile fileByFeign);

필수값이 아니면 required=false 처리도 한다.
multpart/form-data를 처리하기 위한 어노테이션과 타입들도 확인하자.

여기까지가 송신하는 측이다.
수신하는 측은 어떨까?

수신에서 왜 File이 또 Null이야;;

수신측에서 vo로 처리했으면 했는데 못받는다;;;
결국 themeVO에 file이라는 변수명이 있음에도, 별도로 fileByFeign 등으로 별도로 @RequestPart로 처리해야 했다;;;

@Operation(summary = "LMS 테마 수정", description = "LMS 테마 수정")
@PostMapping
public ResponseEntity<ResponseResult> insertUpdateTheme(ThemeVO themeVO,
                                                            @RequestPart(value = "fileByFeign", required = false) MultipartFile fileByFeign) throws IOException {
        return ResponseEntity.ok(ResponseResult.data(service.insertUpdateTheme(themeVO, fileByFeign)));
}

결과적으로 동작한다.

Outro


내 경험으로는 프론트랑 백이랑 통신하는 거랑 백단끼리 feign으로 통신하는 거 미묘하게 다른 거 같다.
기본적으로 프론트에서 백으로 통신할 때는 RequestPart 어노테이션을 쓸 일이 없다. vo로 보내면 다 변수 따라서 자동 매핑 되거든.

이건 feign을 쓸 때만 한 것이지, 보통 http 리퀘스트 보내고 받을 때 꼭 개별 변수명들로 하고~ 파일은 vo랑 별도로 보내는 건 아님.

Updates


2024.08.21
DB 구조를 바꾸라고 인프라팀에서 전달옴.
LMS만 테마 설정을 테이블로 관리하는 것이 아니라,
LMI 테마 설정 테이블이 업데이트 될 때마다 LMS 테마 설정 테이블로 데이터 복사를 해오는 것이다;;
즉, LMS, LMI 모두 테이블을 가지고 있다.

profile
Front-End와 Back-End 경험, 지식을 공유합니다.

0개의 댓글