글의 목적
테이블을 설계하는 것은 무척이나 어렵다.
복잡하고 동적인 현실 세계의 관계를 정적인 구조로 만드는 일은 수 많은 상황을 예측해야 하는 일이다.
테이블을 설계하며 한 고민의 과정을 정리함으로써 다음에는 좀 더 나은 생각을 할 수 있도록 글을 작성한다.
캐치 테이블의 테이블 구조
가장 크게 한 고민은 어떠한 요소를 필드로 둘지, 테이블로 둘지 두 결정 사이의 장단점을 비교하며 설계 당시에는 가장 나은 선택을 했다.
테이블을 설계할 때 가장 중요하게 여긴 가치
- 확장 (기능의 확장)
- 코드로 구현했을 때의 모습 (도메인을 분리해 다른 어플리케이션으로 작동할 수 있을지)
특이사항으로 실제 캐치테이블에서는 지원하지 않는 매장 내 테이블을 지정하여 예약을 하는 기능을 추가했다. (진짜 어려웠음)
복잡한 구조를 쪼개서 바라보기(애그리거트 루트를 기준으로)
이제 캐치테이블이라는 어플리케이션을 조금 더 좁혀서 바라보자. 현재로서는 다음과 같이 좁힐 수 있어 보인다.
- 매장
- 예약
- 웨이팅
매장
먼저 매장으로 볼 수 있는 도메인을 살펴보자.
이 어플은 신기하게도 메뉴를 보여줄 때 메뉴가 아닌 메뉴 카테고리를 메인으로 보여준다. 즉, 메뉴가 없더라도 메뉴 카테고리만으로 메뉴를 표현할 수 있다.(메뉴만 존재하는 경우도 있지만 드물다.)
지역 테이블 같은 경우는 추후 확장을 위해 필드가 아닌 테이블로 두었으며, 메뉴에 달라 붙는 기능 중 메뉴 리뷰라는 것이 있는데 시간상 구현하지 않았으며 코드 레벨에서 어떻게 확장 가능하게 작성할 수 있는지 여기서 보여주도록 하겠다.
이외에는 해석하는데 문제 없을 것이다.
예약
이제 예약 도메인을 살펴보자. 복잡하니 잘 읽어주기를 바란다.
테이블을 설명하기에 앞서 예약을 진행하는 흐름은 다음과 같다.
- 매장을 선택한다.
- 매장에서 예약 가능한 날짜, 시간을 선택한다.
- 해당 날짜와 시간에 예약 가능한 좌석을 선택한다.
- 앞선 정보를 토대로 예약을 생성한다.
이제 테이블을 설명해보겠다.
- 매장과 매장 예약은 1:1로 연결 되어 있다.
→ 예약에 얽혀있는 방대한 정보를 매장과 바로 연결 짓기에 매장은 자신의 역할에 부담을 느낄 것이다.
따라서 매장_예약이라는 중간 테이블을 만들어 예약을 위한 매장의 책임을 덜어냈다.
매장 예약을 매장이라고 치환하고 봐도 무방할 듯 - 매장_예약과 매장_테이블은 1:N 관계이다. (= 매장에는 여러개의 매장 테이블이 있다. 여기서의 테이블은 db에서의 테이블 아닌 식당에 있는 식탁을 의미)
→ 위 설명만으로 충분할 것이다. 좌석이 아닌 테이블로 설정한 이유는 영화관과는 다르게 바로 옆 좌석이 같은 테이블일 수 있기 때문에 테이블로 설정했다. - 매장_예약과 매장_예약_날짜_시간은 1: N 관계이다. (= 매장은 날짜 + 시간 조합으로 예약을 미리 만들어 놓는다.)
→ 캐치 테이블의 예약을 보면 매장에 들어가 예약 가능한 날짜와 시간을 선택한 뒤 예약을 할 수 있다. 따라서 이를 매장 예약 날짜 시간이라는 테이블로 만들었다. - 매장_예약_날짜_시간과 매장_테이블은 N:M 관계이다. (= 매장의 특정 날짜와 특정 시간에 이용 가능한 테이블은 여러개 존재한다. 또한, 테이블은 날짜별로 여러개 존재한다. 예를 들어 1월 1일 5시에 등록된 테이블이 1월 2일 5시에 등록되어 있을 수 있음)
- 4번으로 인해 다대다 관계를 1:N, N:1로 풀어내기 위한 매장_예약_날짜_테이블 이라는 테이블이 만들어졌다.
- 예약할 때 여러개의 테이블을 선택할 수 있으므로 예약과 매장_예약_날짜_테이블은 1:N 관계가 된다. 헷갈린다면 이 또한 예약과 매장_테이블의 관계를 생각해보면 이해가 쉽다.
→ 예약을 할 때 여러개의 테이블을 선택할 수 있으며 해당 테이블이 다른 날짜의 예약에도 포함될 수 있으니 예약과 매장 테이블의 관계는 N:M이 되는 것이다.
좌석을 선택할 수 있다는 기능을 하나 추가함으로써 테이블 구조가 많이 복잡해졌다.. 읽히지 않는다면 예약 흐름을 생각하고 스스로 테이블 구조를 그려본 뒤 읽으면 더 잘 읽힐 것이다. (이는 캐치 테이블이라는 서비스에 국한하지 않고 다른 서비스의 테이블을 설계 할 때에도 그림을 그려보는 것을 추천한다. 암산 하는 것 보다 훨씬 나을 것이다.)
웨이팅
다음으로 웨이팅이다. 웨이팅은 예약에 비해 비교적 간단하다.
- 예약과 마찬가지로 매장과 웨이팅 사이에 웨이팅이라는 중간 테이블을 둬 매장은 매장의 역할에, 웨이팅은 웨이팅의 역할에 충실하게 하려고 했으며 매장 웨이팅이라는 테이블에 두 관계를 위해 존재하는 필드를 넣어 뒀다.
→ 매장 웨이팅의 필드를 매장에 들어갈 필드로 보는 것이 이해가 더 쉬울수도 있다. - 사용자는 여러개의 웨이팅을 등록할 수 있다. 1 : N
(웨이팅 등록은 한번에 한개만 되지만 취소한 웨이팅, 이미 입장한 웨이팅 등을 포함하면 여러개의 웨이팅을 가질 수 있는 셈이다.) - 매장은 여러개의 웨이팅을 받을 수 있다. 1 : N
단순히 캐치테이블의 기능을 구현하는 것은 그렇게 높은 난이도의 개발은 아니었지만 예약 시 좌석(테이블) 을 선택할 수 있다는 기능을 추가하니 개발 난이도가 급격하게 올라갔다.
테이블 구조가 복잡해지고 외래키 제약조건이 많아 테이블 생성 순서, 삭제 순서 데이터를 넣는 순서 모두 지켜야 했으며 개발을 할수록 속도가 느려지는 것 같았다.
이런 복잡한 구조를 코딩할 때 외래키를 안쓰는 것이 더 낫겠다는 생각이 들었으며, 데이터 무결성은 어플리케이션 단에서 지키게 하는 것이 효율적이라고 생각이 들었다.
외래키가 주는 장점이 있지만 장점에 비해 단점이 더 큰 것 같다..
이번 프로젝트를 통해 배운점
- 다대다 관계의 설계법
→ 두 관계를 봤을 때 다대다 관계라는 것을 인지하기 조차 어려운 경우가 많지만 좀 극복한듯 - 외래키를 선택적으로 쓰자
→ 데이터 무결성이 오히려 생산성을 너무 떨어지게 함 - 이 정도를 얻은 것 같다.
'프로젝트 > 고민' 카테고리의 다른 글
우리의 문제 상황에 맞는 락 구현 (0) | 2023.10.06 |
---|---|
이벤트 기반으로 책임 분리 (0) | 2023.10.05 |
주문 코드 리팩토링 (0) | 2023.08.19 |
OpenFeign 적용기 (0) | 2023.07.12 |
elasticsearch에 형태소 분석기 적용하기 (0) | 2023.06.12 |