게시글 기능 설계 - ttasjwi/board-system GitHub Wiki

개요

  • "board-system" 이 우리 서비스의 이름이고, 게시판을 중심으로 기능들이 돌아간다.
    • 근데 실질적으로는 게시판의 핵심 가치는 커뮤니케이션이 발생하는 "게시글" 그 자체이며, 게시글이 가장 중요하다.
  • 게시글 생성 정책
    • 게시글을 생성하기 위해선, 게시글 카테고리를 지정해야한다.
    • 게시글 생성시, 작성시점의 nickname 이 박제된다.(변경 불가능. 그 시점의 작성자 닉네임도 보존하는 정책)
    • 게시글 카테고리 정책상, 일반 사용자의 게시글 작성이 불가능하다면 게시글을 작성할 수 없다.
  • 게시글은 이후 여러가지 부가적인 기능들이 덧붙여질 수 있다. 하지만 이런 기능들의 대상이 되는 게시글이 이들에 대해서는 모르게 설계한다. (맥락 순환참조 방지)
    • 좋아요/싫어요
    • 댓글
    • 조회수

게시글 테이블 설계

CREATE TABLE IF NOT EXISTS articles(
    article_id          BIGINT        NOT NULL PRIMARY KEY,
    title               VARCHAR(50)   NOT NULL,
    content             VARCHAR(3000) NOT NULL,
    board_id            BIGINT        NOT NULL,
    article_category_id BIGINT        NOT NULL,
    writer_id           BIGINT        NOT NULL,
    writer_nickname     VARCHAR(15)   NOT NULL,
    created_at          DATETIME      NOT NULL,
    modified_at         DATETIME      NOT NULL
);
  • article_id : 게시글 식별자
  • title: 제목
  • content: 본문
  • board_id : 게시판 식별자
  • article_category_id: 게시글 카테고리 식별자
  • writer_id : 작성자 식별자
  • writer_nickname: 작성 시점의 작성자 닉네임
    • 현재 nickname 과 구분된다. 작성 시점의 작성자 닉네임을 보존하기 위함이다.
  • created_at : 생성시점
  • modified_at : 마지막 수정 시점

도메인 엔티티

class Article
internal constructor(
    val articleId: Long,
    title: String,
    content: String,
    val boardId: Long,
    articleCategoryId: Long,
    val writerId: Long,
    val writerNickname: String,
    val createdAt: AppDateTime,
    modifiedAt: AppDateTime,
) {

    var title: String = title
        private set

    var content: String = content
        private set

    var articleCategoryId: Long = articleCategoryId
        private set

    var modifiedAt: AppDateTime = modifiedAt
        private set

    companion object {

        fun create(
            articleId: Long,
            title: String,
            content: String,
            boardId: Long,
            articleCategoryId: Long,
            writerId: Long,
            writerNickname: String,
            createdAt: AppDateTime,
        ): Article {
            return Article(
                articleId = articleId,
                title = title,
                content = content,
                boardId = boardId,
                articleCategoryId = articleCategoryId,
                writerId = writerId,
                writerNickname = writerNickname,
                createdAt = createdAt,
                modifiedAt = createdAt,
            )
        }

        fun restore(
            articleId: Long,
            title: String,
            content: String,
            boardId: Long,
            articleCategoryId: Long,
            writerId: Long,
            writerNickname: String,
            createdAt: LocalDateTime,
            modifiedAt: LocalDateTime,
        ): Article {
            return Article(
                articleId = articleId,
                title = title,
                content = content,
                boardId = boardId,
                articleCategoryId = articleCategoryId,
                writerId = writerId,
                writerNickname = writerNickname,
                createdAt = AppDateTime.from(createdAt),
                modifiedAt = AppDateTime.from(modifiedAt)
            )
        }
    }

    fun isWriter(userId: Long): Boolean {
        return this.writerId == userId
    }

    fun update(title: String, content: String, modifiedAt: AppDateTime): UpdateResult {
        if (title == this.title && content == this.content) {
            return UpdateResult.UNCHANGED
        }
        this.title = title
        this.content = content
        this.modifiedAt = modifiedAt
        return UpdateResult.CHANGED
    }

    enum class UpdateResult {
        CHANGED, UNCHANGED
    }

    override fun toString(): String {
        return "Article(articleId=$articleId, title='$title', content='$content', boardId=$boardId, articleCategoryId=$articleCategoryId, writerId=$writerId, writerNickname='$writerNickname', createdAt=$createdAt, modifiedAt=$modifiedAt)"
    }

}