자취생 커뮤니티 개발을 하며 게시글, 댓글에 대해 추천, 신고, 즐겨찾기와 같은 요구사항이 발생했다. 처음엔 해당 기능들은 다 게시글과 1 : N 관계로 구축하면 되겠다고 쉽게 생각했다. 하지만 1 : N으로 맵핑한 뒤 구현을 할 수록 이상함이 느껴졌다. 추천은 '회원이 게시글을 추천한다' 그렇다면 추천이라는 테이블엔 회원, 게시글에 대한 정보가 모두 존재해야 한다. 또한, 한명의 회원은 여러개의 게시글에 추천을 할 수 있고 하나의 게시글 또한 여러명의 회원에게 추천을 받을 수 있다. 즉 1 : N이 아닌 N : M 다대다 관계로 구축해야 했다.
이 다음 발생한 고민이 추천, 신고, 즐겨찾기는 결국 boolean 값으로 isRecommend, isReport, isFavorite과 같이 컬럼에 저장되는데 하나의 테이블에 다 밀어넣으면 안되나? 라는 생각을 했었다. 하지만 이 역시 좋은 생각은 아닌 것 같았다. 그 이유는
첫 번째로 테이블에 null 값이 너무 많을 것 같았다. 하나의 회원이 특정 게시글을 추천만 한 경우 isRecommend는 true이겠지만 나머지 컬럼은 모두 null이다. 물론 추천을 누를 때 나머지 컬럼이 null인지 체크한 뒤 false를 넣어줄 수도 있겠지만 좋은 방법은 아닌 것 같았다.
두 번째로 성능에 대한 문제였다. 물론 내가 개발한 프로젝트가 db에 엄청난 부하를 줄 만큼의 사용자나 데이터는 존재하지 않지만 추천, 신고, 즐겨찾기를 하나의 테이블에 모두 넣은 다음 회원이 즐겨찾기한 게시글을 조회하는 것 보다 즐겨찾기 테이블을 따로 만든 뒤 해당 테이블만 조회하는 것이 훨씬 성능상 이점이 클 것이라 판단했다.
세 번째로 확장성이다. 지금은 별다른 요구사항이 없어 isRecommend와 같은 boolean 값만 테이블에 저장하면 되지만 만약 신고의 경우 신고 횟수도 저장해줘 라는 요구사항이 발생했을 때 하나의 테이블에 모든 정보를 저장한다면 추천, 즐겨찾기는 자신의 기능과 상관없는 신고 횟수를 저장해야 한다. 이 역시 설계상 좋지 않다고 판단했다. 이러한 이유로 난 게시글 추천, 게시글 신고, 게시글 즐겨찾기, 댓글 추천과 같은 테이블을 따로 구축하기로 했다. 아래는 JPA를 이용한 코드이다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RecommendPost extends BaseManyToManyEntity{
@Id
@GeneratedValue
@Column(name = "recommend_post_id")
private Long id;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "post_id")
private Post post;
private Boolean isRecommend;
@Builder
public RecommendPost(Member member, Post post, Boolean isRecommend) {
this.member = member;
addPost(post);
this.isRecommend = isRecommend;
}
public void updateIsRecommend(Boolean isRecommend) {
this.isRecommend = isRecommend;
}
// 연관관계 메서드
public void addPost(Post post) {
this.post = post;
post.getRecommendPosts().add(this);
}
}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class FavoritePost extends BaseManyToManyEntity {
@Id @GeneratedValue
@Column(name = "favorite_post_id")
private Long id;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "post_id")
private Post post;
private Boolean isFavorite;
@Builder
public FavoritePost(Member member, Post post, Boolean isFavorite) {
this.member = member;
this.post = post;
this.isFavorite = isFavorite;
}
public void updateIsFavorite(Boolean favorite) {
isFavorite = favorite;
}
}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ReportPost extends BaseManyToManyEntity{
@Id
@GeneratedValue
@Column(name = "report_post_id")
private Long id;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "post_id")
private Post post;
private Boolean isReport;
@Builder
public ReportPost(Member member, Post post, Boolean isReport) {
this.member = member;
this.post = post;
this.isReport = isReport;
}
public void updateIsReport(Boolean isReport) {
this.isReport = isReport;
}
}
게시글 추천, 즐겨찾기, 신고는 회원, 게시글 모두 1 : N 다대일 관계로 맵핑하여 다대다 관계를 만들었다. 또한 난 setter를 사용하는 것 보다 update와 같은 이름을 사용해 메서드 사용 시 조금이라도 더 직관적으로 보일 수 있게 하였다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RecommendComment {
@Id
@GeneratedValue
@Column(name = "recommend_comment_id")
private Long id;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "comment_id")
private Comment comment;
private Boolean isRecommend;
@Builder
public RecommendComment(Member member, Comment comment, Boolean isRecommend) {
this.member = member;
this.comment = comment;
this.isRecommend = isRecommend;
}
public void updateIsRecommend(Boolean isRecommend) {
this.isRecommend = isRecommend;
}
}
댓글 추천도 게시글과 동일한 방식으로 구현하였다. 이후 내 설계가 옳은지 조금 더 알아보기 위해 구글링을 해보았는데 많은 사람들이 하나의 테이블에 몰아 넣기보단 여러 테이블에 나누어 저장하는 방식을 추천하는것 같아 내 생각이 맞는듯 했다.
'Project > DB' 카테고리의 다른 글
| Redis / RedisTemplate 실험 (0) | 2024.01.20 |
|---|---|
| [Independe] 채팅 DB 설계에 대한 고민 (2) | 2023.12.05 |
| [Independe] 대댓글 설계에 대한 고민 (1) | 2023.09.14 |