You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
기존 큐레이션은 "이름 + 설명 + 커버 이미지 + 위스키 N개 매핑" 형태의 레거시 키워드 큐레이션만 지원했다.
신규 요구사항에서는 큐레이션 타입에 따라 화면에 필요한 데이터 구조가 달라진다.
추천 위스키: 여러 위스키 카드와 추천 코멘트
위스키 페어링: 위스키와 어울리는 음식/아이템, 페어링 노트
위스키 시음회: 일시, 장소, 참가비, 정원, 신청 링크, 시음 위스키
따라서 큐레이션 타입별로 서로 다른 payload를 저장하고, Product API에서는 FE가 타입별 화면을 그릴 수 있는 형태로 내려주는 구조가 필요하다.
현재 채택안
현재 v2 큐레이션은 운영자가 임의의 스펙을 무한 생성하는 구조가 아니라, 개발자가 구성한 스펙을 서버가 동기화하고 어드민이 그 스펙 중 하나를 선택해 payload를 저장하는 구조다.
현 시점의 스펙은 3개로 고정한다.
spec code
의미
FE 렌더링 단위
RECOMMENDED_WHISKY
추천 위스키
추천 위스키 카드 목록
WHISKY_PAIRING
위스키 페어링
페어링 아이템 목록
WHISKY_TASTING_EVENT
위스키 시음회
시음회 상세 정보
추후 스펙을 추가할 수는 있지만, 운영자가 런타임에서 자유롭게 만드는 방식이 아니다. 개발자가 OpenAPI 기반 스펙 리소스를 추가하고 서버 기동 시 curation_spec에 동기화한다.
기존 큐레이션과 v2 큐레이션의 관계
기존 큐레이션은 제거하지 않는다.
레거시 큐레이션: 기존 위스키 목록형 큐레이션 유지
v2 큐레이션: spec 기반 payload 큐레이션을 별도 API로 추가
즉, 기존 curation_keyword 계열을 바로 대체하거나 삭제하지 않고, 신규 v2 surface를 병행한다.
데이터 구조
현재 구현 기준 테이블은 아래 3개가 핵심이다.
테이블
역할
curation_spec
개발자가 구성한 OpenAPI 기반 스펙. request/response schema와 hydration 정보를 저장
curation
큐레이션 공통 정보. 이름, 설명, 이미지, 노출 기간, 정렬 순서, 활성 여부 저장
curation_extension
spec에 맞춰 저장된 실제 payload JSON 저장
초기 구상에 있던 item_record, item, item_record_item 방식은 현재 채택안이 아니다.
erDiagram
curation_spec ||--o{ curation : "spec 선택"
curation ||--|| curation_extension : "payload 확장"
curation_spec ||--o{ curation_extension : "payload 검증 기준"
curation_spec {
bigint id PK
varchar code
varchar name
text description
json request_spec
json response_spec
varchar hydrator_key
int version
tinyint is_active
}
curation {
bigint id PK
bigint spec_id FK
varchar name
text description
varchar cover_image_url
varchar image_url_2
varchar image_url_3
date exposure_start_date
date exposure_end_date
int display_order
tinyint is_active
}
curation_extension {
bigint curation_id PK
bigint spec_id FK
json payload
}
Loading
저장 흐름
flowchart TD
A[어드민: 큐레이션 타입 선택] --> B[서버: curation_spec 조회]
B --> C[어드민: 타입에 맞는 payload 입력]
C --> D[서버: request_spec 기준 payload 검증]
D -->|실패| E[400 오류]
D -->|성공| F[curation 공통 정보 저장]
F --> G[curation_extension.payload 저장]
Loading
어드민 저장 요청은 크게 두 부분으로 나뉜다.
모든 큐레이션 공통 정보
specId
name
description
imageUrls 최대 3장
exposureStartDate, exposureEndDate
displayOrder
isActive
타입별 payload
request_spec에 맞는 자유 JSON
추천 위스키, 페어링, 시음회마다 구조가 다름
payload 저장 정책
payload의 아이템은 독립적인 저장 단위로 취급한다.
alcoholId가 있어도 해당 시점의 화면 표시용 메타 정보는 payload에 캡처해서 저장한다.
예를 들어 source: BOTTLE_NOTE인 아이템이 내부 알코올을 참조하더라도, 이름, 태그, 이미지, 설명 같은 표시용 값은 저장 시점 payload 값을 우선한다. 이후 원본 알코올 정보가 바뀌어도 저장된 큐레이션 화면 메타가 자동으로 바뀌지 않는다.
아이템 source 정책
source
의미
동작
BOTTLE_NOTE
내부 알코올 참조
alcoholId로 내부 알코올을 참조한다. 단, 이름/태그/이미지/설명 등 화면 메타는 저장 시점 payload를 사용하고, 별점/리뷰/픽 수 같은 확장 정보만 조회 시 보강한다.
MANUAL
직접 입력
내부 알코올 참조 없이 payload에 입력된 값을 그대로 사용한다. 별점/리뷰/픽 수 같은 내부 통계는 붙지 않으며 stats는 null이다.
조회 흐름
Product API는 FE가 DB나 GraphQL을 몰라도 렌더링할 수 있는 응답을 내려준다.
flowchart TD
A[FE: Product v2 큐레이션 조회] --> B[서버: 노출 가능한 curation 조회]
B --> C[서버: curation_extension.payload 조회]
C --> D{payload에 BOTTLE_NOTE alcoholId 존재?}
D -->|없음| E[저장 payload 그대로 응답]
D -->|있음| F[별점/리뷰/픽 수 등 stats 보강]
F --> G[저장 메타 + 최신 stats 응답]
Loading
중요한 기준은 다음과 같다.
저장된 메타 정보가 우선이다.
조회 시 보강되는 값은 좋아요, 별점, 리뷰 수, 픽 수 같은 확장 정보다.
MANUAL 아이템만 있는 경우 내부 통계 조회를 하지 않는다.
FE는 spec.code를 보고 타입별 컴포넌트를 선택하면 된다.
API surface
Admin API
API
역할
GET /admin/api/v2/curation-specs
사용 가능한 v2 큐레이션 스펙 목록 조회
GET /admin/api/v2/curation-specs/{specId}
스펙 상세 조회
GET /admin/api/v2/curations
v2 큐레이션 목록 조회
GET /admin/api/v2/curations/{curationId}
v2 큐레이션 상세 조회
POST /admin/api/v2/curations
v2 큐레이션 등록
PUT /admin/api/v2/curations/{curationId}
v2 큐레이션 수정
Product API
API
역할
GET /api/v2/curations
노출 가능한 v2 큐레이션 목록 조회
GET /api/v2/curations/{curationId}
v2 큐레이션 상세 조회
Product API는 활성 상태이고 노출 기간에 포함되는 큐레이션만 내려준다.
FE/기획 관점 요약
레거시 큐레이션은 "위스키 목록"이다.
v2 큐레이션은 "타입이 있는 콘텐츠 블록"이다.
현재 타입은 추천 위스키, 위스키 페어링, 위스키 시음회 3개다.
어드민은 타입을 고르고 그 타입에 맞는 내용을 입력한다.
저장 시점의 화면 메타는 캡처되어 유지된다.
내부 알코올을 참조한 경우에도 화면 메타는 저장값을 쓰고, 별점/리뷰/픽 수 같은 통계만 최신값으로 붙는다.
직접 입력 아이템은 내부 통계가 없으므로 stats: null로 내려온다.
FE는 spec.code별 렌더러만 준비하면 된다.
현재 구현/검증 상태
curation_spec, curation, curation_extension 스키마 추가
배경
기존 큐레이션은 "이름 + 설명 + 커버 이미지 + 위스키 N개 매핑" 형태의 레거시 키워드 큐레이션만 지원했다.
신규 요구사항에서는 큐레이션 타입에 따라 화면에 필요한 데이터 구조가 달라진다.
따라서 큐레이션 타입별로 서로 다른 payload를 저장하고, Product API에서는 FE가 타입별 화면을 그릴 수 있는 형태로 내려주는 구조가 필요하다.
현재 채택안
현 시점의 스펙은 3개로 고정한다.
RECOMMENDED_WHISKYWHISKY_PAIRINGWHISKY_TASTING_EVENT추후 스펙을 추가할 수는 있지만, 운영자가 런타임에서 자유롭게 만드는 방식이 아니다. 개발자가 OpenAPI 기반 스펙 리소스를 추가하고 서버 기동 시
curation_spec에 동기화한다.기존 큐레이션과 v2 큐레이션의 관계
기존 큐레이션은 제거하지 않는다.
즉, 기존
curation_keyword계열을 바로 대체하거나 삭제하지 않고, 신규 v2 surface를 병행한다.데이터 구조
현재 구현 기준 테이블은 아래 3개가 핵심이다.
curation_speccurationcuration_extension초기 구상에 있던
item_record,item,item_record_item방식은 현재 채택안이 아니다.erDiagram curation_spec ||--o{ curation : "spec 선택" curation ||--|| curation_extension : "payload 확장" curation_spec ||--o{ curation_extension : "payload 검증 기준" curation_spec { bigint id PK varchar code varchar name text description json request_spec json response_spec varchar hydrator_key int version tinyint is_active } curation { bigint id PK bigint spec_id FK varchar name text description varchar cover_image_url varchar image_url_2 varchar image_url_3 date exposure_start_date date exposure_end_date int display_order tinyint is_active } curation_extension { bigint curation_id PK bigint spec_id FK json payload }저장 흐름
flowchart TD A[어드민: 큐레이션 타입 선택] --> B[서버: curation_spec 조회] B --> C[어드민: 타입에 맞는 payload 입력] C --> D[서버: request_spec 기준 payload 검증] D -->|실패| E[400 오류] D -->|성공| F[curation 공통 정보 저장] F --> G[curation_extension.payload 저장]어드민 저장 요청은 크게 두 부분으로 나뉜다.
모든 큐레이션 공통 정보
specIdnamedescriptionimageUrls최대 3장exposureStartDate,exposureEndDatedisplayOrderisActive타입별 payload
request_spec에 맞는 자유 JSONpayload 저장 정책
payload의 아이템은 독립적인 저장 단위로 취급한다.
alcoholId가 있어도 해당 시점의 화면 표시용 메타 정보는 payload에 캡처해서 저장한다.예를 들어
source: BOTTLE_NOTE인 아이템이 내부 알코올을 참조하더라도, 이름, 태그, 이미지, 설명 같은 표시용 값은 저장 시점 payload 값을 우선한다. 이후 원본 알코올 정보가 바뀌어도 저장된 큐레이션 화면 메타가 자동으로 바뀌지 않는다.아이템 source 정책
BOTTLE_NOTEalcoholId로 내부 알코올을 참조한다. 단, 이름/태그/이미지/설명 등 화면 메타는 저장 시점 payload를 사용하고, 별점/리뷰/픽 수 같은 확장 정보만 조회 시 보강한다.MANUALstats는null이다.조회 흐름
Product API는 FE가 DB나 GraphQL을 몰라도 렌더링할 수 있는 응답을 내려준다.
flowchart TD A[FE: Product v2 큐레이션 조회] --> B[서버: 노출 가능한 curation 조회] B --> C[서버: curation_extension.payload 조회] C --> D{payload에 BOTTLE_NOTE alcoholId 존재?} D -->|없음| E[저장 payload 그대로 응답] D -->|있음| F[별점/리뷰/픽 수 등 stats 보강] F --> G[저장 메타 + 최신 stats 응답]중요한 기준은 다음과 같다.
MANUAL아이템만 있는 경우 내부 통계 조회를 하지 않는다.spec.code를 보고 타입별 컴포넌트를 선택하면 된다.API surface
Admin API
GET /admin/api/v2/curation-specsGET /admin/api/v2/curation-specs/{specId}GET /admin/api/v2/curationsGET /admin/api/v2/curations/{curationId}POST /admin/api/v2/curationsPUT /admin/api/v2/curations/{curationId}Product API
GET /api/v2/curationsGET /api/v2/curations/{curationId}Product API는 활성 상태이고 노출 기간에 포함되는 큐레이션만 내려준다.
FE/기획 관점 요약
stats: null로 내려온다.spec.code별 렌더러만 준비하면 된다.현재 구현/검증 상태
curation_spec,curation,curation_extension스키마 추가관련 PR