🍃 Spring/🌱 Spring Boot를 이용한 RESTful Web Service
[Spring Boot를 이용한 RESTful Web Services 개발] 25~26강
락꿈사
2022. 2. 9. 18:34
REST API Version 관리
- 사용자 API의 Version 관리하는 기능
- 페이스북 API와 카카오 API 모두 URI에 버전을 명시하고 있음
- API를 사용하는 개발자나 사용자에게 올바른 사용 가이드를 알려주기 위한 목적
- URI Versioning, Request Parameter Versioning 방법은 일반 브라우저에서 실행 가능함
- MIME type Versioning, Headers Versioning 방법은 일반 브라우저에서 실행 불가함
URI를 이용한 REST API Version 관리
- retrieveUser 메소드를 복사하여 retrieveUserV1과 retrieveUserV2를 생성
- 각 URI 앞에 v1, v2를 넣어서 버전 명시
- V1과 V2의 차이를 두기 위해서 V2에서는 UserV2라는 DTO 사용
// GET admin/users/1 --> /admin/v1/users/1 (버전 지정)
@GetMapping("/v1/users/{id}")
public MappingJacksonValue retrieveUserV1(@PathVariable int id){
User user = service.findOne(id);
if (user == null){
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "password", "ssn");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
MappingJacksonValue mapping = new MappingJacksonValue(user);
mapping.setFilters(filters);
return mapping;
}
// GET admin/users/1 --> /admin/v2/users/1 (버전 지정)
@GetMapping("/v2/users/{id}")
public MappingJacksonValue retrieveUserV2(@PathVariable int id){
User user = service.findOne(id);
if (user == null){
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
// 빈환받았던 User 값을 User2로 변경
UserV2 userV2 = new UserV2();
// BeanUtils 스프링 프레임워크에서 제공하는 클래스로 두 인스턴스 간의 작업을 도와. user의 필드의 값이 user2에 복사됨
BeanUtils.copyProperties(user, userV2);
// userV2에서 사용자의 등급 관리
userV2.setGrade("VIP");
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "password", "grade"); // 포함시키고자 하는 필터 선언. "id", "name", "password", "grade" 전달
// filter를 추가할 때는 어떠한 Bean을 대상으로 추가된 필터인지 Bean의 이름을 써줘야 하는데, UserV2 클래스에 @JsonFilter("name") 어노테이션의 "name"값을 첫번째 인자로 넣어줌
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfoV2", filter);
// MappingJacksonValue 객체에 userV2 전달
MappingJacksonValue mapping = new MappingJacksonValue(userV2);
// mapping에 필터 적용
mapping.setFilters(filters);
return mapping;
}
- User를 상속받은 UserV2 클래스 생성
- 이 때 User 클래스에 매개변수가 없는 Default 생성자가 없으면 @AllArgsConstructor 어노테이션에서 에러가 발생하므로 User 클래스에 돌아가서 @NoArgsConstructor 어노테이션을 추가해서 Default 생성자 생성
package com.example.restfulwebservice.user;
import com.fasterxml.jackson.annotation.JsonFilter;
import lombok.AllArgsConstructor;
import lombok.Data;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import java.util.Date;
@Data
@AllArgsConstructor
@JsonFilter("UserInfoV2")
@NoArgsConstructor
public class UserV2 extends User{ // User 클래스를 상속받아 User 클래스의 모든 속성 사용
private String grade;
}
Request Parameter를 이용한 API Version 관리
- @GetMapping("URI")를 주석처리하고 @GetMapping(value="URI", params = "version=1")을 작성 (version2 메소드에도 버전에 맞게 작성)
- 사용하려고 하는 URI 뒤에 Request Parameter 값으로 버전 정보를 명시하는 방법
@GetMapping(value = "/users/{id}/", params = "version=1")
public MappingJacksonValue retrieveUserV1(@PathVariable int id){
User user = service.findOne(id);
if (user == null){
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "password", "ssn");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
MappingJacksonValue mapping = new MappingJacksonValue(user);
mapping.setFilters(filters);
return mapping;
}
@GetMapping(value = "/users/{id}/", params = "version=2")
public MappingJacksonValue retrieveUserV2(@PathVariable int id){
User user = service.findOne(id);
if (user == null){
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
UserV2 userV2 = new UserV2();
BeanUtils.copyProperties(user, userV2);
userV2.setGrade("VIP");
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "password", "grade");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfoV2", filter);
MappingJacksonValue mapping = new MappingJacksonValue(userV2);
mapping.setFilters(filters);
return mapping;
}
Header를 이용한 REST API Version 관리
- @GetMapping(value="URI", params = "version=1")를 주석처리하고 @GetMapping(value="URI", headers = "X-API-VERSION=1")을 작성 (version2 메소드에도 버전에 맞게 작성)
- header 값에 버전을 명시하는 방법
@GetMapping(value = "/users/{id}", headers = "X-API-VERSION=1")
public MappingJacksonValue retrieveUserV1(@PathVariable int id){
User user = service.findOne(id);
if (user == null){
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "password", "ssn");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
MappingJacksonValue mapping = new MappingJacksonValue(user);
mapping.setFilters(filters);
return mapping;
}
@GetMapping(value = "/users/{id}", headers = "X-API-VERSION=2")
public MappingJacksonValue retrieveUserV2(@PathVariable int id){
User user = service.findOne(id);
if (user == null){
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
UserV2 userV2 = new UserV2();
BeanUtils.copyProperties(user, userV2);
userV2.setGrade("VIP");
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "password", "grade");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfoV2", filter);
MappingJacksonValue mapping = new MappingJacksonValue(userV2);
mapping.setFilters(filters);
return mapping;
}
MIME 타입을 이용한 API Version 관리
- MIME 타입은 Multipurpose Internet Mail Extensions의 약자로 이메일과 함께 전송되는 메일을 텍스트 문자로 변환해서 이메일 서버로 전달하기 위한 방법
- 최근에는 웹을 통해서 여러가지 파일을 전달하기 위해 사용되는 일종의 파일 형식 지정
- @GetMapping(value="URI", params = "version=1")주석처리하고 @GetMapping(value="URI", produces="application/vnd.company.appv1+json") 추가 (version2 메소드에도 버전에 맞게 작성)
@GetMapping(value = "/users/{id}", produces = "application/vnd.company.appv1+json")
public MappingJacksonValue retrieveUserV1(@PathVariable int id){
User user = service.findOne(id);
if (user == null){
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "password", "ssn");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
MappingJacksonValue mapping = new MappingJacksonValue(user);
mapping.setFilters(filters);
return mapping;
}
@GetMapping(value = "/users/{id}", produces = "application/vnd.company.appv2+json")
public MappingJacksonValue retrieveUserV2(@PathVariable int id){
User user = service.findOne(id);
if (user == null){
throw new UserNotFoundException(String.format("ID[%s] not found", id));
}
UserV2 userV2 = new UserV2();
BeanUtils.copyProperties(user, userV2);
userV2.setGrade("VIP");
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "password", "grade");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfoV2", filter);
MappingJacksonValue mapping = new MappingJacksonValue(userV2);
mapping.setFilters(filters);
return mapping;
}