팀 프로젝트에서 게시물 추천 기능을 개발하게 되었는데요.
개발 과정에 대해서 써보려고 합니다 :)
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Embedded
private Title title;
@Embedded
private Content content;
@Enumerated(value = EnumType.STRING)
@Column(nullable = false)
private Category category;
@OnDelete(action = OnDeleteAction.CASCADE)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;
@Embedded
private Views views;
@Column(nullable = false)
private boolean isAnonymous;
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
// 생성자 및 로직
}
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String githubId;
@Column(nullable = false)
private String avatarUrl;
}
추천은 게시물에 대한 추천이지만 누가 추천했는지도 알아야합니다.
따라서 추천 테이블에 게시물에 대한 식별자와 유저에 대한 식별자가 필요하다고 판단했습니다.
Like와 Article, Member가 다대일 관계이므로 외래키의 주인을 like entity로 두고 설계했고 Like Entity는 다음과 같습니다.
@AllArgsConstructor
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Table(name = "Likes") // 예약어와 같으므로 Table명을 바꿔줌
@Entity(name = "Likes") // database cleaner를 사용중이므로 Entity명을 바꿔줌
public class Like {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "article_id", nullable = false)
private Article article;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;
public Like(Article article, Member member) {
this.article = article;
this.member = member;
}
}
저희 팀은 최대한 단방향을 유지하는 것을 선호하고 있습니다.
이유는 관리포인트가 늘어나는 것을 모두 컨트롤 하는 것보다 관리 포인트를 최대한 줄이는 것을 선호하기 때문입니다.
public LikeResponse likeArticle(AppMember appMember, Long articleId) {
validateGuest(appMember);
Member member = getMember(appMember);
Article article = getArticle(articleId);
if (!likeRepository.existsByArticleIdAndMemberId(article.getId(), member.getId())) {// 1
likeRepository.save(new Like(article, member));
}
return new LikeResponse(true, likeRepository.countByArticleId(articleId));
}
guest의 경우 like를 할 수 없도록 하였고, article_id와 member_id를 이용하여 like가 존재하는지 확인하고 없다면 like를 저장합니다.
public LikeResponse unlikeArticle(AppMember appMember, Long articleId) {
validateGuest(appMember);
Member member = getMember(appMember);
Article article = getArticle(articleId);
likeRepository.findByArticleIdAndMemberId(article.getId(), member.getId())
.ifPresent(likeRepository::delete);//2
return new LikeResponse(false, likeRepository.countByArticleId(articleId));
}
unlike도 마찬가지로, guest의 경우 unlike를 할 수 없도록 하였고, article_id와 member_id를 이용하여 like를 찾고, 있다면 like를 삭제합니다.
비지니스 로직에서 1,2로 표시한 부분이 고민했던 부분인데요.
일반적인 저장, 삭제 로직과 조금 다른 양상입니다.
일반적으로는 저장할 때 저장되어 있는지 확인 -> 저장되어 있다면 예외 , 저장되지 않았다면 저장
이런 식으로 흘러갑니다.
하지만 추천 기능은 빈번하게 저장과 삭제가 일어날 수 있다고 생각했습니다.
예를 들어 추천 버튼을 연달아 누른다면..!
이 과정에서 예외를 발생시키는 것이 사용자의 사용성을 저하시킨다고 생각되어 예외를 발생시키기 보단 유연한 비지니스 로직을 통해 예외를 발생시키지 않는 방향으로 구현했습니다.
정답은 아니니 언제 변경될지 모릅니다! 하하