개요

Validation(2) 에서는 Library Application 의 /library/books [post] 요청에 대한 validation을 순차적으로 진행합니다.

좀 더 나은 Validation

현재 코드 상황

@PostMapping("/library/books")
    public String bookAdd(@ModelAttribute(name="bookInsertRequest") BookInsertRequestDto bookInsertRequestDto, Model model) {

        int requestDtoNameLength = bookInsertRequestDto.getName().trim().length();
        int requestDtoAuthorLength = bookInsertRequestDto.getAuthor().trim().length();
        int requestDtoExpense = bookInsertRequestDto.getExpense();

        if(requestDtoNameLength < 1 || requestDtoNameLength > 30) {
            model.addAttribute("isError",true);
            model.addAttribute("message","책 이름은 1자 이상 30자 이하로 입력해주세요.");
            return "library/insert";
        }

        if (requestDtoAuthorLength < 1 || requestDtoAuthorLength > 30) {
            model.addAttribute("isError",true);
            model.addAttribute("message","저자 이름은 1자 이상 30자 이하로 입력해주세요");
            return "library/insert";
        }

        if (requestDtoExpense < 0) {
            model.addAttribute("isError",true);
            model.addAttribute("message","비용은 0원 이상으로 입력해주세요.");
            return "library/insert";
        }

        //추가적인 validation...

        bookService.createBook(bookInsertRequestDto);
        return "redirect:/library/books";
    }

현재 Validation 하는 부분은 몇가지 문제가 있어 보입니다.

  1. 코드의 중복

thymeleaf 의 logical address를 반환하는 부분이나, isError 를 model에 싣는 부분이 중복되어 보입니다.

  1. 한번에 하나의 에러 메시지밖에 보내지 못함

지금은 만약 3개의 부분에서 전부 유효하지 않은 값이 오더라도, 가장 상위에 있는 에러 메시지만 사용자에게 전달됩니다. 이를 해결한 사용자는 다시 요청을 보내지만 이제는 그 다음 에러를 마주하게 될 겁니다. 여러 에러를 한번에 사용자에게 전달할 수 있는 방법이 필요해 보입니다.

A post shared by 유머페이지 "모아" (@moa_humor)

  1. expense input 에 0

이건 validation에서 핵심적인 문제는 아닙니다. 하지만 expense의 input에 0이 들어있는것은 너무 열받습니다. 이것은 자바의 원시타입(primitive type)은 null을 가질 수 없어 0으로 초기화되고 그 값이 출력되기 때문입니다. 아주아주 열받으니 Wrapper 타입인 Integer로 변경시켜보도록 하겠습니다.

수정

먼저 가장 간단한 수정인 DTO의 input을 Integer 타입으로 변환해주겠습니다.

public class BookInsertRequestDto {

    private String name;
    private String author;
    **private Integer expense;**

    public Book toEntity() {
        return Book.builder()
                .name(name)
                .author(author)
                .registerDate(LocalDateTime.now().toString())
                .lent(0)
                .expense(expense)
                .build();
    }
}

이렇게만 바꿔주어도 큰 문제 없습니다. 대신 이제 expense 가 null이 될 수 있으므로, validation에서는 null check를 추가로 진행해줘야할 것입니다.

@PostMapping("/library/books")
    public String bookAdd(@ModelAttribute(name="bookInsertRequest") BookInsertRequestDto bookInsertRequestDto, Model model) {

        int requestDtoNameLength = bookInsertRequestDto.getName().trim().length();
        int requestDtoAuthorLength = bookInsertRequestDto.getAuthor().trim().length();

        Integer requestDtoExpense = bookInsertRequestDto.getExpense();

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

        if(requestDtoNameLength < 1 || requestDtoNameLength > 30) {
            errors.put("name","책 이름은 1자 이상 30자 이하로 입력해주세요.");
        }

        if (requestDtoAuthorLength < 1 || requestDtoAuthorLength > 30) {
            errors.put("author","저자 이름은 1자 이상 30자 이하로 입력해주세요.");
        }

        if (requestDtoExpense == null || requestDtoExpense < 0) {
            errors.put("expense","비용 입력은 필수이며, 0 이상이어야 합니다.");
        }

        if(!errors.isEmpty()) {
            model.addAttribute("errors",errors);
            return "library/insert";
        }

        //추가적인 validation...

        bookService.createBook(bookInsertRequestDto);
        return "redirect:/library/books";
    }

Validation error를 저장할 Map 자료형을 가진 errors 라는 변수를 만들었습니다. 이 변수는 validation 간에 발생한 모든 에러를 저장한 뒤, model에 담겨서 view에 전송될 것입니다. 이때에 key는 어떤 input에서 발생한 에러인지를 저장하게 되고, value는 에러 메시지를 저장하게 됩니다.