Skip to main content

Documentation Index

Fetch the complete documentation index at: https://api-docs.vpms.io/llms.txt

Use this file to discover all available pages before exploring further.

ADR-0003: Rate Domain 마이그레이션 런북

  • Status: Accepted
  • Date: 2026-05-15
  • Branch: refactor/rate-domain-redesign
  • Related: ADR-0001, ADR-0002
본 문서는 본 브랜치 머지 후 운영 환경에서 수행해야 할 절차를 정리한다.

1. Phase 별 산출물 요약

Phase산출물
Phase 1Prisma 스키마 재설계. legacy ari-* 4개 도메인 제거. 신규 rate-plan / inclusion 도메인 + AuditLog는 audit-svc로 위임.
Phase 2pricing-rules.service / rate-resolver.service / rate-audit.publisher / rematerialization.service.
Phase 3afolio / reservation / bill / block 의 legacy ari 참조 정리 (rename or stub).
Phase 3bGraphQL rate-plan.gql + resolver. Registry 등록.
Phase 4Redis hot cache + horizon-extension.scheduler + rate-archive.scheduler + restriction.service.
Testspricing-rules.service.test + restriction.service.test.
Docsdocs/api-reference/core-svc/rate-plan.mdx + 본 런북.

2. 머지 전 확인

  1. pnpm prisma generate 재실행 — generated client 가 새 모델/필드 반영하는지 확인.
  2. pnpm test — node:test 가 신규 unit test 를 인식하는지 확인.
  3. pnpm nx run core-svc:build — TS 컴파일 통과 확인.
  4. pnpm nx run core-svc:lint — biome 위반 없는지.

3. DB 마이그레이션

운영 데이터가 없는 시점이므로 prisma migrate reset 후 신규 스키마로 마이그레이션 적용.
cd apps/core-svc
pnpm prisma migrate reset --force
pnpm prisma migrate dev --name rate-domain-redesign
마이그레이션 SQL 에는 다음이 포함되어야 한다:
  • legacy 테이블 DROP: ariRates, ariRateAdjustments, ariRateAdjustmentMappings, ariRatePlans, ariRateHistories, ariRatePlanHistories, ariRatePlanChannelAdjustments, ariRatePlanInclusions, ariInclusions, ariInclusionHistories, ariPeriods, ariPeriodRanges, ariPackages, ariPackageHistories, ariFolioRateSnapshot, ariFolioInclusionSnapshot.
  • 신규 테이블 CREATE: ratePlans, dailyRates, ratePlanRestrictions, ratePlanOccupancyPrices, ratePlanRoomTypes, ratePlanChannelMappings, ratePlanInclusions, ratePlanHistories, rateSeasons, rematerializationProposals, folioRateSnapshots, folioInclusionSnapshots, inclusions, inclusionHistories.
  • 변경: prices.ariPackageIdprices.ratePlanId.

4. BullMQ cron 등록

worker bootstrap 시 다음 작업을 등록한다.
import { horizonExtensionScheduler } from '@/domains/rate-plan/schedulers/horizon-extension.scheduler.ts';
import { rateArchiveScheduler } from '@/domains/rate-plan/schedulers/rate-archive.scheduler.ts';

// Daily 00:00 — 24개월 horizon 유지
queue.add('horizon-extension', {}, { repeat: { pattern: '0 0 * * *' } });

// Monthly 1st 03:00 — archive + proposal 만료 정리
queue.add('rate-archive', {}, { repeat: { pattern: '0 3 1 * *' } });
processor:
new Worker('horizon-extension', async () => horizonExtensionScheduler.run());
new Worker('rate-archive', async () => rateArchiveScheduler.run());

5. Phase 5 booking integration 진행 상태

Phase 3a 에서 NotImplemented stub 으로 비워둔 booking 흐름을 신규 RatePlan / DailyRate 도메인 기반으로 재구현하는 작업. 본 브랜치에서 다음까지 완료되었다:

