본문 바로가기

개발 프로젝트/2024 제로베이스 프로젝트

제로베이스 연습프로젝트 - 매장 테이블 예약 서비스(1) 서비스 요구사항 및 설계

고려할 관점 나열

(1) API 설계

- 기능과 Input/ Output 명확화 : 정확한 input과 output이 산출되고 이것이 견고해야 변경이 적음

- 데이터 validation : 

- 정책 및 예외 : 꼼꼼하게 예외처리를 할것

 

(2) 데이터베이스 설계

- 테이블 정규화 : 정규화 원칙에 부합되도록 테이블을 설계

- 인덱스 설정 : crud의 패턴을 고려하여 인덱스 종류 및 대상칼럼 설정

 

(3) 최적화 기술고려

- 캐쉬 : 데이터베이스 CRUD 패턴을 확인하고 캐쉬 적용포인트 고려

- 쓰레드 : 쓰레드는 현재 배우고 있는 중이므로 적용보류

- 보안 : 보안도 적용보류

 

프로그램 요구사항 (주어짐)

  • 매장을 방문할때 미리 방문 예약을 진행하는 기능"을 구현하고자 합니다.
  • 위 예약을 받기 위해서는 점장(매장의 관리자)이 매장정보를 등록하고자 하는 기능을  구현하고자 합니다.
  • 매장을 등록하기 위해서는 파트너 가입 기능"이 구현되어야 합니다.
  • 매장 이용자가 서비스를 통해서 매장을 검색하고 상세 정보를 확인하는 기능구현하고자 합니다.
  • 서비스를 통해서 예약한 이후에, “예약 10분전에 도착하여 키오스크를 통해서 방문  확인을 진행하는 기능"을 구현하고자 합니다.
  • 예약 및 사용 이후에 리뷰를 작성하는 기능을 구현하고자 합니다.

식당이나 점포를 이용하기 전에 미리 예약을 하여 편하게 식당/점포를 이용할 수 있는 서비스 개발

  •  매장의 점장은 예약 서비스 앱에 상점을 등록한다.(매장 명, 상점위치, 상점 설명)
  • 매장을 등록하기 위해서는 파트너 회원 가입이 되어야 한다.(따로, 승인 조건은 없으며 가입 후바로 이용 가능)
  • 매장 이용자는 앱을 통해서 매장을 검색하고 상세 정보를 확인한다.
  • 매장의 상세 정보를 보고, 예약을 진행한다.(예약을 진행하기 위해서는 회원 가입이 필수적으로 이루어 져야 한다.)
  • 서비스를 통해서 예약한 이후에, 예약 10분전에 도착하여 키오스크를 통해서 방문 확인을 진행한다 .
  • 예약 및 사용 이후에 리뷰를 작성할수있다.
  • 리뷰의 경우, 수정은 리뷰 작성자만, 삭제 권한은 리뷰를 작성한 사람과 매장의 관리자(점장등)만 삭제할 수 있습니다.

 

 

개발명세서 검토

파트너회원 partners

 

1. 파트너 회원 가입 API

  • POST /partners
  • Parameters : 파트너아이디(partnerId), 사업자명(partnerName), 사업자번호(businessId)
  • Policy: 파트너아이디 및 사업자명 10글자 초과시, 사업자번호 10자리 아닐시, 파트너 아이디 중복시 실패응답 (구현필요)
  • Success Response: partnerId: 파트너 아이디, registrationDate: 등록 일시
  • CRUD : 파트너회원 Create 진행

 파트너 회원 (DB)

  • Fields:
    • Id (Long): 파트너 ID (auto-incre)
    • partnerId (VARCHAR): 파트너 고유ID(unique)
    • userName (VARCHAR): 파트너 이름
    • buinessId (long) : 사업자번호
    • phoneNumber(long) : 핸드폰번호
    • registrationDate (DATETIME): 등록 일시
    • 주요 CRUD
      • 파트너 고유ID(unique key) 레코드 create

매장 관리

 

1. 매장 등록

  • POST /stores
  • Parameters:  파트너ID, 매장고유명, 위치, 매장설명
  • Policy: 사용자가 파트너 회원이 아닌 경우, 매장고유명 중복, 
  • Success Response: 매장고유명, 매장위치, 매장설명
  • CRUD : autoincre으로 매장등록 Create 진행

