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와 같은 제어문을 사용하는 명령형 프로그래밍에서 함수형 프로그래밍으로 자연스럽게 패러다임 개선까지 가능하다.