요청 데이터 검증 코드 개선

엉금엉금·2022년 7월 11일
0

오늘 만난 문제

목록 보기
12/24

프로젝트에서 검증 시 에러필드의 정보를 클라이언트에 전달할 로직을 더욱 편하게 개선하는 방법에 대한 글을 작성해봅니다.

목표

기존 'validation message'를 split하여 'field' 이름과 메시지를 분리하던 로직을 개선

기존 응답 부분

@RestControllerAdvice
public class RestaurantControllerAdvice {

	...
    
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public SaveRestaurantResponseDto handleMethodArgumentNotValidation(MethodArgumentNotValidException e) {
        return new SaveRestaurantResponseDto(getErrorData(e.getBindingResult()));
    }

    private Map<String, String> getErrorData(BindingResult bindingResult) {

        Map<String, String> data = new HashMap<>();

        bindingResult.getAllErrors()
                .forEach(objectError -> {
                    String[] errorArray = objectError.getDefaultMessage().split(":");
                    if (isNeededFieldName(errorArray[0])) {
                        data.put(errorArray[0], errorArray[1]);
                    }
                });

        return data;
    }

    private boolean isNeededFieldName(String fieldName) {
        return fieldName.equals("name") || fieldName.equals("minOrderPrice")
                || fieldName.equals("deliveryFee") || fieldName.equals("price")
                || fieldName.equals("quantity");
    }
}
@NotNull(message = "name:음식명은 필수 입력 항목입니다")
  • 해당 코드를 처음에 작성할 시점에는 검증에서 단순히 'code'와 'message'만을 응답하는 것 뿐만아니라 어떤 요청 데이터 필드에서 검증에 실패했는지 클라이언트에 응답하고 이후 잘 고쳐 요청할 수 있게 하자는 취지로 기능 구현을 했습니다.
  • 기존 메시지는 바로 위와 같이 콜론(':')을 기준으로 split할 수 있게 DTO클래스의 필드에 검증 어노테이션 'message' 옵션에 기록해두었다.
  • 'MethodArgumentNotValidException' 발생(클라이언트 요청 데이터 검증 실패 에러 발생) 후 피드백 용도의 데이터 만드는 부분을 'getErrorData'라는 메서드에서 볼 수 있는데, split을 통해 필드명과 에러 메시지를 분리하여 클라이언트에 에러 필드를 피드백하는 데이터를 만든다.

변경 응답 부분

  • 기존 부분은 검증이 필요한 DTO 혹은 필드가 늘어나면 더욱 복잡해질 것이라 판단해 아래와 같이 코드를 수정했다.
@RestControllerAdvice
public class RestaurantControllerAdvice {

	...

	@ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public RestaurantSaveResponseDto handleMethodArgumentNotValidation(MethodArgumentNotValidException e) {
        return new RestaurantSaveResponseDto(getErrorData(e.getBindingResult()));
    }

    private Map<String, String> getErrorData(BindingResult bindingResult) {
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        Map<String, String> validation = new HashMap<>();
        for (FieldError fieldError : fieldErrors) {
            validation.put(getCorrectFieldName(fieldError.getField()), fieldError.getDefaultMessage());
        }

        return validation;
    }

    private String getCorrectFieldName(String field) {
        if (field.contains(".")) {
            String[] strings = field.split("\\.");
            return strings[1];
        }
        return field;
    }
}
  • 검증 시 발생하는 예외는 'MethodArgumentNotValidException'인데 해당 예외 객체를 통해서 'BindingResult'를 얻을 수 있다.
  • 'BindingResult'를 통해서 필트 에러에 대한 정보를 얻어 필드명과 에러 메시지를 구할 수 있다.
  • 다만 필드에러가 한개인 곳에서는 'validation' 피드백 데이터를 예쁘게 잘 전달해주지만 여러개인 부분에서는 'field[0].필드명' 식으로 전달하는 경우를 발견해서 그에 대한 처리를 'getCorrectFieldName'이라는 메서드를 구현하여 필드명만 뽑아낼 수 있게 했다.

결론

  • 코드가 DTO클래스나 ExceptionHandler 부분에서 이전보다 간결해졌다.
  • 하지만 여전히 문제는 'error message'를 DTO클래스의 validation 어노테이션 메시지 옵션 부분에 기록하고 있다.
  • 따라서 다음에는 오류 메시지를 일관성 있게 모아 처리할 수 있는 방안을 알아보고 적용해봐야겠다.
profile
step by step

0개의 댓글