테마
Story: 가드레일 체크 서비스 구현
메타
| 항목 | 값 |
|---|---|
| Story ID | E-05-S-02 |
| Epic | E-05 요금제 정책 재설계 및 가드레일 |
| 상태 | draft |
| 우선순위 | P0 |
| 규모 | L |
| 담당 | 창훈님 (하록님, 수민님) |
사용자 스토리
As a 장사왕 시스템, I want 요금제별 Feature 접근을 통합 관리하는 서비스, So that 모든 가드레일을 일관되게 적용할 수 있다.
수락 기준 (Acceptance Criteria)
AC-01: 요금제 조회 서비스
| 항목 | 내용 |
|---|---|
| Given | 사업장 ID |
| When | 요금제 조회 요청 |
| Then | 현재 요금제 정보 + Feature 제한 반환 |
API 응답:
typescript
interface PlanInfo {
tierId: number;
tierCode: string; // 'starter', 'basic', 'pro', 'business'
tierName: string;
isGrandfather: boolean; // Grandfather 정책 적용 여부
limits: {
maxVendors: number;
maxMonthlyOrders: number | null;
dataRetentionDays: number;
};
features: {
[featureCode: string]: {
enabled: boolean;
limit: number | null; // count 타입일 경우
};
};
}AC-02: Feature 접근 체크
| 항목 | 내용 |
|---|---|
| Given | 사업장 ID + Feature 코드 |
| When | 접근 체크 요청 |
| Then | 접근 가능 여부 + 잔여 한도 반환 |
체크 함수:
typescript
interface FeatureCheckResult {
allowed: boolean;
reason?: 'disabled' | 'limit_exceeded';
currentUsage?: number;
limit?: number;
upgradeRequired?: string; // 필요한 최소 티어
}
checkFeatureAccess(bizId: string, featureCode: string): Promise<FeatureCheckResult>AC-03: 사용량 카운트
| 항목 | 내용 |
|---|---|
| Given | 사업장 ID + count 타입 Feature |
| When | 사용량 조회 |
| Then | 현재 사용량 반환 |
카운트 대상:
| featureCode | 카운트 방식 |
|---|---|
| deep_layer_campaigns | 광고 연동된 캠페인 수 |
| keywords | 키워드 분석 요청 수 (당월) |
AC-04: 캐싱
| 항목 | 내용 |
|---|---|
| Given | 요금제 조회 요청 |
| When | 캐시 존재 |
| Then | 캐시 데이터 반환 (TTL 5분) |
캐시 키: plan:${bizId}
태스크 분해
Task 1: PlanGuardService 생성 AC-01
- [ ] 1.1: PlanGuardService 클래스 생성
- [ ] 1.2: getPlanInfo(bizId) 메서드
- [ ] 1.3: BizPlanInfo → PlanTier 매핑 로직
- [ ] 1.4: Grandfather 판정 로직
Task 2: Feature 체크 AC-02
- [ ] 2.1: checkFeatureAccess(bizId, featureCode) 메서드
- [ ] 2.2: boolean 타입 Feature 체크
- [ ] 2.3: count 타입 Feature 체크
- [ ] 2.4: 업그레이드 필요 티어 계산
Task 3: 사용량 카운트 AC-03
- [ ] 3.1: getFeatureUsage(bizId, featureCode) 메서드
- [ ] 3.2: deep_layer_campaigns 카운트 (CoupangAdCampaignDaily)
- [ ] 3.3: keywords 카운트 (별도 테이블 또는 로그)
Task 4: 캐싱 AC-04
- [ ] 4.1: Redis 또는 메모리 캐시 설정
- [ ] 4.2: 캐시 키 전략
- [ ] 4.3: 캐시 무효화 (요금제 변경 시)
Task 5: 테스트
- [ ] 5.1: 단위 테스트 (각 티어별)
- [ ] 5.2: Grandfather 케이스 테스트
Dev Notes (AI Agent 최적화)
영향 받는 소스 트리
src/
├── service/
│ └── plan-guard.service.ts # 🆕 핵심 서비스
├── dto/
│ ├── plan-info.dto.ts # 🆕
│ └── feature-check.dto.ts # 🆕
└── decorator/
└── require-feature.decorator.ts # 🆕 선택적핵심 로직
typescript
// Grandfather 판정
isGrandfather(bizPlanInfo: BizPlanInfo): boolean {
// PortOne PG 사용 중인 기존 고객
return bizPlanInfo.paymentMethod === 'PORTONE';
}
// 티어 매핑
mapToTier(bizPlanInfo: BizPlanInfo): PlanTier {
if (this.isGrandfather(bizPlanInfo)) {
// 기존 planType → 새 tier 매핑
const mapping = {
'basic1_*': 'starter',
'pro3_*': 'basic',
'pro10_*': 'pro',
'pro20_*': 'business',
};
// ...
}
return bizPlanInfo.planTier;
}충돌 감지
| 항목 | 상태 | 설명 |
|---|---|---|
| 기존 코드 충돌 | 🟡 있음 | 기존 요금제 체크 로직 대체 필요 |
의존성
| 의존 | 설명 | 상태 |
|---|---|---|
| E-05-S-01 | PlanTier/PlanFeature 테이블 | 선행 필수 |
이 Story가 블로킹하는 Story:
- E-05-S-03 ~ S-07 (모든 FE/BE Feature 분기)
생성일: 2026-01-27