2. 파트터명으로 매장검색

  • GET /stores?partnerId
  • Parameters: query: 파트너ID
  • Policy: 파트너 ID에 해당하는 매장존재하지 않을 시에 실패, 
  • Success Response: 리스트 : 매장아이디, 매장고유명, 위치, 매장설명
  • CRUD : 파트너명 칼럼과 일치하는 매장의 레코드 Read 진행

 

3. 사용자가 매장명으로 매장 검색 및 상세 정보 조회

  • GET /stores/storeId
  • Parameters: query: 매장고유명
  • Policy: 고유명 존재하지 않을 때,
  • Success Response: 매장아이디, 매장고유명, 위치, 매장설명
  • CRUD : 매장 아이디로 매장 레코드 Read 진행

 매장 (DB)

  • Fields:
    • Id 매장ID
    • partnerId 파트너ID
    • storeId 매장고유명
    • storeLocation 매장위치
    • storeGeolocaion 매장좌표명(향후도입)
    • storeComment 매장설명
    • registeredAt 등록일시
    • Rating 평점(reddis 항목)
    • reviewNumber 평점개수(reddis 항목)
  • 주요 CRUD
    • 매장고유명(unique key) 레코드 생성
    • 매장고유명(unique key)과 매칭되는 단일 코드 READ
    • 파트너명 (Foreignkey)과 매칭되는 복수 레코드 READ
    • 별점 Rating을 update ??? 

 

사용자 관리

1. 사용자등록

 

사용저 회원 가입 API

  • POST /users
  • Parameters : 사용자아이디, 핸드폰번호
  • Policy: 10글자 초과시, 사용자 아이디 중복시 실패응답 , 핸드폰번호 010으로 시작하고 자리수 다를시 실패
  • Success Response: UserName 사용자 아이디, 
  • CREATE : 사용자 아이디 create - autoincre

 사용자 회원 (DB)

  • Fields:
    • id (Long): 사용자 ID
    • userId (String): 사용자 고유ID
    • userName(String) : 사용자 본명
    • PhoneNumber(String) : 사용자 폰번호
    • registrationDate (DATETIME): 등록 일시
    • CRUD 
      • 사용자 아이디 create

예약 관리

1. 매장 예약

  • POST /reservations
    • Parameters: 사용자의 아이디,  매장의 아이디, 예약 시간
    • Policy: 사용자가 회원이 아님, 매장 정보가 없는 경우, 예약시간이 과거일때,
    • Success Response:  예약 아이디, 예약 일시, 예약매장
    • CRUD : 예약레코드 생성하기

2. 매장주인이 자신의 매장의 예약리스트 확인

  • GET /reservations/by-partner?storeId
    • Parameters: 매장ID
    • Policy: 시간순서대로 정렬
    • Success Response: 예약아이디, 예약매장, 예약시간, 확정여부
    • CRUD : 매장 ID를 통해서 예약 리스트 시간순으로 READ하기
    • Note : 파트너가 자신의 예약 리스트를 뽑으려면 바로 파트너 ID로 조회하는 것이 아니라, 매장 ID로 조회하는게 맞는것이, 파트너 ID로 조회할 경우 무작위 순서로 정렬되기때문에 별도로 정렬해야함. 그리고 API확장성 측면에서도 매장 ID 기준으로 조회하는게 맞다고 생각함.

3. 사용자가 자신의 예약리스트 확인

  • GET /reservations/by-user?userId
    • Parameters: 매장ID
    • Policy: 시간순서대로 정렬
    • Success Response: 예약아이디, 예약매장, 예약시간, 확정여부
    • CRUD : 사용자 ID를 통해서 예약 리스트 시간순으로 READ하기

3. 사용자가 매장의 키오스크에서 10분전 예약 조회

  • GET /reservations/?userid&storeId
    • Parameters: 사용자ID, 매장ID
    • Policy: 시간순서대로 정렬
    • Success Response: 예약아이디, 예약매장, 예약시간, 확정여부
    • CRUD : 사용자 ID와 매장ID 그리고 예약일을 통해서 예약 ID조회

4. 매장의 주인이 예약을 거절하거나, 사용자가 예약을 취소하는 등의 방문 상태변경

  • PUT /reservations/reservations/{reservationId}?status=confirmed/rejected
    • Parameters: 예약아이디
    • Policy: 예약이 존재하지 않거나 이미 확인된 경우 실패
    • Success Response: 예약아이디, 예약자Id, 예약매장, 예약시간, 예약상태
    • CRUD :  예약 ID로 지정하여 예약상태변경

