본문으로 건너뛰기

패스 스키마 구조

패스 도메인의 스냅샷 구조와 현재 스키마 문서

문서 정보


개요

스냅샷 패턴 핵심 원리

변경 가능한(mutable) 필드를 별도 테이블로 분리

원리설명
INSERT only수정 = UPDATE가 아닌 새 스냅샷 INSERT
1:N 관계원본(Pass) : 스냅샷(PassSnapshot) = 1 : N
최신 = 현재가장 최신 스냅샷 = 현재 유저에게 보이는 데이터
실존 데이터스냅샷은 '과거 기록'이 아닌 '실존하는 데이터'

도입 목적

  • 정책 변경 시 기존 UserSchedule에 영향 없이 유지 (적용 시점 정책 보존)
  • 가격 변경 이력 추적 및 분석
  • 구독 시점별 혜택 차별화
  • 감사(Audit) 및 분쟁 대응

현재 스키마

전체 구조

참조 관계

// 건별 구매
UserSchedule.passSnapshotId → PassSnapshot.id

// 구독 (멤버십)
Subscription.sourcePlanId → PassMembership.id // 기능 조회용 (최신 스냅샷)
Subscription.sourceSnapshotId → PassMembershipSnapshot.id // 가격 기록용
Subscription.subscribedPriceInKrw // 구독 시점 가격 고정

// 플랜 변경 관련
Subscription.previousSubscriptionInfo // 이전 플랜 복원용 JSON
Subscription.nextSubscriptionInfo // 예약 플랜 변경 JSON
Subscription.upgradedAt // 즉시 업그레이드 시점 (쿨링오프 기준)
Subscription.refundable // 플랜 변경 환불 가능 여부

featurePolicy JSON 구조

PassSnapshot의 featurePolicy는 패스 종류별 기능 정책을 JSON으로 관리합니다. 모든 value는 객체 타입으로 통일됩니다.

{
"AD": { "enabled": boolean },
"SCHEDULE_ALBUM": { "enabled": boolean },
"CAMERA_TRANSITION_SPEED": { "speed": "NORMAL" | "FAST" },
"CAMERA_ACCESS": { "type": "LIMITED" | "UNLIMITED" },
"STREAMING": { "quality": string, "fps": number },
"DOWNLOAD": { "quality": string, "fps": number },
"WORKOUT_CONDITION": { "enabled": boolean }
}

Note: KOK_VIEW는 v1.2.0에서 benefitPolicy.kok으로 이동되었습니다. 콕 저장/시청은 멤버십 전용 기능입니다.


benefitPolicy JSON 구조

PassMembershipSnapshot의 benefitPolicy는 멤버십 전용 혜택을 JSON으로 관리합니다.

{
"kok": { "limit": number, "periodType": "SCHEDULE" },
"myKok": { "maxCount": number, "periodType": "MEMBERSHIP" }
}

스냅샷 생성 시점

PassSnapshot

  • 관리자가 패스 정책을 변경할 때마다 새 스냅샷 INSERT
  • PASS-POLICY.yml 수정 → /sync-pass-policyprisma seeding

PassMembershipSnapshot

  • 관리자가 구독 가격 또는 혜택을 변경할 때마다 새 스냅샷 INSERT
  • 예: 월 7,800원 → 9,900원 변경 시 새 PassMembershipSnapshot 생성

최신 스냅샷 조회

-- 패스의 현재(최신) 정책 조회
SELECT * FROM pass_snapshots
WHERE pass_id = :passId
ORDER BY created_at DESC
LIMIT 1;

-- 멤버십의 현재(최신) 가격/혜택 조회
SELECT * FROM pass_membership_snapshots
WHERE membership_id = :membershipId
ORDER BY created_at DESC
LIMIT 1;

policyVersion과 Upcasters

policyVersion 필드

model PassSnapshot {
policyVersion Int @default(1) // 스키마 버전
featurePolicy Json // 정책 데이터
}
  • policyVersion = 1: 초기 스키마
  • policyVersion = 2: WORKOUT_CONDITION 추가
  • policyVersion = 3: 현재 버전

Upcasters 패턴

기존 스냅샷은 DB에서 수정하지 않고, 조회 시점에 최신 스키마로 변환합니다.

// feature-policy.vo.ts
private static upcastToLatest(data: unknown, version: number): unknown {
let current = data;

if (version < 2) {
current = { ...current, WORKOUT_CONDITION: { enabled: false } };
}

return current;
}

초기 데이터

패스 종류

codename비고
FREE무료 시청권기본 적용
PLUS플러스 시청권유료
PRO프로 시청권유료 (상위 티어)

시드 가격 (현재 SSOT 기준)

패스건별 가격 (Grains)구독 가격 — 웹 (KRW)구독 가격 — Apple IAP (KRW)구독 가격 — Google IAP (KRW)
FREE0---
PLUS3,0007,900 (MONTHLY)9,500 (MONTHLY)9,500 (MONTHLY)
PRO3,0009,900 (MONTHLY)11,900 (MONTHLY)11,900 (MONTHLY)

SSOT: src/modules/pass/constants/pass-policy.constants.ts (PASS_PRICING, MEMBERSHIP_POLICIES). IAP 가격 컬럼(iapApplePriceInKrw, iapGooglePriceInKrw)은 v6 스키마에서 신설됐으며, 스토어 수수료 ~15% 반영하여 웹 가격 대비 약 20% 마크업.


변경 이력

버전날짜변경 내용
v4.1.02026-04-29PassMembershipSnapshot IAP 가격 컬럼 신설
- iapApplePriceInKrw, iapGooglePriceInKrw 컬럼 추가 (nullable)
- 시드 가격 표를 채널별(웹/Apple/Google)로 확장
- PRO 시드 가격 정정 (3,000 grain), 구독 가격 stale 7,800→7,900 정정
v4.0.02026-03-04구독 API 통합 반영
- PRO 패스 추가 (FREE/PLUS/PRO 3-tier)
- Subscription 필드 추가: previousSubscriptionInfo, nextSubscriptionInfo, upgradedAt, refundable
- 참조 관계에 플랜 변경 관련 필드 설명 추가
v3.0.02026-01-20문서 전면 재작성
- AS-IS/TO-BE 구조 제거 (이미 구현 완료)
- 현재 스키마만 문서화
- Subscription 스키마 변경 반영 (sourceSnapshotId, subscribedPriceInKrw)
- KOK_VIEW → benefitPolicy.kok 이동 반영
- policyVersion/Upcasters 패턴 추가
v2.0.02025-12-29Pass → Pass 마이그레이션
v1.6.02025-12-10PassFeaturePolicy 테이블 추가 문서화 (이후 제거됨)
v1.0.02025-12-01초기 문서 작성