728x90
Version 관리
Version 관리 중요한 점
- URI 주소가 지저분하거나 과도한 정보를 표기하지 말 것.
- 잘못된 헤더값을 사용하지 말 것
- 새로운 버전을 만들 때 이전 버전의 캐시를 지워야한다.
- API 문서를 제공하면 좋다.
1. URI를 이용한 방법
📌 User
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonFilter("UserInfo")
public class User {
private Long id;
@Size(min = 2, message = "Name은 2글자 이상 입력해 주세요.")
private String name;
@Past // 과거 날짜만 가능하도록 제약
private LocalDateTime joinDate;
private String password;
private String ssn; // 주민등록번호
}
📌 UserV2
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonFilter("UserInfoV2")
public class UserV2 extends User{
private String grade;
}
📌 AdminUserController
@RestController
@RequiredArgsConstructor
@RequestMapping("/admin")
public class AdminUserController {
private final UserDaoService service;
@GetMapping("/users")
public MappingJacksonValue retrieveAllUsers() {
List<User> users = service.findAll();
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "ssn");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
MappingJacksonValue mapping = new MappingJacksonValue(users);
mapping.setFilters(filters);
return mapping;
}
@GetMapping("/v1/users/{id}")
public MappingJacksonValue retrieveUserV1(@PathVariable Long id) {
Optional<User> userOptional = service.findOne(id);
if (userOptional.isEmpty()) {
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "ssn");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
User findUser = userOptional.orElse(null);
MappingJacksonValue mapping = new MappingJacksonValue(findUser);
mapping.setFilters(filters);
return mapping;
}
@GetMapping("/v2/users/{id}")
public MappingJacksonValue retrieveUserV2(@PathVariable Long id) {
Optional<User> userOptional = service.findOne(id);
if (userOptional.isEmpty()) {
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
// User -> UserV2
User findUser = userOptional.orElse(null);
UserV2 userV2 = new UserV2();
BeanUtils.copyProperties(findUser, userV2);
userV2.setGrade("VIP");
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "grade");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfoV2", filter);
MappingJacksonValue mapping = new MappingJacksonValue(userV2);
mapping.setFilters(filters);
return mapping;
}
}
BeanUtils.copyProperties()를 통해서 원본 객체를 복사할 수 있다. 이 메서드를 사용하려면 원본 객체에는 getter가 존재해야하고, 복사본 객체에는 setter가 존재해야 한다.
2. Request Parameter를 이용한 방법
📌 AdminUserController
package hello.demo.user;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequiredArgsConstructor
@RequestMapping("/admin")
public class AdminUserController {
private final UserDaoService service;
@GetMapping("/users")
public MappingJacksonValue retrieveAllUsers() {
List<User> users = service.findAll();
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "ssn");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
MappingJacksonValue mapping = new MappingJacksonValue(users);
mapping.setFilters(filters);
return mapping;
}
/**
* Request param을 이용한 방법
* - @GetMapping의 value 속성값에 uri 주소를 적고 마지막에 '/' 를 꼭 붙여야 한다.
* - @GetMapping의 params 속성값은 쿼리 파라미터로 전달한다.
* 예) /users/2/?version=1
*/
@GetMapping(value = "/users/{id}/", params = "version=1")
public MappingJacksonValue retrieveUserV1(@PathVariable Long id) {
Optional<User> userOptional = service.findOne(id);
if (userOptional.isEmpty()) {
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "ssn");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
User findUser = userOptional.orElse(null);
MappingJacksonValue mapping = new MappingJacksonValue(findUser);
mapping.setFilters(filters);
return mapping;
}
@GetMapping(value = "/users/{id}/", params = "version=2")
public MappingJacksonValue retrieveUserV2(@PathVariable Long id) {
Optional<User> userOptional = service.findOne(id);
if (userOptional.isEmpty()) {
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
// User -> UserV2
User findUser = userOptional.orElse(null);
UserV2 userV2 = new UserV2();
BeanUtils.copyProperties(findUser, userV2);
userV2.setGrade("VIP");
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "grade");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfoV2", filter);
MappingJacksonValue mapping = new MappingJacksonValue(userV2);
mapping.setFilters(filters);
return mapping;
}
}
3. Header를 이용한 방법
📌 AdminUserController
@RestController
@RequiredArgsConstructor
@RequestMapping("/admin")
public class AdminUserController {
private final UserDaoService service;
@GetMapping("/users")
public MappingJacksonValue retrieveAllUsers() {
List<User> users = service.findAll();
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "ssn");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
MappingJacksonValue mapping = new MappingJacksonValue(users);
mapping.setFilters(filters);
return mapping;
}
/**
* Header를 이용한 방법
* - @GetMapping의 headers 속성에는 임의의 헤더 값을 지정하면 된다.
*/
@GetMapping(value = "/users/{id}", headers = "X-API-VERSION=1")
public MappingJacksonValue retrieveUserV1(@PathVariable Long id) {
Optional<User> userOptional = service.findOne(id);
if (userOptional.isEmpty()) {
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "ssn");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
User findUser = userOptional.orElse(null);
MappingJacksonValue mapping = new MappingJacksonValue(findUser);
mapping.setFilters(filters);
return mapping;
}
@GetMapping(value = "/users/{id}", headers = "X-API-VERSION=2")
public MappingJacksonValue retrieveUserV2(@PathVariable Long id) {
Optional<User> userOptional = service.findOne(id);
if (userOptional.isEmpty()) {
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
// User -> UserV2
User findUser = userOptional.orElse(null);
UserV2 userV2 = new UserV2();
BeanUtils.copyProperties(findUser, userV2);
userV2.setGrade("VIP");
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "grade");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfoV2", filter);
MappingJacksonValue mapping = new MappingJacksonValue(userV2);
mapping.setFilters(filters);
return mapping;
}
}
4. MIME Type을 이용한 방법
음악 등의 바이너리 파일은 ASCII만으로 전송이 불가능했고 이를 텍스트로 변환한 것을 인코딩이라 한다.
MIME이란 Multipurpose Internet Mail Extension의 약자로 일종의 인코딩방식이다.
다.
MIME은 이메일과 함께 동봉할 파일을 텍스트 문자로 전환해서 이메일 시스템을 통해 전달하기 위해 개발됐다. 하지만 현재는 웹을 통해서 여러 형태의 파일을 전달하는데 쓰이고 있다.
📌 AdminUserController
@RestController
@RequiredArgsConstructor
@RequestMapping("/admin")
public class AdminUserController {
private final UserDaoService service;
@GetMapping("/users")
public MappingJacksonValue retrieveAllUsers() {
List<User> users = service.findAll();
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "ssn");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
MappingJacksonValue mapping = new MappingJacksonValue(users);
mapping.setFilters(filters);
return mapping;
}
/**
* MIME을 이용한 방법
* - application/vnd.company.appv1+json
* - application/vnd : MIME의 타입 종류 중 하나다.
* - .company.applyv1+json : 사용자 임의로 값을 넣으면 된다.
* 이 방법도 header 값을 전달하여 사용해야 한다.
*/
@GetMapping(value = "/users/{id}", produces = "application/vnd.company.appv1+json")
public MappingJacksonValue retrieveUserV1(@PathVariable Long id) {
Optional<User> userOptional = service.findOne(id);
if (userOptional.isEmpty()) {
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "ssn");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
User findUser = userOptional.orElse(null);
MappingJacksonValue mapping = new MappingJacksonValue(findUser);
mapping.setFilters(filters);
return mapping;
}
@GetMapping(value = "/users/{id}", produces = "application/vnd.company.appv2+json")
public MappingJacksonValue retrieveUserV2(@PathVariable Long id) {
Optional<User> userOptional = service.findOne(id);
if (userOptional.isEmpty()) {
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
// User -> UserV2
User findUser = userOptional.orElse(null);
UserV2 userV2 = new UserV2();
BeanUtils.copyProperties(findUser, userV2);
userV2.setGrade("VIP");
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "grade");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfoV2", filter);
MappingJacksonValue mapping = new MappingJacksonValue(userV2);
mapping.setFilters(filters);
return mapping;
}
}
👀 참고 자료
https://zzang9ha.tistory.com/304
https://jeonghoon.netlify.app/Spring/SpringBoot12-manageVersion/
https://server-talk.tistory.com/183
728x90
'[ Spring ] > REST API' 카테고리의 다른 글
[Spring] Rest API Documentation을 위한 Swagger 사용 (0) | 2022.05.10 |
---|---|
[Spring] API 구현을 위한 Hateoas 적용 (0) | 2022.05.10 |
[Spring] Response 데이터 제어를 위한 Filtering (0) | 2022.05.09 |
[Spring] AOP를 이용한 Exception Handing (0) | 2022.05.08 |
[Spring] URI 조립을 위한 ServletUriComponentsBuilder (0) | 2022.05.08 |