Skip to content

[아키텍처] 큐레이션 구조 개편 - spec 기반 v2 큐레이션 #240

Description

@Whale0928

배경

기존 큐레이션은 "이름 + 설명 + 커버 이미지 + 위스키 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

어드민 저장 요청은 크게 두 부분으로 나뉜다.

  1. 모든 큐레이션 공통 정보

    • specId
    • name
    • description
    • imageUrls 최대 3장
    • exposureStartDate, exposureEndDate
    • displayOrder
    • isActive
  2. 타입별 payload

    • request_spec에 맞는 자유 JSON
    • 추천 위스키, 페어링, 시음회마다 구조가 다름

payload 저장 정책

payload의 아이템은 독립적인 저장 단위로 취급한다.

alcoholId가 있어도 해당 시점의 화면 표시용 메타 정보는 payload에 캡처해서 저장한다.

예를 들어 source: BOTTLE_NOTE인 아이템이 내부 알코올을 참조하더라도, 이름, 태그, 이미지, 설명 같은 표시용 값은 저장 시점 payload 값을 우선한다. 이후 원본 알코올 정보가 바뀌어도 저장된 큐레이션 화면 메타가 자동으로 바뀌지 않는다.

아이템 source 정책

source 의미 동작
BOTTLE_NOTE 내부 알코올 참조 alcoholId로 내부 알코올을 참조한다. 단, 이름/태그/이미지/설명 등 화면 메타는 저장 시점 payload를 사용하고, 별점/리뷰/픽 수 같은 확장 정보만 조회 시 보강한다.
MANUAL 직접 입력 내부 알코올 참조 없이 payload에 입력된 값을 그대로 사용한다. 별점/리뷰/픽 수 같은 내부 통계는 붙지 않으며 statsnull이다.

조회 흐름

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 스키마 추가
  • OpenAPI curation spec 리소스 3종 추가
  • admin-api 기동 시 spec resource sync 추가
  • Admin v2 curation/spec API 추가
  • Product v2 curation list/detail API 추가
  • request payload 검증 추가
  • response materialize 및 BOTTLE_NOTE stats 보강 추가
  • MANUAL-only payload에서 불필요한 stats 조회를 하지 않도록 보강
  • 저장 시점 메타가 조회 시점 원본 알코올 메타로 덮어써지지 않는 테스트 추가

관련 PR

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions