졸작을 하면서 두개의 게시판에 댓글 및 대댓글 기능을 각각 넣어야 하는 상황이었다.
동네 줍깅 서비스에서 운영하는 게시판은 2가지가 있다.
댓글 및 대댓글은 각각의 게시판에서 동일한 구조로서 사용되기 때문에 하나의 댓글 및 대댓글 도메인을 만들고 이를 각각에 게시판에 붙히는 구조로 기능을 구현했다.
우선 댓글 기능을 먼저 만들기로 하였고, Comment
라는 엔티티를 다음과 같이 만들어주었다.
Comment
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "comment_id", nullable = false)
Long id;
@Embedded
@Column(nullable = false)
private CommentBody commentBody;
@ManyToOne
@JoinColumn(name = "member_id")
private Member writer;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "recruitment_board_id")
private RecruitmentBoard recruitmentBoard;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "certification_board_id")
private CertificationBoard certificationBoard;
@Column(nullable = false)
private LocalDateTime createdDateTime;
@Column(nullable = false)
private LocalDateTime lastModifiedDateTime;
//...
}
서로다른 두 종류의 게시글에서 댓글을 사용해야 했으므로 Comment
는 RecruitmentBoard
와 CertificationBoard
각각 양방향으로 N : 1
연관관계를 가지도록 하였다.
하나의 댓글은 오로지 하나의 게시판에만 속해야 했기 때문에 Comment
객체를 생성 할 때, 특정 종류의 게시판에 댓글이 매핑되어야 했다. 때라서 Comment
내부에 정적팩토리 메서드를 두개를 만들어주었다.
Comment 정석 팩토리 메서드
// 지역 플로깅 구인 게시판에 달리는 댓글을 생성하기 위한 메서드
public static Comment createOnRecruitmentBoardWith(final RecruitmentBoard board, final String commentBody, final Member writer, final LocalDateTime now) {
return new Comment(board, commentBody, writer, now);
}
private Comment(final RecruitmentBoard board, final String commentBody, final Member writer, final LocalDateTime now) {
this.commentBody = new CommentBody(commentBody);
this.writer = writer;
this.createdDateTime = now;
this.lastModifiedDateTime = createdDateTime;
setRecruitmentBoard(board);
}
// 플로깅 인증 게시판에 달리는 댓글을 생성하기 위한 메서드
public static Comment createOnCertificationBoardWith(final CertificationBoard board, final String commentBody, final Member writer, final LocalDateTime now) {
return new Comment(board, commentBody, writer, now);
}
private Comment(final CertificationBoard board, final String commentBody, final Member writer, final LocalDateTime now) {
this.commentBody = new CommentBody(commentBody);
this.writer = writer;
this.createdDateTime = now;
this.lastModifiedDateTime = createdDateTime;
setCertificationBoard(board);
}
댓글 기능을 양쪽 게시판에 각각 넣으려다 보니, RecruitmentBoard
에 달리는 Comment
의 CertificationBoard
값은 데이터 베이스 상에서 자연스럽게 null
로 기록된다.
반대로 CertificationBoard
에 달리는 작성한 Comment
는 RecruitmentBoard
값이 null
로 기록이 된다.
대댓글 기능 구현에서 다음 두가지 방식 중 어떤 방식으로 구현을 해야할지 많은 고민을 했었다.
우선 대댓글과 댓글의 차이는 크게 없다. 사실 대댓글이라는 것은 댓글에 달리는 댓글이기 때문에 댓글과 대댓글에 대한 개념을 분리해서 구현을 해야할지, 하나로 보고 구현을 해야할지 고민이었다.
만약 댓글 및 대댓글 기능을 계층형 구조로 구현을 한다면 다음과 같다.
예를 들어 A 댓글
에 a, b 대댓글
이 달린다면
a, b 댓글
은 자신의 부모 댓글로 A 댓글을 가진다.
다음과 같이 댓글이 구성되어 있는경우 Comment
테이블엔 아래의 형태로 저장이 될 것이다.
ID | 부모 댓글ID | 내용 | 작성일 | 수정일 | 작성된 게시판 ID | |
---|---|---|---|---|---|---|
A 댓글 | 1 | null | AAA | 23 | ||
a 댓글 | 2 | 1 | aaa | 23 | ||
b 댓글 | 3 | 1 | bbb | 23 |
계층형 구조의 장단점을 따져 보면
Comment
엔티티 하나로 댓글 및 대댓글 까지 관리가 가능하다는 장점이 있었다.
또한 대댓글을 부모로 가지는 대댓글의 댓글을 구현할 수 있기에 무한 대댓글
까지 간편하게 구현 할 수 있다는 장점이 있다.
댓글
과 대댓글
의 명확한 분리가 안된다고 생각할 수 있다. 하나의 댓글 엔티티
로 댓글
및 대댓글
을 관리한다는 것이 매력적이지만, 실제로는 댓글과 대댓글은 명확하게 다른 개념
이기 때문에 댓글 엔티티 하나로 댓글 및 대댓글을 관리하는것이 부자연스럽다고 느낄 수 있다.
위에 작성한대로 계층형 구조를 가졌을 때 댓글과 대댓글의 명확한 분리가 안되는 상황은 부자연스럽다고 생각했다. 간편하지만 현실에서 명확하게 댓글과 대댓글의 개념은 분리가 되기 때문에 객체로서도 이들을 분리해주는 것이 자연스럽다고 생각했다. 또한 졸업작품 서비스 기획 단계에서 댓글의 depth 는 대댓글 1번 까지로 두었기 때문에 무한 덧글이 가능한 계층형 댓글 구조를 사용할 이유가 없다.
따라서 추가적으로 ReplyComment
엔티티를 다음과 같이 구현하였다.
ReplyComment
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ReplyComment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "reply_comment_id", nullable = false)
Long id;
@Embedded
@Column(nullable = false)
private CommentBody replyCommentBody;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member writer;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "comment_id")
private Comment comment;
@Column(nullable = false)
private LocalDateTime createdDateTime;
@Column(nullable = false)
private LocalDateTime lastModifiedDateTime;
public ReplyComment(final Comment comment, final String replyCommentBody, final Member writer, final LocalDateTime now) {
setReplyComment(comment);
this.replyCommentBody = new CommentBody(replyCommentBody);
this.writer = writer;
this.createdDateTime = now;
this.lastModifiedDateTime = now;
}
//...
}
Comment
와 ReplyComment
는 1:n 양방향 연관관계를 가진다.
Comment
와 ReplyComment
의 구별이 되기 때문에 개념적으로 분리가 명확하다.
계층형 구조와 비교 했을 때 ReplyComment
엔티티를 추가적으로 구현해야했기 때문에 시간적 비용이든다.
댓글 대댓글 기능은 처음 구현해보았기 때문에 어떠한 방식으로 하는게 좋을지 고민이었다. 처음 댓글 기능을 구현하기위해 검색을 해보았을 때 보통 계층형 구조로 많이 구현을 하는 것 처럼 느겼지만, 그럼에도 부모 댓글과 대댓글의 개념적인 경계를 나누는 것이 더 객체지향적인 방식이라고 생각했다.
또한 현재 서비스 기획에서 무한 덧글을 사용하지 않기 때문에 계층형 구조의 장점을 살릴 수 없다는 생각을 했다.결과적으로 댓글과 대댓글 엔티티를 각각 만들어 주었다.
추가적으로 구현 이미지를 남긴다.