예약 (Reservations)DB

  • Fields:
    • reservationId (UUID): 예약 고유 ID
    • userId (UUID): 예약한 사용자 ID
    • storeId (UUID): 예약된 매장 ID
    • reservationTime (DATETIME): 예약 시간
    • regissteredAt (DATETIME): 예약 생성 일시
    • confirmedAt (DATETIME, nullable): 방문 확인 일시
    • status (Enum)
    • 주요CRUD
      • 사용자 ID, 매장ID, 예약일을 통해서 예약 ID조회
      • 사용자 ID, 예약일 순으로 예약 리스트 READ하기
      • 매장 ID, 예약일 순으로 예약리스트 read하기
      • 예약 ID를 통해서 예약 상태변경하기

 

 

리뷰 관리

1. 리뷰 작성

  • POST /reviews
    • Parameters:  작성자아이디, 예약ID, rating: 평점, comments: 리뷰 내용
    • Policy: 예약이 존재하지 않거나 이미 확인된 경우 실패
    • Success Response: reviewId: 리뷰 아이디, postedDate: 리뷰 작성 일시
    • CRUD :  리뷰 ID를 생성

2. 작성자의 리뷰조회

  • GET /reviews?userId
    • Parameters:  작성자아이디
    • Policy : 작성자 ID가 존재하지 않거나 이미 확인된 경우 실패
    • Success Response: reviewId: 리뷰 아이디, postedDate: 리뷰 작성 일시
    • CRUD :  작성자 ID로 리뷰를 READ

3. 매장의 리뷰조회

  • GET /reviews?userId
    • Parameters:  매장ID
    • Policy : 작성자 ID가 존재하지 않거나 이미 확인된 경우 실패
    • Success Response: reviewId: 리뷰 아이디, postedDate: 리뷰 작성 일시
    • CRUD :  매장 ID로 리뷰를 READ

 

4. 리뷰 수정

  • PUT /reviews/{reviewId}
  • Parameters: reviewId: 리뷰 아이디, rating: 수정할 평점, comments: 수정할 내용
  • Policy: 작성자만 수정가능
  • Success Response: updatedDate: 수정 일시
  • CRUD :  리뷰 ID로 수정

5. 리뷰 삭제

  • DELETE /reviews/{reviewId}
  • Parameters: reviewId: 리뷰 아이디
  • Policy: 리뷰 작성자 또는 매장 관리자만 삭제 가능
  • Success Response: deletedDate: 삭제 일시
  • CRUD :  리뷰 ID로 삭제

 리뷰DB (Reviews)

  • Fields:
    • Id
    • reviewId (UUID): 리뷰 고유 ID
    • 예약ID
    • 사용자ID
    • 매장ID
    • rating (INT): 평점 (1-5)
    •  reviewContents : 리뷰 내용
    • postedDate (DATETIME): 리뷰 작성 일시
    • updatedDate (DATETIME, nullable): 리뷰 수정 일시
    • deletedDate (DATETIME, nullable): 리뷰 삭제 일시
    • 주요 CRUD:
      • 리뷰ID로 생성하고 조회
      • 매장ID로 리뷰조회
      • 작성자ID로 리뷰 조회

 

설계고려사항

 테이블 정규화 설계고려

1.  예약과 방문확인과 리뷰는 비즈니스상 연속적 과정인데 예약/ 방문확인/ 리뷰를 별도의 테이블로 나눠야할 것인가 하나의 테이블로 둘것인가?

  A.테이블을 합쳐서 관리 : 예약테이블에 예약확정데이터+리뷰데이터를 추가해서 사용하면 예약레코드 리스트에서 예약확인과 리뷰작성d을 하지않는 데이터까지 관리를 해야하기 때문에 데이터량이 확연히 늘어날 수 있음. 

  B. 테이블을 별도로 분리 : 별도로 관리할 경우, 예약확정이 되지 않을 데이터와 리뷰작성을 안할 데이터에 대해서, 해당 데이터를 관리하지 않아서 데이터량 자체는 줄일 수 있음. 단, 예약레코드를 찾아볼때, 예약확정여부를 확인하려면 별도로 다른 테이블을 확인해야함. 

  결론 :  쿼리의 양상을 보면, 예약레코드와 예약확정여부를 같이 확인할 필요가 많아보임, 그러나 테이블을 별도로 분리하면 예약레코드 조회와 예약확정테이블을 별도로 조회한 후 Join쿼리를 사용해야해서 DB에 부하를 줄 수 있음. 그러므로 예약확정 데이터는 예약레코드의 한 column으로 status로서 관리할 것. 리뷰데이터는 예약과 함께 조회할 일이 적고 리뷰데이터 자체의 칼럼이 양이 많아서 정규화를 위한 분리를 할 것. 

 

