developers.facebook.com/docs/apps/versions
버전 관리라는 것은 단순히 사용자에게 보여지는 화면만을 제어하는 것이 아니라
REST API의 설계가 변경되거나, application의 구조가 바뀔 때에도 버전을 변경해서 사용해야한다.
또한 사용자에게도 어떠한 API의 버전을 사용하는지 적절히 명시해주어야한다.
Versioning
1) URI Versioning - 일반 브라우저에서 실행 가능
2) Request Parameter Versioning - 일반 브라우저에서 실행 가능
- Amazon
3) (Custom) Headers Versioning - 일반 브라우저에서 실행 불가
- Microsoft
4) Media Type Versioning - 일반 브라우저에서 실행 불가
(a.k.a "content negotiation" or "accept header")
- GitHub
Factors
- URI Pollution
- Misuse of HTTP Headers
- Caching
- Can we execute the request on the browser?
- API Documentation
No Perfect Solution!
(공통) User를 상속받는 UserV2 클래스 생성
// UserV2
package com.example.restfulwebservice.user;
import com.fasterxml.jackson.annotation.JsonFilter;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonFilter("UserInfoV2")
public class UserV2 extends User{
private String grade; // 회원 등급
}
1) URI에 버전을 포함시켜 버전을 관리하는 방법
- AdminUserController version1
// 개별 사용자 조회
// GET방식 /admin/users/1 - /admin/v1/users/1 (버전관리 URI)
@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;
}
- AdminUserController version2
// GET방식 /admin/users/1 - /admin/v2/users/1 (버전관리 URI)
@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));
}
// 반환받았던 데이터를 UserV2로
UserV2 userV2 = new UserV2();
// BeanUtils : 스프링 프레임워크에서 제공하는 bean들간의 작업을 도와주는 클래스
BeanUtils.copyProperties(user,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;
}
2) Request Parameter를 이용한 버전 관리 방법 params =""
// AdminUserController
@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 userV2 = new UserV2();
// BeanUtils : 스프링 프레임워크에서 제공하는 bean들간의 작업을 도와주는 클래스
BeanUtils.copyProperties(user,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
@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 userV2 = new UserV2();
// BeanUtils : 스프링 프레임워크에서 제공하는 bean들간의 작업을 도와주는 클래스
BeanUtils.copyProperties(user,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을 이용해 버전을 관리하는 방법
- Multipurpose Internet Mail Extensions : 간단히 말해 파일 변환
- 인코딩 : 바이너리 파일에서 텍스트 파일로 변환
- 디코딩 : 텍스트 파일에서 바이너리 파일로 변환
// AdminUserController
@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 userV2 = new UserV2();
// BeanUtils : 스프링 프레임워크에서 제공하는 bean들간의 작업을 도와주는 클래스
BeanUtils.copyProperties(user,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;
}