Spring을 이용해 Rest API 개발중, file과 data를 같이 업로드해야 하는 상황이 있었습니다.
file과 data는 논리적으로 연관관계가 있었으며, 이를 List형태로 전송하는 경우, 각각의 인덱스 요소가 논리적으로 같은 데이터임을 기억해야하는 상황이었습니다.
이때 이들을 하나의 객체로 맵핑하여 이 조건을 개발자가 기억하지 않아도 되도록 변경하였습니다.
1. 시나리오
예를들어, 어떤 팀에 멤버를 추가하는 상황에서 멤버의 프로필 정보와, 사진을 같이 받아야 한다고 할때. 클라이언트는, 서버에게 사용자의 프로필정보(data)
와 사진(file)
을 함께 전송해야 합니다.
1.1 중요 사항
이때, 멤버의 사진과 프로필 정보는, 논리적으로 연관이 있는 데이터들입니다. 즉, Server에서 값을 전달받았을때,
특정 사진이 특정 프로필의 사진임을 알 수 있어야 합니다.
1.2 해결 방법
1) 사진을 Base64로 인코딩하여 JSON을 이용해 전달
가장 간편한 방법입니다. JSON을 이용해 @RequestBody로 전달한다면, 이미지와, 프로필 정보간의 논리적 연관관계를 구성하여 전달할 수 있습니다.
Controller
x
public class RegisterMemberRequest {
private String base64ProfileImage;
private MemberProfile memberProfile;
}
...
public class MemberController {
...
("/")
public void registerMember( RegisterMemberRequest request) {
...
}
}
Controller는 위와 같이 @RequestBody
어노테이션을 이용해, 사용자의 요청에 담긴 데이터를, 객체로 맵핑할 수 있습니다.
문제점
- base64로 이미지를 변환하는 경우 최대 33% 까지 파일의 크기가 증대됩니다.
- 위의 이유로 네트워크 대역 낭비가 심합니다.
- Spring에서는 특정 크기이상의 데이터가 Body에 담기는 경우 요청을 해석하지 못하는 경우가 있습니다.(원인 불명...)
2) 사진과, 프로필을 form으로 별도 전달
xxxxxxxxxx
...
public class MemberController {
...
("/")
public void registerMember( ("profileImage") MultipartFile profileImage,
"memberProfile") MemberProfile memberProfile) { (
...
}
}
이번 방법은 form data
을 이용해 데이터를 전송하는 방법입니다
문제점
- 객체를 form을 이용해 데이터를 전달받고, 해당 데이터를 객체로 맵핑하기 위해서,
Spring Type Conversion
을 이용해야 합니다. - 멤버 리스트를 전달 받는 경우 데이터의 논리적 연관을 파악하기가 어렵습니다.
데이터의 논리적 연관을 파악하기 어려움 : 개발자에게 기억을 강요
위 형식으로 데이터를 받는 경우, 멤버 리스트 데이터를 받을 때 더 큰 문제가 발생합니다.
xxxxxxxxxx
...
public class MemberController {
...
("/")
public void registerMember( ("profileImage") List<MultipartFile> profileImageList,
"memberProfile") List<MemberProfile> memberProfileList) { (
...
}
}
리스트로 전달받는다면 위와 같은 형식으로 데이터를 전달 받을 수 있습니다. 각각 배열의 같은 인덱스 요소가 같은 멤버의 데이터입니다.
문제점은 바로 두 데이터간의 논리적 연관관계를 개발자가 인지하고 있어야 한다는 점입니다. 즉, "같은 인덱스의 요소는 같은 멤버의 데이터야"를 가정한 채로, 프로그래밍을 해야합니다.
3) formdata를 하나의 객체로 전달
xxxxxxxxxx
public class MemberProfile {
private int age;
private String name;
}
public class RegisterMemberRequest {
private MultipartFile profileImage;
private MemberProfile memberProfile;
}
...
public class MemberController {
...
("/")
public void registerMember( RegisterMemberRequest request) {
...
}
}
이번에는 @ModelAttribute
를 이용해 form data를 객체로 바로 맵핑하는 방법입니다. 이방식으로 처리하면, 데이터 간의 논리적 연관관계를 표현하며, base64와 같이 인코딩을 하지 않기 때문에 파일 사이즈가 증대되는 현상도 발생하지 않습니다.
아래에서 더 자세히 알아보겠습니다.
2. 구현
앞서 살펴본 구현 방법들중 3) formdata를 하나의 객체로 전달
을 이용. 하여 구현을 할 것입니다.
2.1 한 멤버의 정보를 전송
MemberProfile
xxxxxxxxxx
public class MemberProfile {
private int age;
private String name;
}
RegisterMemberRequest
xxxxxxxxxx
public class RegisterMemberRequest {
private MultipartFile profileImage;
private MemberProfile memberProfile;
}
TestController
xxxxxxxxxx
public class TestController {
"/members") (
public void test( RegisterMemberRequest request) {
System.out.println(request.getMemberProfile().getName());
System.out.println(request.getMemberProfile().getAge());
System.out.println(request.getProfileImage().getOriginalFilename());
}
}
테스트
PostMan
을 이용해 테스트를 진행합니다.
POST
메소드를 선택하고 url을 입력합니다.
Body
를 클릭하고, form-data
를 선택합니다.
그 후, key의 type을 File
로 변환합니다.
그 후, RegisterMemberRequest
클래스의 MultipartFile
에 해당하는 변수의 이름을 적습니다. 마지막으로 value에 이미지를 업로드합니다.
xxxxxxxxxx
Key : memberProfile.age
Key : memberProfile.name
이번엔 MemberProfile
에 맵핑될 데이터를 파라미터에 설정해야합니다. key에는 RegisterMemberRequest
클래스의 MemberProfile
타입의 변수의 이름 .
MemberProfile
클래스의 필드 이름 형식으로 입력합니다.
2.2 여러 멤버의 정보를 전송
이번엔 여러 멤버의 데이터를 전송해보겠습니다.
RegisterMemberRequestForm
xxxxxxxxxx
public class RegisterMemberRequestForm {
private List<RegisterMemberRequest> memberRequestList;
}
우선 List<RegisterMemberRequest>
를 멤버변수로 가지는 클래스를 생성합니다.
TestController
xxxxxxxxxx
public class TestController {
"/members") (
public void test( RegisterMemberRequestForm request) {
request.getMemberRequestList().stream()
.forEach(memberRequest -> {
System.out.println(memberRequest.getProfileImage().getOriginalFilename());
System.out.println(memberRequest.getMemberProfile().getAge());
System.out.println(memberRequest.getMemberProfile().getName());
});
}
}
Contoller를 위와같이 변경합니다.
테스트
List에 데이터를 바로 집어 넣는다는 생각으로 formdata의 키값을 입력하시면 됩니다.
RegisterMemberRequestForm
클래스의 memberRequestList
변수는 list형태입니다. 따라서, memberRequestList[0], memberRequestList[1] ... 의 형식으로 키값을 지정합니다.
마무리
앞서 알아본 방식들중 3번째 방식을 이용한다면, 개발자에게 특정 조건을 기억하도록 강요하지 않습니다. 개발자는 항상 코드를 통해 어플리케이션을 표현해야합니다.
'FrameWork > Spring Boot' 카테고리의 다른 글
Spring Boot - Service Layer 테스트하기 (5) | 2020.09.23 |
---|---|
Spring Boot - REST API 인증 - 2 (Refresh Token이란?) (0) | 2020.07.03 |
Spring Boot - Mockito를 이용해 외부라이브러리를 이용하는 서비스 테스트하기 (0) | 2020.06.26 |
Spring Boot - FCM Push 서버 구축하기 (15) | 2020.06.12 |
Spring Boot - rest docs 사용방법과 자동 목차생성(spring boot restdocs 설정) (2) | 2020.06.03 |