이 글은 정답이 아닌 개인적인 저의 생각 정리입니다...!
.
.
.
고민
개발을 하다보면 계속 Controller와 Service의 역할에 대한 의문이 들었다.
Controller가 Service에 있어야할 비즈니스 로직을 가지고 있게 된다고 생각했기때문이다.
(내가 그렇게 했기때문에 그런거지만... ㅠㅠ)
실제로 내가 겪은 구체적인 상황:
코드:
//기존 코드
@PostMapping
public ResponseEntity<CreateBookDto> createBook(@SessionAttribute(name = SessionConst.LOGIN_USER, required = false) User loginUser,
@RequestBody CreateBookDto createBookDto, UriComponentsBuilder b){
BookInfo bookInfo = BookInfo.builder()
.name(createBookDto.getName())
.isbn(createBookDto.getIsbn())
.build();
try {
bookInfoService.createBookInfo(bookInfo);
}catch (IllegalStateException e){
log.info("bookInfoService = {}",e.toString());
BookInfo byIsbn = bookInfoService.findByIsbn(bookInfo.getIsbn());
bookInfo = byIsbn;
}
Book newBook = Book.builder()
.category(createBookDto.getCategory())
.bookInfo(bookInfo)
.build();
Long bookId = bookService.createBook(newBook);
ClubBookUser cbu = ClubBookUser.builder()
.book(newBook)
.user(loginUser)
.build();
cbuService.createClubBookUser(cbu);
UriComponents uriComponents =
b.path("/books/{bookId}").buildAndExpand(bookId);
// return ResponseEntity.noContent().build().created(uriComponents.toUri()).build();
return ResponseEntity.created(uriComponents.toUri()).body(createBookDto);
}
코드에서 확인할수 있듯, 컨트롤러는 그냥 Book 엔티티를 생성하라는 단순한 요청만 해야 하는데,
그와 관련된 엔티티를 순서를 맞춰서 저장하는 복잡한 일을 한다.
일단 컨트롤러를 스윽 봤는데 저렇게 길고 복잡한 코드가 있으면...
이게 뭐하는 코드인지 한눈에 안들어오고 유지보수하기 어려울것이다.
"지켜야 하는 순서를 맞춰서 저장" 하는것도 컨트롤러에서 할 일이 아니라고 생각한다.
그래서 엔티티와 관련된 Service에서 그런 역할을 하는 방법으로 리팩토링하려 했다.
BookService에서 예시 코드의 복잡한 작업을 하자니 생긴 문제였다.
위에 있는 코드에서 보듯이 BookInfo, Book, ClubBookUser라는 3가지 엔티티가 있다.
BookService에서는 Book 자체와만 관련된 코드를 작성하고 싶다는 생각이 있었다.
해결방법
service를 주입받아서 사용만하는 service를 만들어서 컨트롤러에 있던 코드를 거기로 옮겨서 컨트롤러에 비즈니스 로직이 생기는걸 없에고 순환참조, 코드 중복 등을 제거 해봤다.
코드:
//수정한코드
@PostMapping
public ResponseEntity<CreateBookDto> createBook(@SessionAttribute(name = SessionConst.LOGIN_USER, required = false) User loginUser,
@RequestBody CreateBookDto createBookDto, UriComponentsBuilder b) {
Long bookId = bookComplexService.createBookAndRelated(createBookDto, loginUser.getId());
UriComponents uriComponents = b.path("/books/{bookId}").buildAndExpand(bookId);
return ResponseEntity.created(uriComponents.toUri()).body(createBookDto);
}
//서비스를 주입받는 서비스
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class BookComplexService {
private final BookInfoService bookInfoService;
private final BookService bookService;
private final ClubBookUserService cbuService;
private final UserService userService;
private final PostService postService;
@Transactional
public Long createBookAndRelated(CreateBookDto createBookDto, Long userId) {
BookInfo bookInfo = BookInfo.builder()
.name(createBookDto.getName())
.isbn(createBookDto.getIsbn())
.build();
bookInfo = bookInfoService.createOrFindBookInfo(bookInfo);
Book newBook = Book.builder()
.category(createBookDto.getCategory())
.bookInfo(bookInfo)
.build();
Long bookId = bookService.createBookWithValidation(newBook, bookInfo.getIsbn(), userId);
User user = userService.findById(userId);
ClubBookUser cbu = ClubBookUser.builder()
.book(newBook)
.user(user)
.build();
cbuService.createClubBookUser(cbu);
return bookId;
}
참고
1. 나와 비슷한 상황을 겪은 사람
(댓글까지 다 읽는거 추천)
https://velog.io/@sumusb/Spring-Service-Layer%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0
2. 김영한님께 질문했음
https://www.inflearn.com/questions/355565
'Dev > Spring' 카테고리의 다른 글
테스트 코드와 관련 프레임워크 (0) | 2022.04.14 |
---|---|
테스트 작성과 Jacoco (0) | 2022.04.13 |
[Lombok ] 클래스 단위로 @Builder 사용시 주의점 (0) | 2021.08.20 |
Servlet 서블릿에 대하여 (0) | 2021.07.17 |
9. 빈 스코프 (0) | 2021.03.28 |