2. 파트너의 예약관리 기능 조회편의를 위해서 예약테이블에 칼럼으로 파트너이름을 넣을 것인가?

  • A. 넣을 때
    • 장점 : 파트너가 자신의 매장의 예약을 전체 조회하고자 할때 한번의 호출로 파트너사의 매장의 예약을 모두 호출할 수 있게된다
    • 단점
      • 파트너사를 넣음으로 인해서 정규화원칙을 깨어 중복된 데이터량이 많아짐
      • 파트너사를 넣어서 조회에 사용한다고 하여도 매장별로 보고자할텐데, 별도로 정렬을 해야할 것이다. 그럴바에는 파트너의 매장을 조회하고 
      • 매장별로 조회하는 것이 API 확장성이 좋다. 
      • 파트너의 매장을 조회하고 join문으로 매장의 예약을 조회하는 방법이 있다.

3. 파트너와 유저 그리고 멤버테이블을 별도로 분리할 것인가

  • 확장성 측면에서 파트너의 정보와 멤버의 정보가 서로 다른 것들이 많이 별도로 추가될 것이라고 생각되어 확장성 측면에서 별도로 분리함. 

4. 리뷰아이디에 유저아이디와, 매장아이디를 넣는 것은 역정규화인데 추가할 것인가? 그 근거는?

  • 유저아이디, 매장아이디를 역정규화 : 리뷰는 예약을 근거로 작성하는 것이기때문에 예약에 모든 정보가 포함되어있다. 그러나 매장아이디가 역정규로화로 들어가야하는 이유는, 매장의 평점 점수를 조회하고 유지하기 위해서 이다. 매장아이디가 리뷰점수에 들어가지 않는다면,  예약테이블에 매장아이디를 조회하고 각 예약 ID를 리뷰테이블에 조회하여 1:1로 대응해야할 것이다. 혹은 예약테이블에 리뷰작성여부 테이블을 추가하여 예약ID를 조회하여 리뷰테이블을 N번 조회해야할 것이다. 매장의 예약의 개수(n)에 비례하여 logx*y*logz(전체리뷰의 개수)으로 시간 복잡도가 상당히 늘어나는 형태임. 반면, 역정규화를 하면 logm으로 제한시킬 수 있다.

 

 캐쉬도입

- 예약당일 가까이에 예약조회가 빈번할 것같으니 캐쉬도입 (키오스크)

- 매장데이터의 경우 예약의 relatedENtity로서 검색이 빈번하고 데이터 크기가 작으며 변경이 거의 없을 것으로 보여

- partnerId는 데이터량이 작고 relatedEntity로써 검색이 빈번하고 변경이 거의 없을 것으로 보여 캐쉬도입

 

3. store 테이블의 rating 데이터 관리

  store 테이블에 있는 Rating column review 테이블의 rating을 평균으로 산출하는 데이터인데 이 경우, 해당 store rating을 어떻게 유지보수할 지 고민일 필요하다.

A. 매장의 리뷰가 작성될때마다 리뷰 table내에 store에 해당하는 row를 select하여 평균을 구하고 업데이트할것인지

B. 정기적인 간격으로 peaktime이 아닐때 rating을 업데이트하고 실시간 동기화는 하지 않을 것인지

결론 : A와 B를 절충하여, DB의 store 테이블의 rating column은 peak가 아닌 시간에 정기적으로 업데이트를 하고, store의 rating column과 rating개수를 캐쉬에 저장한다. 그리고 review 데이터가 업데이트 될때마다 해당 데이터를 서버사이드에서 계산하고 reddis 내에 실시간으로 동기화한다.

 

4. index설정

 

자주사용되는 foreign key의 경우 별도로 index를 설정하지 않으면 primarykey만 index로 설정됨.

 

 

5. 복잡한 Entity관계 최적화를 위한 LazyLoading

 

어떤 데이터베이스가 어떤식으로 CRUD될 것인지 미리 예측하고 CRUD될때 파급효과를 최소화하기위해서 redis사용과 lazyloading을 적절히 혼합하여서 사용하고자한다. redis도입정도만 우선은 고려하고, 그외 필요사항은 grinder을 통해서 확인해보고 필요에따라 도입해보자