🍃 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

  • 페이스북 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;
}

v1 버전의 URI를 호출하자 정상적으로 작동하는 것 확인
v1 버전의 URI를 호출하자 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;
}