[Spring] Dto를 어떻게 관리할까?

gwonsang247·2024년 1월 28일
0

Java

목록 보기
10/11
post-custom-banner

한 주 동안 기능 구현에 빠져서 API를 개발만 진행했더니 팀원들 개개인이 작성한 코드와 내 코드 모두 작성자의 개성을 너무 드러내고 있었다... 일단 당장의 문제는 DTO 였다. 대부분의 View - Controller / Service - Controller 에서 DTO를 사용하고 있었고 심지어는 Controller - Service 처럼 부가적으로 더 많은 DTO를 사용하는 메서드들도 있었다. 이러다보니 DTO 파일이 구분없이 너무 많은 상태였다.

일단 Request, Response, 그리고 메서드에서 사용되는 DTO를 Common으로 분리하긴 했지만 절대 유지관리가 쉬울 것 같지는 않다.

그래서 이번 리팩토링을 진행하면서 DTO의 사용에 대해 고민해보고 관리하는 방법을 정리해보려한다.

💡 Inner Class

DTO 관리에 대한 키워드로 검색하며 가장 먼저 뜨는 내용이다.
같은 도메인에서 사용하는 API들이 호출하는 DTO들을 한 파일에 두어서 관리를 하는 방법이다. 아래와 같이 DTO 클래스를 작성하고

/* ReviewDTO */
public class ReviewDTO {

    @Getter
    @Setter
    /* Review Create할 때 쓸 DTO => ReviewDTO.Create */
    public static class Create {
        private Long tutoringId;
        private String body;
        private Long tagId;
    }

    @Getter
    @Setter
    /* Review Update할 때 쓸 DTO => ReviewDTO.Update */
    public static class Update {
        private String body;
        private Long tagId;
    }

    @Getter
    @Setter
    /* Review Check할 때 쓸 DTO => ReviewDTO.Check */
    public static class Check {
        private Boolean isCompleted;
    }

    @Getter
    @Setter
    /* Review 반환할 때 쓸 DTO => ReviewDTO.Response */
    public static class Response {
        private Long id;
        private String body;
        private Boolean isCompleted;
        private ReviewNoteDTO note;
        private ReviewTagDTO tag;

        /* Entity -> DTO Response */
        public Response(Review review) {
            this.id = review.getId();
            this.body = review.getBody();
            this.isCompleted = review.getIsCompleted();
            this.note = new ReviewNoteDTO(review.getNote());
            this.tag = new ReviewTagDTO(review.getTag());
        }

        /* Entity List -> DTO Response List */
        public static List<Response> ResponseList (List<Review> reviewList) {
            List<Response> responseList = reviewList.stream()
            										.map(o->new Response(o))
            										.collect(Collectors.toList());
            return responseList;
        }

        @Getter
        public static class ReviewNoteDTO {
            private Long id;

            public ReviewNoteDTO(Note note) {
                this.id = note.getId();
            }
        }

        @Getter
        public static class ReviewTagDTO {
            private Long id;
            private String name;

            public ReviewTagDTO(Tag tag) {
                this.id = tag.getId();
                this.name = tag.getName();
            }
        }
    }
}

아래처럼 호출해서 사용한다.

/* service 예시 */
public String createReview(ReviewDTO.Create createReview) {
	Optional<Tutoring> tutoring = tutoringRepository.findById(createReview.getTutoringId());
	Optional<Tag> tag = tagRepository.findById(createReview.getTagId());
	// ...생략...
}

public List<ReviewDTO.Response> reviewList() {
	List<Review> reviewList = reviewRepository.findAll();
	return ReviewDTO.Response.ResponseList(reviewList);
}

참조 : https://velog.io/@kjyeon1101/Spring


이 방법을 적용했더니 문제가 발생했다.

❗️ @query 어노테이션

JPQL은 inner class 에 대한 참조를 지원하지 않는다.......!!!!!

여러 테이블에 대한 조회가 필요해 repository 레이어에서 JPQL을 아래처럼 사용했더니 QnaResDto 클래스의 inner Class로 선언한 QnaDetailResDto를 참조하지 못하는 문제가 발생한 것이다.

    @Query("select new com.dmarket.dto.response.QnaResDto.QnaDetailResDto(q, p, u, qr) " +
            "from Qna q " +
            "join Product p on q.productId = p.productId " +
            "join User u on q.userId = u.userId " +
            "left join QnaReply qr on q.qnaId = qr.qnaId " +
            "where q.qnaId = :qnaId")
    QnaDetailResDto findQnaAndReply(Long qnaId);

선택지

  1. 일반 Object 객체로 받아서 서비스 레이어에서 다시 응답객체로 맵핑한다.
  2. Inner Class로의 변경을 포기한다.
  3. 다른 방법을 찾는다.

해결

📝 $를 사용해서 JPQL이 Inner Class에 접근하도록 한다!!!!!!!!!

팀원 중 한분이 찾아오심...........😭
여기서 OptionResDto는 external 클래스 OptionDetaulResDto는 inner class이다.

    @Query("select new com.dmarket.dto.response.QnaResDto.QnaResDto$QnaDetailResDto(q, p, u, qr) " +
            "from Qna q " +
            "join Product p on q.productId = p.productId " +
            "join User u on q.userId = u.userId " +
            "left join QnaReply qr on q.qnaId = qr.qnaId " +
            "where q.qnaId = :qnaId")
    QnaDetailResDto findQnaAndReply(Long qnaId);

결과

entitiy 테이블 별로 DTO를 관리할 수 있음.
그런데 Entitiy별로 관리하는건 별로 정리되어 보이지 않아서 서비스,도메인 별로 구분하는 것이 나을 것 같다.

post-custom-banner

0개의 댓글