728x90
반응형
유효성 체크를 위한 Validation API 사용
Validation API
- Hibernate 라이브러리에 포함된 기능
데이터베이스 관련 어플리케이션 개발
-> Java의 어떤 객체를 데이터베이스 엔티티하고 매핑하기 위해 사용되는 프레임워크
// pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.2.1</version>
</dependency>
// User
package com.example.myrestfulservice.bean;
import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.Size;
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(message = "등록일은 미래 날짜로 입력하실 수 없습니다.") // 과거 데이터만 들어올 수 있음
private Date joinDate;
}
// UserController
// @Valid 추가
@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
User savedUser = service.save(user);
// 현재 request 정보 -> URI 생성
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}") // 경로 지정
.buildAndExpand(savedUser.getId()) // 저장되어 있는 유저 값
.toUri(); // 전체 정보 -> URI
return ResponseEntity.created(location).build(); // 반환값 201
}
// CustomizedResponseEntityExceptionHandler
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {
ExceptionResponse exceptionResponse =
new ExceptionResponse(new Date(), "Validation Failed", ex.getBindingResult().toString()); // 현재 들어가 있는 에러 메세지 출력
return new ResponseEntity(exceptionResponse, HttpStatus.BAD_REQUEST);
}
다국어 처리 위한 Internationalization 구현 방법
Internationalization
- 지역코드 or 언어설정 -> 적절한 언어 표시(X -> 기본값)
- 특정 컨트롤러에서만 처리 X, 프로젝트 전반에 적용 -> SpringBoot 어플리케이션 클래스에 등록
=> SpringBoot가 초기화 될 때 메모리에 등록해서 사용할 수 있도록 함
- @Configuration 등록
- LocalResolver
- Default Locale
- Locale.US or Locale.KOREA
- ResourceBundleMessageSource
- Usage
- generate message budle files
- @Autowired MessageSource: 주입
- @RequestHeader(value="Accept-Language", required=false) Local local
- MessageSource.getMessage("greeting.message", null, local)
// MyRestfulServiceApplication
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
localeResolver.setDefaultLocale(Locale.US);
return localeResolver;
}
// HelloWorldController
private MessageSource messageSource;
// 생성자 ~> 주입
public HelloWorldController(MessageSource messageSource) {
this.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);
}
// resources -> application.yml
spring:
message:
basename: messages
// resources -> messages.properties
greeting.message=Hello
// messages_fr.properties
greeting.message=Bonjour
Response 데이터 형식 변환 - XML format
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>3.2.1</version>
</dependency>
Response 데이터 제어를 위한 Filtering
// User
package com.example.myrestfulservice.bean;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Date;
@Data
@AllArgsConstructor
@JsonIgnoreProperties(value = {"password", "ssn"})
public class User {
private Integer id;
@Size(min = 2, message = "Name은 2글자 이상 입력해 주세요.")
private String name;
@Past(message = "등록일은 미래 날짜로 입력하실 수 없습니다.") // 과거 데이터만 들어올 수 있음
private Date joinDate;
// @JsonIgnore
private String password;
// @JsonIgnore
private String ssn;
}
// UserDaoService
static {
users.add(new User(1, "Kenneth", new Date(), "test1", "111111-111111"));
users.add(new User(1, "Alice", new Date(), "test1", "222222-111111"));
users.add(new User(1, "Elena", new Date(), "test1", "333333-111111"));
}
프로그래밍으로 제어하는 Filtering - 개별 사용자 조회
// AdminUser
package com.example.myrestfulservice.bean;
import com.fasterxml.jackson.annotation.JsonFilter;
import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonFilter("UserInfo")
public class AdminUser {
private Integer id;
@Size(min = 2, message = "Name은 2글자 이상 입력해 주세요.")
private String name;
@Past(message = "등록일은 미래 날짜로 입력하실 수 없습니다.")
private Date joinDate;
private String password;
private String ssn;
}
package com.example.myrestfulservice.controller;
import com.example.myrestfulservice.bean.AdminUser;
import com.example.myrestfulservice.bean.User;
import com.example.myrestfulservice.dao.UserDaoService;
import com.example.myrestfulservice.exception.UserNotFoundException;
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.beans.BeanUtils;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/admin")
public class AdminUserController {
private UserDaoService service;
public AdminUserController(UserDaoService service) {
this.service = service;
}
// /admin/users/{id}
@GetMapping("/users/{id}")
public MappingJacksonValue retrieveUserAdmin(@PathVariable int id) {
User user = service.findOne(id);
AdminUser adminUser = new AdminUser();
if (user == null) {
throw new UserNotFoundException(String.format("ID[%s] not found", id));
} else {
// adminUser.setName(user.getName());
BeanUtils.copyProperties(user, adminUser); // source, target
}
// AdminUser의 JsonFilter를 프로그램 내에서 처리
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "ssn");
// UserInfo에 필터 적용
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
// 새로운 JSON 데이터 만들어서 반환
MappingJacksonValue mapping = new MappingJacksonValue(adminUser);
mapping.setFilters(filters);
return mapping;
}
}
프로그래밍으로 제어하는 Filtering - 전체 사용자 조회
// /admin/users
@GetMapping("/users")
public MappingJacksonValue retrieveAllUsersAdmin() {
List<User> users = service.findAll();
List<AdminUser> adminUsers = new ArrayList<>();
AdminUser adminUser = null;
for (User user : users) {
adminUser = new AdminUser();
BeanUtils.copyProperties(user, adminUser);
adminUsers.add(adminUser);
}
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "ssn");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
MappingJacksonValue mapping = new MappingJacksonValue(adminUsers);
mapping.setFilters(filters);
return mapping;
}
Version 관리 - URI를 이용한 버전관리
// v1 추가, v2
// /admin/v1/users/{id}
@GetMapping("/v1/users/{id}")
public MappingJacksonValue retrieveUserAdmin(@PathVariable int id) {
User user = service.findOne(id);
AdminUser adminUser = new AdminUser();
if (user == null) {
throw new UserNotFoundException(String.format("ID[%s] not found", id));
} else {
// adminUser.setName(user.getName());
BeanUtils.copyProperties(user, adminUser); // source, target
}
// AdminUser의 JsonFilter를 프로그램 내에서 처리
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "ssn");
// UserInfo에 필터 적용
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);
// 새로운 JSON 데이터 만들어서 반환
MappingJacksonValue mapping = new MappingJacksonValue(adminUser);
mapping.setFilters(filters);
return mapping;
}
// /admin/v2/users/{id}
@GetMapping("/v2/users/{id}")
public MappingJacksonValue retrieveUserAdminV2(@PathVariable int id) {
User user = service.findOne(id);
AdminUserV2 adminUser = new AdminUserV2();
if (user == null) {
throw new UserNotFoundException(String.format("ID[%s] not found", id));
} else {
BeanUtils.copyProperties(user, adminUser);
adminUser.setGrade("VIP");
}
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name", "joinDate", "grade");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfoV2", filter);
MappingJacksonValue mapping = new MappingJacksonValue(adminUser);
mapping.setFilters(filters);
return mapping;
}
// AdminUserV2
package com.example.myrestfulservice.bean;
import com.fasterxml.jackson.annotation.JsonFilter;
import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonFilter("UserInfoV2")
public class AdminUserV2 extends AdminUser{
private String grade;
}
Version 관리 - Parameter와 Header를 이용한 버전관리
Parameter
@GetMapping(value = "/users/{id}", params = "version=1")
@GetMapping(value = "/users/{id}", params = "version=2")
Header
@GetMapping(value = "/users/{id}", headers = "X-API-VERSION=1")
@GetMapping(value = "/users/{id}", headers = "X-API-VERSION=2")
mime-type
@GetMapping(value = "/users/{id}", produces = "application/vnd.company.appv1+json")
@GetMapping(value = "/users/{id}", produces = "application/vnd.company.appv2+json")
Versioning
- 일반 브라우저에서 실행 가능
- URI Versioning - Twitter
- Request Parameter versoining - Amazon
- 일반 브라우저에서 실행 불가
- (Custom) headers versioning - Microsoft
- Media type versioning (a.k.a "content negotiation" or "accept header") - Github
- 중요 포인트
- URI Pollution
- Misuse of HTTP Headers
- Caching
- Can we execute the request on the browser?
- API Documentation
728x90
반응형