✅ 완료

  • snapshot-creation.service: prepareSnapshotData / commitPreparedSnapshots / createSnapshotsRateResolverService.resolve 로 가격을 해석하고 FolioRateSnapshot / FolioInclusionSnapshot 을 생성한다. ratePlanSnapshot / inclusionSnapshot JSON 으로 immutability 를 보장.
  • folio-amount-calculator.service: 신규 snapshot 의 rateAmount/inclusionAmount/totalAmount 를 그대로 활용. history join 불필요.
  • bill-creation.service: FolioRateSnapshot 단위로 Bill 발행. BillBriefKey.CHARGE_RESERVATION_DAILY / CHARGE_INCLUSION 적용.
  • FolioRatePlanInput / RatePlanInput / RatePlanUpdateInput 입력 shape 을 신규 ratePlanId(ID) + roomTypeId(ID) + occupancy + amountOverride 로 마이그레이션.
  • reservation-modify.helpers.deriveRatePlanInclusionInputs: RatePlanInclusion 직접 조회로 default inclusion 목록 구성.
  • reservation.service.createReservationForFolio: 예약 확정 전 RestrictionService.assertBookable 호출로 CTA/CTD/StopSell/LOS 위반 차단.
  • block.service: FolioRatePlanInput 호출부 신규 shape 으로 마이그레이션.

✅ schema 마이그레이션 (추가)

  • ReservationNight.ratePlanId: IntBytes(ULID). rateId: IntBigInt? (DailyRate.id 참조, sparse 허용).
  • BlockComposition.ratePlanId/rateId: 동일 패턴으로 Bytes? / BigInt?.
  • reservation-night.helpers.createReservationNights 시그니처를 string / bigint? 로 갱신, 호출 흐름 복원.
  • ReservationNight.ratePlan/rateBlockComposition.ratePlan/rate resolver 가 신규 RatePlan / DailyRate row 를 직접 findUnique 한다.
  • GraphQL ReservationNight.ratePlanId/rateId / BlockComposition.ratePlanId/rateId 타입을 ID!/String 으로 정정 (BigInt 직렬화).

✅ snapshot JSON shape 정리 (추가)

  • BlockCompositionRateSnapshot shape: ratePlanIdstring(ULID), rateIdstring|null(BigInt 직렬화). currencySymbol 폐기. ratePlanVersion / rateIdVersion 은 deprecated 표시 후 legacy archive 호환용으로만 유지 (신규 row 는 안 채우는 것이 권장).
  • block.gql 의 BlockCompositionRateSnapshot type / BlockCompositionRateSnapshotInput 도 동일하게 갱신.

✅ BigInt id 직렬화 (추가)

DailyRate 와 RatePlanRestriction 의 PK 가 BigInt 인데 GraphQL ID!string 이라 resolver 경계에서 직렬화 필요. serializeBigIntId / serializeBigIntIds 헬퍼를 rate-plan.resolver 에 추가하고 setDailyRate / getRatePlanRestrictions / setRatePlanRestriction / block.resolver.rate / reservation.resolver.rate / folio.resolver.appliedRate 에 적용. 모든 BigInt 반환 경로가 string 으로 일관 직렬화.

⏭ Out of scope (별도 PR)

  • Bill domain 의 Payment.currencySymbol 컬럼 — ADR 원래 범위 외. 통화 코드만 저장하는 표준에 맞춰 별도 마이그레이션 PR 권장.
  • 신규 appliedPackage resolver 가 현재 RatePlan 을 반환 — package 개념이 inclusion bundle 로 흡수된 만큼 GraphQL surface 에서 제거할지 별도 논의.
  • E2E 통합 테스트 (예약 → folio snapshot → bill 발행 happy path).

6. PostgreSQL partitioning

ADR-0001 §2.6 정책에 따라 daily_rates 테이블을 stay_date 월별로 파티셔닝한다. 초기 row 수가 적은 동안은 단일 테이블로 운영 가능. 다음 조건 중 하나 충족 시 도입:
  • row 수 1천만 초과
  • 시설 1천 개 초과
  • 캘린더 쿼리 P95 > 200ms
도입 시 pg_partman 자동 파티션 생성 + monthly cron 으로 13개월 cutoff partition 을 archive 테이블로 DETACH → COPY → DROP 한다.

7. 롤백 정책

운영 데이터 마이그레이션이 시작된 후에는 롤백이 어렵다. 본 변경은 운영 데이터가 없는 시점에 적용되므로 다음 안전망만 유지:
  • 머지 전 main 브랜치 백업 tag 생성 (pre-rate-redesign).
  • 머지 후 24시간 핫스탠바이 운영자 배치.
  • 외부 통합(채널 매니저 등)은 별도 ramp-up 일정으로 분리.