본문 바로가기

Programming/Spring boot

섹션 3. RESTful Service 기능 확장

* 인프런의 Spring Boot를 이용한 RESTful Web Services 개발 강의를 듣고 정리한 내용입니다.

섹션 3에서 다루는 것

  • Validation
  • Internationalization
  • XML format으로 반환하기
  • Filtering
  • Version 관리

유효성 체크를 위한 Validation API 사용

사용자 입력값의 유효성을 체크하려면 어떤 코드를 추가해야 하는지 배울 수 있다.

User.java 파일 변경 내용

package com.example.demo.user;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.Date;

@Data
@AllArgsConstructor
public class User {
    private Integer id;
    @Size(min=2, message = "Name은 2글자 이상 입력해 주세요.")
    private String name;
    @Past
    private Date joinDate;
}

name에 최소 글자 수와 message를 추가했다.

 

CustomizedResponseEntityExceptionHandler.java 파일 추가 내용

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                                  HttpHeaders headers,
                                                                  HttpStatusCode status,
                                                                  WebRequest request) {
        ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(),
                "Validation Failed", ex.getBildingResult().toString());

        return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
    }

@Override 어노테이션으로 handleMethodArgumentNotValid 함수를 재정의했다.

 

UserController.java 파일 변경 내용

@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
    User savedUser = service.save(user);

    URI location = ServletUriComponentsBuilder.fromCurrentRequest()
            .path("/id")
            .buildAndExpand(savedUser.getId())
            .toUri();

    return ResponseEntity.created(location).build();
}

@Valid 라는 어노테이션을 @RequestBody 앞에 추가시켜준다.

다국어 처리를 위한 Internationalization 구현 방법

DemoApplication.java 파일 추가 내용

    @Bean
    Public LocaleResolver localeResolver() {
        SessionLocaleResolver localeResolver = new SessionLocaleResolver();
        localeResolver.setDefaultLocale(Locale.KOREA);
        return localeResolver;
    }

application.yml 파일 추가 내용

spring:
  messages:
    basename:messages

resources 경로 아래에 messages.properties 파일 추가

greeting.message=안녕하세요

messages_fr.properties 파일 내용

greeting.message=Bonjour

messages_en.properties 파일 내용

greeting.message=Hello

HelloWorldController.java 파일 추가 내용

    @Autowired
    private MessageSource messageSource;

@GetMapping(path = "/hello-world-internationalized")
public String helloWorldInternationalized(
        @RequestHeader(name="Accept Language", required=false) Locale locale) {
    return messageSource.getMessage("greeting.message",null, locale);
}

이렇게 위의 파일들을 각각 생성 및 수정 후 Postman에서 hello-world-internationalized 경로로 접속한 후 요청 Headers에 Accept-Language 를 추가하여 VALUE에 en, fr 등으로 변경하면 Hello와 Bonjour가 출력되는 것을 확인 가능하다.

Response 데이터 형식 변환 - XML format

pom.xml 파일 내용 추가

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.10.2</version>
</dependency>

위의 내용을 추가한 후 Postman으로 application/xml 형식으로 send를 하면 오류나지 않고 출력에 성공하는 것을 확인할 수 있다.

Response 데이터 제어를 위한 Filtering

User.java 추가 내용

  @JsonIgnoreProperties(value={"password"})
    private String password;
    private String ssn;

어노테이션 @JsonIgnoreProperties 는 클래스 정의 전에 작성해주고 password와 ssn은 클래스 내부에 작성한다.

 

UserDaoService.java 파일 수정 내용

    static {
        users.add(new User(1,"Kenneth", new Date(), "pass1", "701010-1111111"));
        users.add(new User(2,"One", new Date(),"pass2", "801010-2222222"));
        users.add(new User(3,"Two", new Date(),"pass3", "901010-1111111"));
    }

새로 추가한 password와 ssn 값을 추가했다.

그 결과 Postman으로 보면 /users 경로에서 password 값은 보이지 않고 id, name, joinDate, ssn 4가지 값만 모든 사용자에 대해 출력되는 것을 알 수 있다.

프로그래밍으로 제어하는 Filtering 방법 - 개별 사용자 조회

AdminUserController.java 파일 내용

package com.example.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 org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/admin")
public class AdminUserController {
    private UserDaoService service;
    //의존성 주입
    //스프링에서 빈을 의존성 주입으로 관리한다.

