Optional 도입

Optional

기존 코드는, 아래와 같은 식으로 직접 Repository에서 요소를 반환하도록 되어 있었다. 이 경우, null 처리를 하는데 번거로움이 존재한다. Optional을 도입하면, 메서드 체이닝을 통해 쉽게 예외 상황을 처리할 수 있는 메서드를 stream을 사용하는 것 처럼 제공하기 때문에, 편리해진다.

public interface BookRepository {
    /**
     * 책 저장
     * @param bookName : 저장할 책 이름
     * @param isbn : 저장할 책 isbn
     */
    void save(String bookName, String isbn);

    /**
     * id로 책을 찾음
     * @param id : 책 id
     * @return Book
     */
    Book findById(Long id);

    /**
     * 이름으로 책을 찾음
     * @param name : 책 이름
     * @return Book
     */
    Book findByName(String name);

    /**
     * 모든 책 리턴
     * @return Book List
     */
    List<Book> findAll();

    /**
     * 책 삭제
     * @param bookId : 삭제할 책 id
     * @return 삭제한 책 리턴
     */
    Book remove(Long bookId);

    /**
     * 모든 내용 삭제
     */
    void clearAll();

    Book findByIsbn(String isbn);
}

Repository의 반환 타입을 다음과 같이 도입하였다.

public interface BookRepository {
    /**
     * 책 저장
     * @param bookName : 저장할 책 이름
     * @param isbn : 저장할 책 isbn
     */
    void save(String bookName, String isbn);

    /**
     * id로 책을 찾음
     * @param id : 책 id
     * @return Book
     */
    Optional<Book> findById(Long id);

    /**
     * 이름으로 책을 찾음
     * @param name : 책 이름
     * @return Book
     */
    Optional<Book> findByName(String name);

    Optional<Book> findByIsbn(String isbn);

    /**
     * 모든 책 리턴
     * @return Book List
     */
    List<Book> findAll();

    /**
     * 책 삭제
     * @param bookId : 삭제할 책 id
     * @return 삭제한 책 리턴
     */
    Optional<Book> remove(Long bookId);

    /**
     * 모든 내용 삭제
     */
    void clearAll();
}

코드의 변화

기존 코드는 다음과 같다.

@ResponseBody
@DeleteMapping("/books/{bookId}")
public ResponseEntity<JsonResponse> deleteBook(HttpSession session, @PathVariable("bookId") Long bookId) {
    if (adminUtils.isDefault(session)) {
        return new ResponseEntity<>(ErrorResponse.builder()
                .code("roleError")
                .message("권한이 없습니다.")
                .build(), HttpStatus.FORBIDDEN);
    }

    Book removed = adminService.deleteBook(bookId);

    if (removed == null) {
        return new ResponseEntity<>(ErrorResponse.builder()
                .message("삭제되지 않았습니다.")
                .build(), HttpStatus.FORBIDDEN);
    }

    return new ResponseEntity<>(JsonResponse.builder()
            .message("정상 삭제되었습니다.")
            .build(), HttpStatus.OK);

}

Optional을 도입하면 다음과 같이 개선이 가능하다

@ResponseBody
@DeleteMapping("/books/{bookId}")
public ResponseEntity<? extends JsonResponse> deleteBook(HttpSession session, @PathVariable("bookId") Long bookId) {
    if (adminUtils.isDefault(session)) {
        return new ResponseEntity<>(ErrorResponse.builder()
                .code("roleError")
                .message("권한이 없습니다.")
                .build(), HttpStatus.FORBIDDEN);
    }

    ResponseEntity<? extends JsonResponse> responseEntity = bookService.deleteBook(bookId)
            .map(removed -> new ResponseEntity<JsonResponse>(JsonResponse.builder()
                    .message("정상 삭제되었습니다.")
                    .build(), HttpStatus.OK))
            .orElseGet(() -> new ResponseEntity<JsonResponse>(ErrorResponse.builder()
                    .message("삭제되지 않았습니다.")
                    .build(), HttpStatus.FORBIDDEN));

    return responseEntity;

}

map은 Optional.empty(), 즉, Optional 안의 value 필드가 null이 아닐 경우에만 작동하고, orElseGet은 Optional.empty() 일 때만 작동한다. Optional을 사용하면, if와 같은 제어문을 사용하는 명령형 프로그래밍에서 함수형 프로그래밍으로 자연스럽게 패러다임 개선까지 가능하다.

댓글

개발자  김철준

백엔드 개발자 김철준의 블로그입니다.

주요 프로젝트