📝 학습 목표
- Database 설계를 어떻게 하는 것이 좋은지 이해한다.
🤔 데이터베이스는 어떻게 설계하는 것이 좋을까?
백엔드 서버에서 빼놓을 수 없는 DATABASE.
그래서 이번 스터디의 주제는
데이터베이스를 어떻게 설계하는 것이 좋을까? 를 고민하고,
제시된 요구 사항에 대해
해당 상황에서 어떻게 설계를 하는 것이 좋을지를 다루는 것이 주요 내용이다.
데이터베이스를 직접 설계해보는 것이 매우 중요하다.
직접 설계를 해보며 스스로 익혀보자.
- 데이터베이스를 어떻게 설계할까
- 이런 상황에서는 어떻게 설계할까
- 직접 데이터베이스를 설계해보자
✒️ 0. 들어가기 전
그렇다면 데이터베이스는 언제 설계를 해야 할까?
데이터베이스의 설계 결과물은 ERD(Entity-Relationship Diagram)이다.
ERD를 그리는 툴은 여러가지가 있다.
StarUML과 AQueryTool 등이 많이 사용된다.
온라인상에서 작성하고 공유하고 싶다면 erdcloud도 많이 사용된다.
자, 그러면 ERD는 언제 설계하는 것이 좋을까요?
🌟ERD는 프로젝트 시작과 동시에 설계하는 것이 좋다.🌟
ERD는 세세한 내용은 언제든지 바뀔 수 있기에
처음부터 완벽하게 만들 필요는 없다!
(틀만 잡는 것이다! 여기에 시간을 며칠을 쏟고 그러지는 말자..)
보통 실제 기능 구현을 진행하며 필요한 내용을 수정한다!
그리고 주의할 점은 만약에 E-RD를 여러 명이 작성하는 경우가 있다면,
❗모든 팀원이 인지하는 데이터베이스는 동일해야 한다는 것이다.❗
팀 프로젝트를 할 때,
각자 자기 마음대로 DB를 설계하고, 작업 후 나중에 합치는 행위는 금지한다...
꼭 처음에 빠르게 ERD를 설계하여,
모두가 공통된 데이터베이스에 대해 숙지/인지한 후 작업을 하는 것이 필수!
- 유저 테이블을 어떻게 설계하는 것이 좋은지
- N : M(다대다) 관계는 어떻게 하는 것이 좋은지
- 알림을 보내야 하는 경우는 어떻게 하는 것이 좋은지 등
- >> 설계를 할 때 어떤식으로 하는 것이 좋은지 다룬다.
... MySQL 기준!
✒️ 1. ERD
💡 Entity-Relationship Diagram(ERD) 란?
- 엔티티와 관계를 중심으로 표현하는 데이터 모델링 도구이다!
- 이는 데이터베이스의 구조를 시각적으로 표현하여 데이터베이스의 설계 및 구현을 돕는다!
- (피터슨, IE, Barker 등) 이 있다
💡 모델링의 순서
데이터베이스 설계를 한다고 치면, 들어본 적이 있을 것이다.
바로 "개념적 - 논리적 - 물리적 모델링"
(1) 업무 파악
- 업무를 파악하고, 요구사항을 이해하여 데이터 모델을 구성하는 단계
- UI (FIGMA 등)를 보고 구조를 이해하는 단계
(2) 개념적 데이터 모델링
- 업무에서 개념을 도출하는 단계이다!
- 이를 기반으로 개념적 데이터 모델을 작성한다. (정보, 그룹, 관계를 표현한다!)
- ERD는 Entity, Relation, Attribute 등이 필요
- 의사소통 시 주의할 점
- 개념에 집중한 것 => DB용어와 다르다!
- Entity - Table
- Attribute - Column
(3) 논리적 데이터 모델링
- 이전에 엔티티, 속성, 관계 를 찾았다면,
이번 단계에서는 실제로 어떤 유형 DB (NoSQL, RDBMS 등) 를 사용할 건지에 따라
해당 유형에 맞게 정제하는 단계이다!
- 우리는 보통 관계형 데이터베이스를 사용함으로 이에 맞추어
엔티티 -> 테이블 로 , 속성 -> 칼럼 으로, 관계 -> PK/FK 로 변환한다!
(4) 물리적 데이터 모델링
- 현재 다룰 것은 아니지만
실제 DB시스템에서 사용할 데이터 모델을 작성하는 단계이다.
- 물리적으로 컴퓨터에 어떻게 저장될 것인가에 대한 정의를 물리적 스키마라고 한다.
- 해당 단계에서 트랜잭션 세부사항을 설계하고, 테이블, 칼럼 등으로 표현되는 물리적인 저장 구조와 사용될 저장 장치, 자료를 추출하기 위해 사용될 접근 방법 등을 정한다.
=> 다시 한 번 말하지만, 실제 프로젝트에서는 물리적 데이터 모델링을 수행하는 경우는 드물다...
💡 개념적 데이터 모델링 으로써 ERD 작성하기
사실, 개념적 데이터 모델링 의 산출물 = ERD 라고 생각하면 편하다.
이후에 논리적 데이터 모델링 에서 정규화, 참조 무결성 규칙 정의, M:M 관계 해소 등을 추가적으로 해준다.
우리가 ERD 최종 결과물을 완성시키면 개념적/논리적 데이터 모델링 과정을 거쳤다고 생각하면 편하다!
💡 엔티티 찾기
요구 사항을 보자.
[ 글 by 사용자 / (글 아래에) 댓글 by 사용자 ] 라는 UI 가 있다고 생각해보자.
- 이 과정에서 Entity를 뽑아내는 것이다!
💡 속성 찾기 + 식별자
UI 혹은 PM 과의 대화를 통해 속성을 알아낸다.
기본 키(PK)
- 각 테이블의 행을 고유하게 식별할 수 있게 해주는 키!
외래 키(FK)
- 연관관계를 설정할 때 사용!
- 한 테이블의 기본 키가 다른 테이블의 칼럼으로 사용되어서 그 테이블과의 관계를 표현한다!
💡 관계 찾기 + Cardinality / Optionality
Cardinality
한 테이블이 다른 테이블을 얼마나 가지고 있는가
1:1, 1:N, N:M 으로 표현!
Optionality
관계가 필 수 인지!
글은 저자가 필수로 갖고 있어야 하지만,
저자는 글을 필수로 갖고 있을 필요는 없다.
💡 개념적 데이터 모델링의 결과물
💡 논리적 데이터 모델링
- Entity -> Table, Attribute -> Column, Relation -> PK,FK
💡 외래 키 설정 (연관 관계의 주인)
어디에 PK 를 둘까? = 누가 주인?
일대일
장단점이 있지만 주테이블(많이 쓰는 테이블)에 있는 것이 편하다!
일대다
N쪽에 있어야 한다!
이유는?
다대다
가운데 매핑 테이블을 두자!
💡 정규화 / 반정규화
추가적으로 정규화 / 반정규화도 해주어야 하지만, 실제로는 1~3 정규화 정도만 해주고, 크게 사용하지는 않는다...
정규화와 반정규화는 데이터베이스 설계 과정에서 한 단계이다.
이 두 가지 과정은 데이터를 효율적으로 구성하고 데이터의 중복을 최소화하는 데 도움이 된다.
(추가적인 내용은 구글링)
✒️ 2. DATABASE 설계 해보기
도서 대여 관리 app에 대한 요구사항을 보자
💡 요구사항
(1) 사용자 관련 요구사항
- 카카오 소셜 로그인을 구현 할 예정이다.
- 회원 탈퇴 기능이 필요하다.
- 이름, 닉네임, 전화번호, 성별이 필요하다.
(2) 책 관련 요구 사항
- 사용자가 책 여러 권을 대여할 수 있다.
- 책은 하나의 카테고리가 있다.
- 책은 제목, 설명에 대한 정보가 필요하다.
- 책 소개 페이지에 해시태그가 붙을 수 있고,
책 한 권에 해시태그 여러 개가, 해시태그 하나가 여러 책에 붙을 수 있다. - 사용자가 책 설명 페이지에서 책에 좋아요를 누를 수 있다.
- 책 카테고리 별로 현재 몇 개의 책이 있는지 집계가 필요하다.
(3) 알림 관련 요구 사항
- 알림은 공지 관련 알림, 책 반납 시간 임박 알림, 마케팅 알림이 있을 수 있다.
💡 어떻게 설계할까?
🤔 간단히 규칙을 생각해보자
1. 먼저 테이블이름(우측 상단의 이름)은 SnakeNaming 해줘야 한다!
2. 기본 키를 위해 각 엔티티 정보 중 유일한 값을 기본 키로 설정하기 보다. (보통 db 이론 공부할 때 이렇게 배운다.)
아예 새로운 index를 두어서 유일한 값으로써 기본 키로 사용하는 것이 편하다.
그리고 굳이 book_id, member_id 이렇게 나누기 보다 id로 이름을 짓는 것이 좋다.
3. 기본 키 타입은 int가 아닌 서비스 확장을 고려하여 bigint로 해주자.
✅ 이제 타입을 정해보자
사실, 타입 같은 경우는 PM(Project Manager)에게 물어가 보며 확실히 결정해야 한다.
따라서, DB 설계 단계에서 본인은 "당연히, 무릇" 이렇게 할 것이라고 생각하더라도,
조금의 의문이라도 든다면 바로 PM에게 물어봐서 확인 해야 한다!
그렇다면, 지금 PM이 없으니 임의로 정해보자
MySQL에서 varchar의 괄호 안은 최대 글자수를 나타낸다 ( 유니코드 기준 글자수 )
text 타입은 길이 제한이 없는 타입니다.
(예시로 이렇게 해놓은 것 뿐, 이 또한 PM과 상의 후 글자 수 제한을 어떻게 할지 결정하는 것이 좋다.)
- member 테이블에서 gender의 경우,
- 0 남자 / 1 여자
- varchar
- enum타입으로 문자로 설정
등 여러 가지 전략 중 하나를 택해서 사용해도 된다!
✅ created_at, update_at 추가
데이터를 추적하고 관리하기 위해서 보통 모든 테이블에서 created_at, update_at를 추가한다!
이 데이터 타입은 datetime(6), 즉 소수점 6자리까지 구분하여 저장한다.
참고로, SpringBoot에서 entity를 작성할 때, created_at, update_at 와 같은 filed는
BaseTimeEntity로 따로 빼서 관리하는 것이 좋다!
🤔 왜 밀리초까지 구분할까?
항상 코딩을 할 때는 확장성을 고려해야 한다!
거창한 말이 아니라, 자주 쓰이는 기능이나, 이후에 추가할 수도 있다! 라고 생각이 된다면,
해당 기능을 염두에 두고 개발해야 한다는 뜻이다.
데이터의 최신순 정렬 기능 또한 정말 흔한 기능이다.
그래서 MySQL 기준 최대 자릿수인 6자리까지 저장하는 것이다!
✅ 멤버에는 status와 inactive_date 추가
- 상태 (휴면, 탈퇴, 활동중) 를 저장할 status와
- 비활성화된 날짜를 추적하기 위해 inactive_date를 추가하는 것이 좋다.
🤔 왜 member에는 상태를 추가해!?
우리는 어떠한 엔티티 (게시글이든, 회원이든) Delete하는 경우가 있다!
보통 단순히 생각하면 "그냥 테이블에서 Delete해버리면 되는 거아냐?" 생각한다.
오케이, 이렇게 HTTP Method 중 Delete로 쏴주고, 실제 테이블에서 바로 삭제해버리는 것을
Hard Delete라고 부른다!
하지만, 이러한 Hard Delete 방법은 지양하는 것이 좋다.
요구 사항에 따라 다르겠지만, 우리의 사용자들은 변덕 쟁이 들이다...
항상 undo에 대한 기능을 염두해 두어야 한다.
서비스에 따라 다르겠지만, 우리가 열심히 쓴 글을 지웠을 때 한 번의 클릭으로 날아간다고 생각해보자... (그래본 경험 있어요..)
또한, 회원 탈퇴의 경우도 요즘에는 기간 내로 돌아온다면 복구할 수 있는 기능이 대부분이다.
(서비스의 사용자 수 유지 측면에서도 매우 중요하다.)
이러한 고민이 드는 순간 PM은 정해야한다.
1. 로직을 단순화 하기 위해 그냥 Hard Delete한다.
-> 다만 이러한 경우에는 무조건 적으로 UI에서 경고 메시지를 한 번 띄워주는 것이 중요하다.
2. 일단 비활성 상태로 두고, 일정 기간동안 비활성인 경우 그 때 비로소 자동 삭제 되도록 한다!
따라서, User와 같이 곧바로 삭제시켜버리는 것을 지양할 엔티티는 2번의 방법을 쓰도록 하였다!
즉, 일단 비활성 상태로 두고, 일정 기간동안 비활성인 경우 자동 삭제가 되도록
이 때, status는 active, inactive등 enum으로 관리하고!
(활성/ 비활성 이분법적으로만 구분 한다면, 0과 1을 사용해도 된다.)
비활성화 한 지 얼마나 지났는지 알아내기 위해 inactive_date를 따로 둔다.
여담이지만, SpringSecurity를 사용하여 인가/인증을 처리할 때,
여러가지 내부 메소드, 멤버 변수를 커스터마이징 해서 사용한다.
그 중 User의 정보를 담는 UserDetails에는 isCredentialsNonExpired, isEnabled 등의 메서드가 있다.
이러한 특성 때문에 미리 멤버나 유저 엔티티에는 상태와 관련된 필드를 추가하는 것이 좋다!
🤔 그럼 어떻게 자동으로 지우지?
바로 batch를 사용한는 것!
batch 란 정해진 시간에 자동으로 실행되는 프로세스 이다.
그리고, 이렇게 기간을 정해두고 자동으로 삭제하는 방식을 Soft Delete라고 한다!
꼭 회원 뿐만이 아니라, 아까 게시글 이야기도 하였는데
이 처럼 복구가 필요한 요구사항이 있는 경우, 모두 Soft Delete 해야 한다!
=> 이러한 경우, HTTP Method는 Delete가 아닌 Patch를 사용한다!
🤔 연관관계...
이제 테이블 간의 연관관계를 표기해주어야 한다..
MySQL은 RDB 기반이고, RDB에서는 외래 키로 연관 관계를 표시한다!
여기서 사용자가 책을 대여할 때, 어떤 연관 관계를 사용해야 할까요!?
단순히 생각하면 사용자 : 책 = 1 : N 으로 하면 될 것 같다!
하지만, book을 책 종류라고 생각한다면!
한 종류의 책을 여러권 뒀을 수 도 있다는 말.
즉, 사용자 : 책 = N : M 으로 규정한다!
우리는 다대다 관계에서 가운데에 양쪽의 기본키를 왜래키로 가진, 매핑 테이블을 따로 둔다고 배웠다.
🤔 책에 붙는 해시태그? 사용자가 책에 누르는 좋아요?
- 해시태그도 여러개가 한 책에 붙고,
책에 여러 해시태그가 붙기에
N : M 관계 - 마찬가지로 한 종류의 책에 사용자 여러명이 좋아요를 누르고,
한 사용자가 여러 책에 좋아요를 누르기에
역시 N : M 관계
🤔 알림...
알림 의 경우 ERD 설계할 때 너무 골 아프다.
"알림" 자체는 설계에 큰 문제가 없지만,
특정 알림 터치 시에 특정 창으로 이동이 된다는 요구사항이 있다면, 고민이 되기 시작한다...
그렇다는 것은 "어떤 알림"인지를 알아야 한다는 것인데...
이를 해결할 몇가지 방법이 있다!
1. 슈퍼타입 / 서브타입 구성
2. 하나의 테이블에 두고 / datatype으로 구분
: dtype을 테이블로 따로 관리를 하거나 enum으로 관리하는 것은 선택
3. 그냥 테이블 다 나눠버리기
💡 데이터베이스 설계 마무리
사실 DB 설계란 처음부터 완벽하게 설계를 할 수는 없다...
(개발 중 변경 사항이 생길 수 밖에 없다.)
그러나 설계를 많이 하다보면,
어떤 부분을 설계하면서
'아 이부분은 좀 애매한데, 나중에 이런 식으로 다시 바꿀 것 같다' 하는 부분이 생길 것이다.
위 요구사항 만 봐도,
사용자가 책에 좋아요를 누를 수 있고 이를 집계를 한다고 했다.
그럼, 이럴 때
1. 책 테이블에 likes 칼럼을 두고 좋아요를 집계하는 것이 좋을까?
2. 좋아요를 누르면 + 1, 취소하면 -1 계산하는 게 좋을까?
다시. 아래의 요구 사항이 추가된다면 어떻게 될까?
사용자 간 차단 기능 추가 => 차단 한 사용자가 누른 좋아요는 집계를 하지 않는다.
이렇게 변경된다면...?
2번 처럼 하신 분은 골 아프게 된다...
'차단 한 경우마다 좋아요를 -1을 해야 하나??ㅠㅠ'
좋아요 숫자 집계는
순수 DML 연산으로, book_likes에서 해당 책 아이디를 가진 것이 몇 개인지 직접 세는 것이 좋다!