이번 시간에는 DTO를 Entity로 변환하던 코드를 수정해보도록 하겠습니다.
* 요즘에는 계속 코드 수정만 하고 있습니다.
생각도 못했는데, 의외로 찾아봐주시는 분들이 계시더라고요.
미숙한 코드를 보여드렸던 것이 죄송스러울 따름이고, 뒤늦게라도 최대한 개선해보고자 합니다.
너그럽게 이해해주시길 바랍니다.
먼저 문제가 되는 코드를 살펴보겠습니다.
// CommentCreateRequest.java
public class CommentCreateRequest {
...
public static Comment toEntity(CommentCreateRequest req, MemberRepository memberRepository, PostRepository postRepository, CommentRepository commentRepository) {
return new Comment(
req.content,
memberRepository.findById(req.memberId).orElseThrow(MemberNotFoundException::new),
postRepository.findById(req.postId).orElseThrow(PostNotFoundException::new),
Optional.ofNullable(req.parentId)
.map(id -> commentRepository.findById(id).orElseThrow(CommentNotFoundException::new))
.orElse(null)
);
}
}
요청으로 전달받은 DTO를 Entity로 변환하는 코드입니다.
CommentCreateRequest.toEntity에서 Repository를 전달받으며 Entity를 생성해주고 있었습니다.
하지만 다시 생각해보니, DTO에서 Repository를 의존하는 것은 좋은 설계가 아닌 것 같습니다.
Entity 생성 과정도 하나의 비즈니스 로직으로 본다면, DTO가 비즈니스 로직을 가지게 된 것과 다름없기 때문입니다.
서비스 코드를 최대한 간결하게 유지해보고자 DTO -> Entity 변환 책임을 마땅한 곳에 부여해주고 싶었는데,
차라리 컨트롤러에서 Entity를 전달해주든지, 컨트롤러와 서비스 사이에 Entity 변환을 위한 새로운 계층을 구성한다든지, 별도 객체의 책임으로 빼버리든지 다양한 대안이 있었을 것입니다.
우리는 그냥 서비스 코드에서 Entity를 생성해주도록 하겠습니다.
기존의 서비스 코드가 그렇게 복잡한 것도 아니고, 굳이 번거로운 방법을 택할 필요는 없을 것입니다.
CommentCreateRequest.toEntity는 제거해주고, CommetService.create에서 Comment Entity를 생성해주도록 하겠습니다.
// CommentService.java
@Transactional
public void create(CommentCreateRequest req) {
Member member = memberRepository.findById(req.getMemberId()).orElseThrow(MemberNotFoundException::new);
Post post = postRepository.findById(req.getPostId()).orElseThrow(PostNotFoundException::new);
Comment parent = Optional.ofNullable(req.getParentId())
.map(id -> commentRepository.findById(id).orElseThrow(CommentNotFoundException::new))
.orElse(null);
Comment comment = commentRepository.save(new Comment(req.getContent(), member, post, parent));
comment.publishCreatedEvent(publisher);
}
가독성을 위해 Comment 생성자의 인자는, 중간 변수를 두었습니다.
다른 DTO들도 동일하게 적용해봅시다.
XXXCreateRequest.toEntity는 제거하고, 해당 코드를 XXXService.create에 그대로 옮겨주면 됩니다.
단순한 작업이므로, 이에 대한 코드는 생략하겠습니다.
테스트를 수행해보니, 갑자기 예외가 던져지고 있네요.
org.springframework.dao.InvalidDataAccessApiUsageException: For queries with named parameters you need to use provide names for method parameters. Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters.; nested exception is java.lang.IllegalStateException: For queries with named parameters you need to use provide names for method parameters. Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters.
다른 부분은 건드린 곳이 없는데, 엉뚱하게 RepositoryTest에서 예외가 던져지고 있습니다.
해결책은 다 제시되어있어서 문제될 것은 없지만, 갑자기 이러는 이유가 무엇인지는 잘 모르겠네요.
프로젝트는 자바 11로 진행되고 있고, 인텔리제이에서 invalidate caches/restart를 한 번 수행했었는데, javac flag에 -parameters가 지정되어있던걸까요.
아무튼 원인은 명확하니, Repository의 메소드 파라미터에 @Param을 지정해주도록 하겠습니다.
이에 대한 코드는 생략하겠습니다.
* 확인해보니 인텔리제이의 빌드 설정에서 Build and run을 Gradle에서 Intellij로 변경했었네요. 빌드 방식의 차이가 있나봅니다. Spring Data JPA에서는 위치 기반으로 파라미터를 바인딩하기 때문에, 리팩토링 과정에서 이러한 예외가 발생할 수도 있다고 합니다. 물론, 지금은 연관된 코드를 건드리진 않았지만, 함께 전달되는 Pageable로 인해 제대로 바인딩되지 않았던 것이라고 판단됩니다. 이 외에도, Build and run 설정이 바뀌면 QueryDSL로 생성된 클래스들의 저장 경로도 달라지도록 설정되어있으므로, @Param을 선언한 코드는 유지하되, Build and run 설정은 Gradle로 다시 되돌려두었습니다. 이에 대한 내용은 개인의 선택에 맞게 조절하시면 되겠습니다.
이번 시간에는 DTO를 Entity로 변환하는 코드를 수정하였습니다.
서비스 로직을 간결하게 유지하고자, 이에 대한 책임을 부여할 마땅한 장소를 찾는 과정에서 일어나버린 잘못된 결과였습니다.
결국 DTO에서 Entity를 생성하던 스태틱 메소드는 제거되었고, 서비스 코드에서 Entity를 직접 생성해주게 되었습니다.
DTO에 작성된 비즈니스 로직도 제거될 수 있었습니다.
* 질문 및 피드백은 환영입니다.
* 전체 소스코드에서는 여기에서 확인해볼 수 있습니다.
'Spring > 게시판 만들기' 카테고리의 다른 글
스프링부트 게시판 API 서버 만들기 (37) - API 접근 제어 방식 수정 (4) | 2022.01.08 |
---|---|
스프링부트 게시판 API 서버 만들기 (35) - 토큰 인증 방식 수정 (2) | 2022.01.04 |
스프링부트 게시판 API 서버 만들기 (34) - 국제화 (2) | 2022.01.02 |
스프링부트 게시판 API 서버 만들기 (33) - 알람 - 이벤트 다루기 (0) | 2022.01.01 |
스프링부트 게시판 API 서버 만들기 (32) - 쪽지 - 무한 스크롤 - 2 (0) | 2021.12.27 |