게시글 좋아요 기능 설게 - ttasjwi/board-system GitHub Wiki

게시글 좋아요/싫어요 기능 개요

  • 사용자는 게시글에 좋아요/싫어요 할 수 있다.
    • 사용자 당 한번씩 가능하다.
  • 좋아요/싫어요 하면 좋아요수, 싫어요수가 1 증가한다.
  • 게시글 카테고리 정책상 좋아요/싫어요 불가능하다면 불가능하다.

데이터베이스 테이블 설계

좋아요 테이블

CREATE TABLE IF NOT EXISTS article_likes(
    article_like_id BIGINT   NOT NULL PRIMARY KEY,
    article_id      BIGINT   NOT NULL,
    user_id         BIGINT   NOT NULL,
    created_at      DATETIME NOT NULL,
    CONSTRAINT uq_article_id_and_user_id UNIQUE (article_id, user_id)
);
  • article_like_id : 식별자
  • article_id : 게시글 식별자
  • user_id : 사용자 식별자 (누가?)
  • created_at: 언제 좋아요했는 지
  • 유니크 제약 : (article_id, user_id)

싫어요 테이블

CREATE TABLE IF NOT EXISTS article_dislikes(
    article_dislike_id BIGINT   NOT NULL PRIMARY KEY,
    article_id         BIGINT   NOT NULL,
    user_id            BIGINT   NOT NULL,
    created_at         DATETIME NOT NULL,
    CONSTRAINT uq_article_id_and_user_id UNIQUE (article_id, user_id)
);
  • article_dislike_id : id
  • article_id : 게시글 id
  • user_id : 사용자 id (누가?)
  • created_at : 언제 싫어요 했는 지
  • 유니크 제약 : (article_id, user_id)

좋아요 수 테이블

CREATE TABLE IF NOT EXISTS article_like_counts(
    article_id BIGINT NOT NULL PRIMARY KEY,
    like_count BIGINT NOT NULL
);
  • article_id : 게시글 id
  • like_count : 좋아요 수

싫어요 수 테이블


CREATE TABLE IF NOT EXISTS article_dislike_counts(
    article_id    BIGINT NOT NULL PRIMARY KEY,
    dislike_count BIGINT NOT NULL
);
  • article_id : 게시글 id
  • dislike_count : 싫어요 수

좋아요 수 테이블을 별도로 분리한 이유


모듈

image

  • 게시글에 대한 좋아요/싫어요 기능을 모듈로 별도 분리하였다. (향후 서비스를 독립시키는 것을 가정)
  • 서비스가 따로 분리될 경우, '게시판 서비스', '게시글 서비스' 에 대해 의존이 발생할 수 있다.
    • 게시글 카테고리 의존 : 좋아요 가능 여부에 대한 정책 조회가 필요하다. 이 '게시판 서비스'에서 관리된다.
    • 게시글 의존 : 대상 게시글의 존재 여부, 게시글의 기본 메타데이터 조회가 필요하다. '게시글 서비스'에서 관리된다.

좋아요(싫어요) 생성 흐름

싫어요 기능은 구조적으로 흐름은 동일하기 때문에 생략한다.

    @Transactional
    fun like(command: ArticleLikeCreateCommand): ArticleLike {
        // 게시글 조회
        val article = getArticleOrThrow(command.articleId)

        // 게시글 카테고리 조회
        val articleCategory = getArticleCategoryOrThrow(article.articleCategoryId)

        // 카테고리 정책 확인 (좋아요 금지를 하진 않았는지...?)
        checkAllowLike(command.articleId, articleCategory)

        // 좋아요 존재 확인 (이미 좋아요했다면, 예외 발생)
        checkDuplicateLike(command.articleId, command.likeUser.userId)

        // 좋아요 생성 및 저장
        val articleLike = createArticleLikeAndSave(command)

        // 좋아요수 증가, 수정 반영
        upsertArticleLikeCount(command.articleId)
        return articleLike
    }
  • 게시글 조회 : 대상 게시글을 조회해온다.
  • 게시글 카테고리 조회 : 게시글의 카테고리 정보를 조회해온다.
  • 카테고리 정책 확인 : 카테고리 정책 상, 일반 사용자의 좋아요가 허용되지 않으면 예외 발생
  • 중복 좋아요 확인 : 이미 좋아요했는 지 확인
  • 좋아요 생성 및 저장
  • 좋아요 수 증가, 수정 반영

좋아요(싫어요) 취소 흐름

    @Transactional
    fun cancelLike(command: ArticleLikeCancelCommand) {
        // 게시글 존재여부 확인
        checkArticleExists(command.articleId)

        // 좋아요 존재 여부 확인
        checkArticleLikeExists(command.articleId, command.user.userId)

        // 좋아요 제거
        removeArticleLike(command.articleId, command.user.userId)

        // 좋아요수 감소, 수정 반영
        decreaseArticleLikeCount(command.articleId)
    }
  • 게시글 존재 여부 확인
  • 게시글에 대한 좋아요 존재 여부 확인
  • 좋아요 제거
  • 좋아요수 감소, 수정 반영

좋아요(싫어요)수 조회

    override fun readLikeCount(articleId: Long): ArticleLikeCountReadResponse {
        checkArticleExists(articleId)

        val articleLikeCount = articleLikeCountPersistencePort.findByIdOrNull(articleId)

        return ArticleLikeCountReadResponse(
            articleId = articleId.toString(),
            likeCount = articleLikeCount?.likeCount ?: 0L
        )
    }
  • 게시글 존재여부 확인 : 대상 게시글이 없을 경우 예외를 발생시킨다.
  • 좋아요 수 조회 : 데이터베이스를 통해 좋아요 수를 조회해온다.
    • 좋아요수가 저장되어 있지 않다면, 아무도 좋아요를 한 게시글이 아니므로 0을 반환한다.
    • 좋아요수가 저장되어 있다면, 그 값을 반환한다.