뉴스피드 프로젝트 회고

‍박태우·2024년 10월 24일

nbc_spring

목록 보기
25/28

1. 과제 개요

https://teamsparta.notion.site/Spring-3-1232dc3ef5148085baffe0fab0439310

-> 인스타, 페이스북과 같이 사람들간의 게시물을 올리고 댓글 및 좋아요를 구현하는 프로젝트이다.

와이어 프레임

우리 팀의 ERD

사용자 (Member) 중심으로 게시물, 댓글 및 친구관계를 형성하였다.
사용자는 여러개의 게시물, 여러개의 댓글, 여러개의 친구를 가질 수 있으며, 회원 탈퇴 테이블을 두어 다시 회원이 동일한 이메일로 등록 하려고 하면 방지하는 기능으로 구현하였다.

API 명세서

https://teamsparta.notion.site/1232dc3ef51481069001fb1f84568f7b?v=1232dc3ef51481bba9ad000cf0d0b2f4&pvs=4

내 담당 부분

  • 댓글 관련 CURD 및 댓글 좋아요 및 좋아요 취소 기능 구현
  • 이미지 구현 (S3 가 아닌 db에 경로만 저장하는 형식으로)

=> 글로 적어보니 그렇게 많은 부분을 구현한 것 같지 못해 팀원 분들께 죄송한 느낌이 있다.


2. 게시물 및 댓글 부분 코드 리뷰 및 개선 사항

  • 모든 코드에 대한 리뷰가 아닌 튜터님과 팀원들 간의 피드백 하에 진행된 개선이 필요한 부분위주로 회고 하고자 한다.

1) 프로젝트 디렉토리

=> 구조를 살펴보면 위와 같이 구현하였다. articlecomment 이 지금 다른 패키지에 존재하지만 우리팀의 튜터님의 피드백으로는 이 두 패키지를 합치는게 좀다 올바르다고 피드백을 해주셨다.

이유 : comment 가 article 에 종속적이라고 생각하심, 만약 comment 가 article 뿐만 아니라 image, member등에도 적용을 할 때 위와 같이 comment를 분리하는 게 맞다고 하셨다.

2) Entity

(1) Comment

package com.sparta.spartanewsfeed.domain.comment.entity;

// import 생략

@Getter
@Builder
@Entity
@Table
@AllArgsConstructor
@NoArgsConstructor
public class Comment extends Timestamp {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

	@Column(nullable = false)
	private String body;

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "article_id")
	@OnDelete(action = OnDeleteAction.CASCADE)
	private Article article;

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "member_id")
	@OnDelete(action = OnDeleteAction.CASCADE)
	private Member author;


//양 방향 제거
	//@OneToMany(mappedBy = "comment", cascade = //CascadeType.REMOVE, orphanRemoval = true)
	//@JsonIgnore // 순환 참조 방지
	//private List<CommentLike> likes;

	public void update(String body) {
		this.body = body;
	}
}

(2) CommentLike

package com.sparta.spartanewsfeed.domain.comment.entity;

// import 생략

@Getter
@Builder
@Entity
@Table
@AllArgsConstructor
@NoArgsConstructor
public class CommentLike {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;



	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "comment_id")
	private Comment comment;

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "member_id")
	private Member member;
}

Comment 와 CommentLike 는 지금 양방향을 가지고 있다. 하지만 이 경우 외래키의 주인인 CommentLike 가 Comment 를 가지고 갈 수 있지만 Comment 의 경우는 CommentLike 를 알 필요가 없다고 피드백이 옴

=> 따라서 위 주석과 같이 해당 부분을 삭제할 수 있다.

if ("popular".equals(sortBy)) {
			comments = commentRepository.findAllByArticleIdOrderByLikesCount(articleId, pageable);  // 인기순 정렬
		}

현재는 위와 같이 CommentService 에서 comment를 통해 commentLike 를 조회 하고 있기 때문에 위와 같이 주석을 하면 오류가 난다.
따라서 이 코드를 수정할 필요가 있다.

  • 생각해 보니 해당 댓글에 대한 좋아요 수 또는 회원의 좋아요 여부는 CommentLike 에서만 조회가 충분히 가능하다는 것을 알게 되었다.

=> 그러면 위에서 commentRepository 대신에 commentLikeRepository 를 이용하는 것은 어떨까?

=> 적용해보자!!!


3. 코드 수정해보기

  • 서비스 코드 수정
@Transactional
	public Page<CommentResponseDto> getComments(Long articleId, int page, int size, String sortBy,
		String authorization) {
		if (!articleRepository.existsById(articleId)) {
			throw new NotFoundEntityException(NOT_FOUND_ARTICLE);
		}
		// Pageable 객체 생성 (기본값: 최신순)
		Pageable pageable = PageRequest.of(page, size, Sort.by("updatedAt").descending());

		Page<Comment> comments;

		if ("popular".equals(sortBy)) {
			comments = commentLikeRepository.findCommentsByArticleOrderByLikes(articleId, pageable);
			//comments = commentRepository.findAllByArticleIdOrderByLikesCount(articleId, pageable);  // 인기순 정렬
		} else {
			comments = commentRepository.findAllByArticleId(pageable, articleId);  // 기본 최신순 정렬
		}

		Member author = findByEmail(authorization);

		return comments.map(comment -> {
				boolean isLiked = commentLikeRepository.existsByCommentIdAndMemberId(comment.getId(), author.getId());
				return CommentResponseDto.of(comment, isLiked);
			}
		);
	}
  • 위 주석 처리 코드 참고

위와 같이 commentRepository 대신 commentLikeRepository 로 부터 불러오게 된다.
(현재 CommentLike 가 Comment 를 가리키는 단방향으로 가리키고 있기 때문에 위와 같이 불러 올 수 있다.)

  • Repository 코드 수정
@Query("SELECT c FROM Comment c " +
		"LEFT JOIN CommentLike cl ON cl.comment.id = c.id " +
		"WHERE c.article.id = :articleId " +
		"GROUP BY c.id " +
		"ORDER BY COUNT(cl.id) DESC")
	Page<Comment> findCommentsByArticleOrderByLikes(@Param("articleId") Long articleId, Pageable pageable);

JPQL 을 이용해서 위와 같이 Comment 테이블과, CommentLike 테이블을 left 조인하였고 조건에 맞게 정렬하도록 하였다.
@Param 은 쿼리에서 테이블에 매핑될 칼럼의 이름이 된다.


4. 결과 테스트

쿼리 파라미터를 통해 popular 를 주게 되면 위와 같이 true 가 있는
(현재는 좋아요 개수 1이 최대인 상태) 댓글이 먼저 출력되고 이후에 false 댓글이 출력되는 것을 알 수 있다.

그렇지 않은 경우는 디폴트로 최신순으로 출력되는 것을 확인하였다.

=> 반드시 양방향 관계여야 할 것이라고 생각 했던 것이 단방향 만으로도 구현이 되어서 되게 신기했던 것 같다. 이와 같이 JOIN 과 같이 다른 방법을 통해서 쿼리 수를 줄이면서 단방향을 되도록 유지 하는 것이 좋은 방법일 것 같다.


추가 사항

  • 추가로 좋아요 테이블을 아래와 같이 생성, 수정일을 추가하였다.

=> 그리고 이제와서 안 사실이지만 위 테이블에 article_id 가 있었어도 JPQL 없이 가능할 것 같다. (물론 성능 쿼리 수 개선을 위해 사용 할 수도 있을 듯)

profile
잘 부탁드립니다.

0개의 댓글