Bangcom 게시판 제작기 − ① (기획 / ERD 설계)

dondonee·2024년 7월 24일
0

Bangcom 게시판 제작기

프로젝트 기획

게시판 프로젝트를 선택한 이유는 기획 시간을 줄이기 위해서다.

디자인에도 시간을 많이 들이고 싶지 않아서 UI는 개발자 커뮤니티 OKKY를 참고했다. 디자인과 구조가 심플했기 때문이다. 게시판의 주제는 방송대 컴퓨터과학과 오픈 카톡에서 아이디어를 얻었다.

기능 구현은 회원, 게시글, 댓글 도메인을 우선적으로 구현하기로 했다.

휴면 계정 시스템도 넣을까 했었는데, 2023년 9월부터 개인정보보호법의 개정으로 많은 사이트에서 없애고 있는 추세라고 하고, 탈퇴 계정 처리와 차이가 크지 않아 생략했다. 개인정보보호법 개정으로 인한 변화에 대해서는 요즘 IT의 “계정이 휴면 해제될 예정입니다” 메일 오는 이유에서 잘 정리해주었다.



ERD 설계

DBMS는 많이 쓰이고 익숙한 MySQL을 선택했다. 개발 과정에서는 인메모리 모드를 지원하는 H2를 사용했는데, MySQL 모드로 설정하면 대부분의 MySQL 문법을 사용할 수 있다.


ID 컬럼명

ID 컬럼의 이름을 id로 하는 것이 좋은지 tablename_id로 하는게 좋은지는 의견이 분분하다.

나는 SQL에서는 tablename_id 형태인 것이 JOIN할 때 어느 테이블의 ID인지 헷갈리지 않아 좋은 것 같다고 생각했고, Java에서는 객체를 통해 member.getId와 같은 형태로 사용되기 때문에 필드 이름으로는 간단하게 id로 사용하는 것이 좋아보였다.

따라서 DB 컬럼명으로는 tablename_id, 클래스 필드명으로는 id로 사용했고 MyBatis의 resultMap에서 매핑해주었다.


ID 컬럼 타입 - INT vs. BIGINT

ID 컬럼 타입으로는 INT 혹은 BIGINT를 사용할 수 있을 것이다. MySQL 문서를 보면 차이는 다음과 같다.

  • INT : 4 바이트, 표현 가능한 최대값 2147483647 (약 20억)
  • BIGINT : 8 바이트, 표현 가능한 최대값 2^63-1

INT도 20억이 넘는 값을 표현할 수 있지만, ID는 변경이 어렵기 때문에 대체로 BIGINT를 사용하는 것 같다.



회원 관련 테이블

member_user

회원 고유번호(user_no), 로그인 아이디(login_name)으로 이루어진 심플한 테이블이다. 회원 관련 테이블의 코어라고 할 수 있다.

이 구조는 회원 가입 및 로그인을 위한 테이블 설계 블로그 글을 참고했다.

아이디는 로그인 외에는 거의 사용하지 않는데, 사용 맥락이 다른 프로필 정보와 묶어다니는 것은 좋지 않다. 아이디를 공개적으로 노출하는 것은 보안상으로도 좋지 않을 것이고, 추후 소셜 로그인 연동 등 인증 정보가 복잡해졌을 때를 대비해서도 별도 테이블로 분리하는 것이 좋다고 생각했다.


member_profile

애플리케이션에서 자주 사용되는 회원의 프로필 정보를 담은 테이블이다.

  • 기본 프로필 : 닉네임, 프로필 사진, 자기소개
  • 학교 관련 정보 : 편입 여부, 학년, 지역
  • 애플리케이션에서 필요한 정보 : 권한, 가입일, 수정일

학년, 지역, 권한 컬럼의 값을 제한하기 위해서 참조 테이블을 사용할 수 있다. 그런데 각각에 대해 참조 테이블을 만들다보니 ERD가 너무 복잡해져서 핵심 테이블 구조를 파악하기 어려웠다.

제한된 값이라면 참조 테이블 대신 Java의 Enum을 사용하는 것은 어떨까 싶었다.

학년, 지역, 권한 컬럼에 대응하는 Grade, Region, Authority Enum 클래스를 만들었다. MySQL에서 ENUM 컬럼 타입을 제공하지만, 호환성 문제로 사용하지 않았다. 개발 과정에서 사용한 H2에서 MySQL 모드로 설정했음에도 호환이 되지 않았기 때문에, DB에서는 기본 타입(TINYINY, VARCHAR)을 사용하고 CHECK 제약조건과 함께 사용했다.

편입여부 컬럼Y, N 두 개의 값만 존재하기 때문에 Java 필드에서는 boolean 값을 사용했고 DB 컬럼에서는 TINYINT를 사용했다. MySQL에는 boolean 타입이 없고 1 바이트를 가지는 TINYINT가 가장 작은 정수 타입 컬럼이다(참고).


auth_password

비밀번호와 비밀번호 변경일을 저장하는 테이블이다.

비밀번호는 원본을 저장하지 않고 단방향 암호화하여 저장한다.


profile_image

업로드한 프로필 이미지 파일의 원본 이름을 저장하기 위한 테이블이다. 이미지를 다운로드 할 때 원본 이름을 제공하기 위해 만들었다.



탈퇴 관련 테이블

withdrawal_user

회원이 탈퇴하면 withdrawal_user 테이블에 탈퇴한 회원의 user_no만 저장한다. member_user 데이터가 삭제되고, 연관된 테이블의 데이터도 DELETE CASCADE 된다.

단, 회원이 탈퇴해도 게시물과 댓글은 남도록 했다. member_user 테이블은 회원 관련 테이블과만 물리적으로 연관관계가 있으며, 게시글과 댓글 테이블과는 논리적으로만 연결되어 있다.