    public AdminUserController(UserDaoService service) {
        this.service = service;
    }

    @GetMapping("/users")
    public List<User> retrieveAllUsers() {
        return service.findAll();
    }


    @GetMapping("/users/{id}")
    public MappingJacksonValue retrieveUser(@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","joinDate","ssn");

        FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter)

        MappingJacksonValue mapping = new MappingJacksonValue(user);
        mapping.setFilters(filters);

        return mapping;
    }
}

 

User.java 파일 수정 및 추가 내용

//@JsonIgnoreProperties(value={"password"})
@JsonFilter("UserInfo")

어노테이션 @JsonFilter를 추가해준다. UserInfo로 지정해준다.

AdminUserController.java 파일에서 addFilter로 추가해주고 setFilters 등 필요한 내용을 추가한다.

Postman으로 /admin/users/1 경로에 접속하면 id, name, joinDate, ssn 4가지 정보가 잘 출력되는 것을 확인할 수 있다.

프로그래밍으로 제어하는 Filtering 방법 - 전체 사용자 조회

AdminUserController.java 파일 수정 내용

    @GetMapping("/users")
    public MappingJacksonValue retrieveAllUsers() {
        List<User> users = service.findAll();

        SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
                .filterOutAllExcept("id","name","joinDate","password");

        FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter)

        MappingJacksonValue mapping = new MappingJacksonValue(users);
        mapping.setFilters(filters);

        return mapping;
    }

전체 사용자를 조회하는 부분에 filtering 하는 코드를 추가했다.

Postman으로 /admin/users 경로에 접속하면 전체 사용자의 id, name, joinDate, password 값을 확인 가능하다.

URI를 이용한 REST API Version 관리

UserV2.java 파일 내용

package com.example.demo.user;

import com.fasterxml.jackson.annotation.JsonFilter;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
//@JsonIgnoreProperties(value={"password"})
@JsonFilter("UserInfoV2")
public class UserV2 extends User {
    private String grade;

}

AdminUserController.java 파일 추가 및 수정 내용

    @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","joinDate","ssn");

        FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter)

        MappingJacksonValue mapping = new MappingJacksonValue(user);
        mapping.setFilters(filters);

        return mapping;
    }

    @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.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;
    }

 

User.java 파일 추가 내용

@NoArgsConstructor

이렇게 3개의 파일을 추가 및 수정해주면 v1 버전과 v2 버전의 사용자 개별 조회에서 차이가 발생하게 된다.

Postman으로 /admin/v2/users/1 경로로 send를 보내면 Grade가 VIP라고 출력해주는 것을 알 수 있다.

Request Parameter와 Header를 이용한 API Version 관리

AdminUserController.java 수정 내용

//    @GetMapping("v1/users/{id}")
    @GetMapping(value = "/users/{id}", params = "version=1")
    
//    @GetMapping("v2/users/{id}")
    @GetMapping(value = "/users/{id}", params = "version=2")

value에 경로를 그대로 두고 params에 버전 정보를 적어준다.

 그 후 Postman으로 /admin/users/1/?version=2 로 테스트해보면 grade가 "VIP"로 나온다.

 

다음으로 Header를 이용한 API  Version 관리이다.

AdminUserController.java 수정 내용

//    @GetMapping(value = "/users/{id}", params = "version=1")
    @GetMapping(value = "/users/{id}", headers="X-API-VERSION=1")
    
//    @GetMapping(value = "/users/{id}", params = "version=2")
    @GetMapping(value = "/users/{id}", headers="X-API-VERSION=2")

headers 정보를 추가해준다.

그 후 Postman으로 headers에 X-API-VERSION 값을 1이나 2로 넣어서 테스트해보면 버전 별로 잘 나온다.

 

Media Type으로 Version 관리하기

AdminUserController.java 수정 내용

//    @GetMapping(value = "/users/{id}", headers="X-API-VERSION=1")
    @GetMapping(value="/users/{id}", produces = "application/vnd.company.appv1+json")
    
//    @GetMapping(value = "/users/{id}", headers="X-API-VERSION=2")
    @GetMapping(value="/users/{id}", produces = "application/vnd.company.appv2+json")

produces 정보를 추가해준다.

그 후 Postman으로 Accept에 application/vnd.company.appv2+json 값을 넣어서 테스트해보면 v2 버전으로 잘 나오는 것을 알 수 있다.