cheese10yun / spring-jpa-best-practices

:octocat: spring-jpa best practices
1.08k stars 241 forks source link

안녕하세요. JPA 공부하다가 궁금한 사항이 있어서 글 남깁니다. #68

Open pasudo123 opened 5 years ago

pasudo123 commented 5 years ago

유용한 자료 올려주셔서 감사합니다.

자료를 보면서 JPA 관련한 토이프로젝트를 해보고 있는데 몇가지 궁금증이 생겨 글을 남깁니다.

어느 아티클이 있고 해당 아티클에 댓글이 달리는 작은 서비스를 만들어보고 있습니다. Article 과 Comment 라는 엔티티이고 둘의 관계는 일대다 관계로 설정되어 있습니다.

Article 의 기준에서 Comment 는 여러개가 달릴 수 있고, ( 1 : N ) Comment 의 기준에서 여러 개의 Comment 들은 하나의 Article 에 달릴 수 있다고 판단하여 ( N : 1 ) 관계로 두었습니다.

첫번째 질문.

하나의 아티클에 댓글 작성 시 dto 작성에 관한 질문.

만약에 사용자가 특정 아티클에 대해서 댓글을 작성한다고 가정한다면, 저는 해당 Dto 를 Commenet 에 관한 Dto 로 작성하고 아래와 같이 표현하였습니다.

CommentOneRequestDto.class

@Getter
public class CommentOneRequestDto {

    private Long articleId;

    @NotBlank(message = "comment is not empty.")
    private String comment;

    public Comment toEntity() {
        return Comment.builder()
                .comment(this.comment)
                .build();
    }
}

하나의 댓글을 등록함에 있어서 해당 Dto 가 article 에 대한 Id 를 Dto 내부에 가지고 있는게 올바른 방법인지 헷갈려서 질문을 남깁니다. (해당 Dto 는 Comment 쪽의 Controller 에서 받는것으로 정의되어 있습니다. )

Comment 에 관한 컨트롤러는 아래와 같이 구성되어 있습니다. CommentController.class

@PostMapping("/comment")
public ResponseEntity<CommentOneResponseDto> saveComment(@Valid @RequestBody CommentOneRequestDto dto,
                                                         BindingResult bindingResult) throws ValidationException {

두번째 질문.

댓글을 작성하였을 때 서비스 레이어에서 댓글을 등록하는 방법에 관한 질문

하나의 댓글을 작성하고 저장하는 로직이 댓글 서비스 레이어에서 있다고 한다면 레이어에 2 개의 레파지토리가 필요하더군요. ArticleRepository, CommentRepository 저는 이렇게 하는 것이 좋은지 혹은 CommentService 에서 ArticleService 에 대한 메소드를 호출하는 것이 나은지 궁금합니다.

CommentService.class :: 두 개의 레파지토리를 가지고 있는 경우

@Service
public class CommentService {

    @Autowired
    private ArticleRepository articleRepository;

    @Autowired
    private CommentRepository commentRepository;

    public CommentOneResponseDto addNewComment(CommentOneRequestDto dto) throws ArticleNotFoundException{

        Article article = articleRepository.findById(dto.getArticleId())
.orElseThrow(() -> new ArticleNotFoundException("아티클이 존재하지 않습니다."));

        Comment comment = dto.toEntity();
        comment.setArticle(article);

        article.addComment(comment);

        commentRepository.save(comment);

        return new CommentOneResponseDto(comment);
    }
}

CommentService.class :: 하나의 레파지토리를 가지고, ArticleService 의 메소드를 이용하는 경우

@Service
public class CommentService {

    @Autowired
    private ArticleInternalService articleInternalService;

    @Autowired
    private CommentRepository commentRepository;

    public CommentOneResponseDto addNewComment(CommentOneRequestDto dto){

        Article article = articleInternalService.findById(dto.getArticleId());

        Comment comment = dto.toEntity();
        comment.setArticle(article);

        article.addComment(comment);

        commentRepository.save(comment);

        return new CommentOneResponseDto(comment);
    }
}

질문이 두 개지만 질문을 하기위한 설명을 제대로 하는게 좋을 것 같아서 좀 장황하게 작성이 되었습니다. 다시한번 좋은 자료 공유해주셔서 감사드리고 혹 시간되실 때, 답변주시면 감사하겠습니다.

cheese10yun commented 5 years ago

하나의 아티클에 댓글 작성 시 dto 작성에 관한 질문.

이 부분은 명확하게 답이 있다고 하긴 어렵겠네요. Rest API 관점에서 본다면

article/{id}/comments

이렇게 하는것이 적절해 보이긴 하지만 Rest API로 표현하는 것에 얼메이는 것이 좋지는 않다고 생각해요. 팀 컨벤션이 있다면 그것을 따르는 것이 더 높은 가치라고 생각합니다.

RequestBody로 ID를 넘기는 것도 괜찮다고 생각합니다. 만약 기능 중에 관리자가 여러 글에 댓글을 남기는 요구사항이 있으면 (이벤트가 종료되었습니다. 등등 ) Dto로 컬렉션 타입으로 ID를 받을 수 도 있을거 같습니다.

댓글을 작성하였을 때 서비스 레이어에서 댓글을 등록하는 방법에 관한 질문

이 질문에 대답은 명확합니다. 서비스를 통해서 접근 해야합니다.

Article article = articleRepository.findById(dto.getArticleId())
.orElseThrow(() -> new ArticleNotFoundException("아티클이 존재하지 않습니다."));

쉽게 설명 드리면 위의 코드가 계속 중복됩니다. findById를 데이터베이스에서 null을 반환할 수 있기 때문에 모든 코드에 위처럼 예외 처리하는 코드가 추가됩니다. 이런 부분을 서비스 영역에서 예외처리를 하고 그것을 제공하는 것이 맞습니다.

이것은 중복 코드의 문제를 넘어서 인프라스트럭처를 대하는 방식입니다. 예를들어 커머스 시스템에서 여러 택배사의 배송 완료 메시지가 CJ는 배송완료, 한진 배달 완료를 주더라도 우리는 우리만의 코드로 DeliveryCompleted 으로 관리할 수 있습니다. 인프라스트럭처의 대표적인 예가 외부 API, 데이터베이스 등입니다. 이 처럼 외부 영역에 대해서는 우리만의 방식으로 관리해주는 계층을 하다 두고 그 계층을 통해서 제어되게 해야합니다.

cheese10yun commented 5 years ago

추가적으로 말씀드리면 서비스의 크기는 작아야합니다. 정확히는 서비의 책임이 명확해야 합니다 물론 서비스 객체에만 해당되는 이야기는 아니고 모든 객체에 해당 됩니다. ArticleFindService 처럼 행위가 드러나게 네이밍하면 자연스럽게 알맞는 책임을 갖게 될수 있습니다

https://github.com/cheese10yun/spring-guide/blob/master/docs/service-guide.md 여기 한번 참고해보세요

그리고 꼼꼼학 예제 까지 올려주셔서 이해하기 편했습니다

pasudo123 commented 5 years ago

상세하게 말씀해주신 덕분에 궁금증이 해소가 되었습니다. 답글로 남겨주신 링크는 읽어보았습니다. 저 부분을 따르게 된다면 저 또한 아티클에 대한 서비스를 세부적으로 나누어야 겠다는 생각이 듭니다.

친절한 답변과 좋은 링크 공유해주셔서 감사합니다. 👍