쿠릉쿠릉 쾅쾅
쿠릉쿠릉 쾅쾅
쿠릉쿠릉 쾅쾅
250x250
전체 방문자
오늘
어제
  • 분류 전체보기
    • HTML CSS
    • 잡담
    • 프로그래밍 꿀팁 사이트
    • 코딩 도서
    • [자바]
      • 디자인 패턴
      • 자바의 정석 - 3판
      • 자바
      • 자바 문법
    • git
    • [TDD]
    • 개발 서적 독후감
      • 클린 코더
      • 토비 스프링3
      • 객체지향의 사실과 오해
      • 모던 자바 인 액션
      • 엘레강트 오브젝트
    • CS
      • 운영체제
      • HTTP
    • [SQL]
      • SQL 기초
      • 혼자공부하는SQL
    • [ Spring ]
      • REST API
      • Spring Toy
      • Spring 에러
      • Spring
      • Spring 입문
      • Spring 핵심 원리
      • SpringMVC 1편
      • SpringMVC 2편
      • Spring Boot를 이용한 RESTful We..
      • Batch
    • [JPA]
      • JPA
      • JPA 에러
      • JPA 프로그래밍 - 기본편
      • 스프링 부트와 JPA 활용 1 - 웹 애플리케이..
      • 실전! 스프링 부트와 JPA 활용2 - API 개..
      • 실전! 스프링 데이터 JPA
      • 실전! Querydsl
    • 인텔리제이
    • [DB]
      • DB
      • H2
    • Gradle
    • 면접
    • [알고리즘]
      • 알고리즘
      • 자료구조
      • 자바 알고리즘 공부
    • [프로젝트]
    • 쿠릉식 객체지향 사고
    • 리눅스

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 백준
  • 재귀
  • REST API
  • 스프링부트
  • 깃허브
  • java
  • MVC
  • 함수형인터페이스
  • 스프링
  • 자바
  • Git
  • 자료구조
  • Spring
  • SQL
  • http
  • springboot
  • JPA
  • 알고리즘
  • querydsl
  • GitHub

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
쿠릉쿠릉 쾅쾅

쿠릉쿠릉 쾅쾅

[Spring] Rest Api Version 관리
[ 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

'[ 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
    '[ Spring ]/REST API' 카테고리의 다른 글
    • [Spring] Rest API Documentation을 위한 Swagger 사용
    • [Spring] API 구현을 위한 Hateoas 적용
    • [Spring] Response 데이터 제어를 위한 Filtering
    • [Spring] AOP를 이용한 Exception Handing
    쿠릉쿠릉 쾅쾅
    쿠릉쿠릉 쾅쾅
    깃허브 주소 : https://github.com/kureung

    티스토리툴바