우리는 유저의 입력을 검증할 필요가 있다.
정규식을 이용한다던가, 간단히 조건문을 이용할 수 있겠다.
하지만 이번에는 비즈니스 로직과 Validation 파트를 분리해서 보겠다.
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Api<T> {
private String resultCode;
private String resultMessage;
@Valid
private T data;
private Error error;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public static class Error{
private List<String> errorMessage;
}
}
Inner Class를 이용해서 입력한 정보의 에러를 표시하도록 했다.
처음 보는 어노테이션은 @Valid로 해당 데이터의 Validation을 진행한다.
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class UserRegisterRequest {
@NotBlank
private String name;
@Size(min = 1, max = 12)
@NotBlank
private String password;
@NotNull
@Min(1)
@Max(100)
private Integer age;
@Email
private String email;
@Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$", message = "양식에 맞지 않습니다. ")
private String phoneNumber;
@FutureOrPresent
private LocalDateTime registerAt;
}
하나의 클래스를 유저 데이터로 만들고 위 내용을 잘 작성했는지 검증할 것이다.
데이터에 관해서 유효성을 검증하는 어노테이션들이 나타나있다. 링크텍스트
@PostMapping("")
public Api<UserRegisterRequest> register(
@Valid
@RequestBody
Api<UserRegisterRequest> userRegisterRequest
){
log.info("init : {}", userRegisterRequest);
var body = userRegisterRequest.getData();
var response = Api.<UserRegisterRequest>builder()
.resultCode(String.valueOf(HttpStatus.OK.value()))
.resultMessage(HttpStatus.OK.getReasonPhrase())
.data(body)
.build();
return response;
}
builder패턴으로 객체를 생성하는 것은 이제 익숙할 것이다.
해서 body에 입력한 데이터를 받아 response를 내려준다.
그런데 유저는 예상과 다르게 입력할 경우가 있다.
하지만 Controller에서 해당 예외를 처리하기에는 비즈니스 로직과 관련없는 코드가 불필요하게 길어진다.
@ExceptionHandler(value= {MethodArgumentNotValidException.class})
public ResponseEntity<Api> validationException(
MethodArgumentNotValidException exception
){
log.error("", exception);
var errorMessageList = exception.getFieldErrors().stream()
.map(it -> {
var format = "%s : { %s } 은 %s";
var message = String.format(format, it.getField(), it.getRejectedValue(), it.getDefaultMessage());
return message;
}).collect(Collectors.toList());
var error = Api.Error
.builder()
.errorMessage(errorMessageList)
.build();
var errorResponse = Api
.builder()
.resultCode(String.valueOf(HttpStatus.BAD_REQUEST.value()))
.resultMessage(HttpStatus.BAD_REQUEST.getReasonPhrase())
.error(error)
.build();
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(errorResponse);
}
해당 비즈니스 로직을 처리하기 위해서 예외를 처리하도록 했다.
Valid 어노테이션으로 검증한 데이터에 에러가 있으면 MethodArgumentNotValidException을 ControllerAdvice에 추가한다.
위 Inner Class로 정의한 Error는 동시에 여러가지가 발생할 수 있다.
발생한 error는 리스트로 저장해 어떤 데이터가 어떤 오류를 품고 있는지 Json 데이터에 포함시켜서 Client가 확인할 수 있도록 한다.
Exception에서 학습했던 status와 body를 error 내용을 담아 response로 내릴 수 있게 됐다.