[ Spring ]/REST API

[Spring] Rest Api Version 관리

쿠릉쿠릉 쾅쾅 2022. 5. 10. 02:39
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

 

[Spring] 객체복사 BeanUtils.copyProperties() & 작동 원리

• 안녕하세요~ 이전에 운영하던 블로그 및 GitHub, 공부 내용을 정리하는 Study-GitHub 가 있습니다! • 네이버 블로그 • GitHub • Study-GitHub • 🐔 ✔ BeanUtils.copyProperties() 안녕하세요, 이번에..

zzang9ha.tistory.com

 

https://jeonghoon.netlify.app/Spring/SpringBoot12-manageVersion/

 

Spring boot) Rest API 버전 관리하기(URL, Request Parameter, Header)

Rest API 버전 관리하기 왜 Rest API의 버전을 관리해야할까? Rest API의 설계가 변경되거나 애플리케이션의 구조가 바뀔때마다 버전을 변경해서 사용해야 한다. 주의해야할 점 URI에 정보를 노출시키

jeonghoon.netlify.app

 

https://server-talk.tistory.com/183

 

MIME이란 무엇인가?

MIME이란 무엇인가? MIME이란? Multipurpose Internet Mail Extensions의 약자로 간략히 말씀을 드리면 파일 변환을 뜻한다고할 수 있습니다. MIME는 이메일과 함께 동봉할 파일을 텍스트 문자로 전환해서 이메

server-talk.tistory.com

 

728x90