withdrawal_log

회원탈퇴시 사용자에게 탈퇴사유를 입력받고 테이블에 기록한다. 탈퇴일(withdrawal_date)도 함께 남긴다.

집계가 용이하도록 값은 코드화했고, Java에서 Enum 클래스로 관리했다.

status_code는 자진 탈퇴인지, 강제 탈퇴인지, 장기 미사용으로 인한 탈퇴인지 구분하는 코드이다. reason_code는 상세한 탈퇴 사유에 대한 코드이다. 사용자가 자진 탈퇴한 경우 졸업/휴학/편입 등으로 이용 사유가 없어져 탈퇴하는지, 다른 서비스를 이용해서 사용하는 지 등 상세한 사유를 선택하도록 했다. 만약 탈퇴 사유가 '기타'인 경우 reason_text 컬럼에 직접 사유를 남길 수 있다.



게시글 테이블

게시물 ID, 작성자, 제목, 내용, 생성일, 수정일, 조회수 컬럼을 가지며, 게시글의 카테고리를 구분하기 위한 토픽 컬럼(topic)이 있다.

카테고리 게시판

Bangcom 게시판은 네 개의 게시판과 그 하위에 토픽이 있는 구조이다.

  • 커뮤니티 : 학교생활, 사는얘기, 벼룩시장
  • 정보 : 멘토알림, 정보공유
  • QnA : 학사, 학습, 기타
  • 공지사항

모든 게시판이 같은 형태이고 레코드가 아주 많지 않기 때문에 하나의 DB 테이블로 통합하기로 했다. 토픽을 구분하기 위한 topic 컬럼을 만들어 주었다. 게시판 구분 컬럼은 중복되기 때문에 토픽 컬럼으로만 게시판을 구분하도록 했다.

그런데 객체지향 애플리케이션에서는 토픽이 게시판을 결정짓는 형태보다는, 게시판 객체가 토픽 객체를 가지는 형태가 편하다. 따라서 게시판을 의미하는 TopicGroup 클래스가 Topic 클래스를 가지도록 했다. 또한 게시판과 토픽은 한정된 값이기 때문에 TopicGroup, Topic은 모두 Enum 클래스로 만들었다.

@Getter
@AllArgsConstructor
public enum TopicGroup {

    NOTICE("공지사항", new Topic[]{Topic.NOTICE}, "notice"),
    INFO("정보", new Topic[]{Topic.MENTOR, Topic.USER}, "info"),
    COMMUNITY("커뮤니티", new Topic[]{Topic.CAMPUS, Topic.LIFE, Topic.MARKET}, "community"),
    QUESTIONS("Q&A", new Topic[]{Topic.CAREER, Topic.STUDY, Topic.QNA_ETC}, "questions");

    private String description;
    private Topic[] topics;
    private String uri;  // 게시판 URI
    
    // 생략
}

자세한 내용은 다른 포스팅에서 정리하겠다.


content 컬럼 타입 - VARCHAR vs. TEXT

최대 4000자 까지 허용할 내용 컬럼(content)은 TEXT 타입으로 설정해주었다. 크기가 크고 자주 사용되지 않는 컬럼은 VARCHAR보다 TEXT가 좋다고 한다. 두 타입의 비교는 다음 블로그를 참고했다.


날짜 컬럼 타입 - DATETIME vs. TIMESTAMP

게시글의 작성일, 수정일은 날짜와 시간 정보가 필요하다. MySQL의 DATETIME, TIMESTAMP 모두 YYYY-MM-DD hh:mm:ss[.fraction] 형식으로 날짜를 저장할 수 있다. ([.fraction]은 6자리 마이크로초 형식으로, 필요한 경우 추가할 수 있다.)

둘의 주요 차이는 다음과 같다.

  • TIMESTAMP는 DB에 값을 저장할 때 UTC로 자동 변환하고, 값을 꺼내줄 때도 현재 타임존으로 자동 변환해준다. (타임존 인식은 커넥션마다 다르다.)
  • DATETIME의 표현 범위가 더 넓다. DATETIME1000-01-01 00:00:00.000000 ~ 9999-12-31 23:59:59.499999'이고, TIMESTAMP1970-01-01 00:00:01.000000 ~ 2038-01-19 03:14:07.499999이다.

글로벌 서비스를 제공한다면 TIMESTAMP를 사용하는 것이 맞겠다. 그러나 국내 서비스라면 유동적인 타임존 때문에 혼란이 생길 수 있어 DATETIME을 사용하는 것을 추천하는 사람들이 많았다. Bangcom은 국내 서비스 콘셉트 이기 때문에 DATETIME을 사용하기로 했다.


DB에서 사용할 타임존을 선택하는 방법은 다음을 참고했다. 나는 애플리케이션 설정인 @PostConstruct를 사용했는데, 글로벌 서비스를 위해 TIMESTAMP를 사용할 경우 DB 커넥션마다 다르게 타임존을 설정해야 할 수 있는 URL 파라미터를 사용하는 것이 좋다.



댓글/대댓글 테이블

post_comment

댓글 테이블은 댓글 ID, 게시글 ID, 작성자, 내용, 작성일, 수정일 컬럼을 가지며, 대댓글 구현을 위한 부모 댓글 ID, 그룹, 깊이, 정렬 순서에 대한 컬럼을 추가로 가진다.

게시글에 소속되어있기 때문에 게시글이 삭제되면 ON DELETE CASCADE 되도록 설정했다.

대댓글 구현

대댓글은 셀프조인을 통해 구현했다. 자세한 내용은 다른 포스팅에서 정리하겠다.

0개의 댓글