아키텍처 개요
Copy
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 앱 │────▶│ 백엔드 │────▶│ 사주 │
│ (프론트엔드) │ │ (프록시) │ │ API │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
│ ▼
│ ┌─────────────────┐
│ │ 캐시 │
│ │ (Redis) │
│ └─────────────────┘
│
▼
┌─────────────────┐
│ 로컬 사주 │ ← 클라이언트 계산 (API 호출 없음)
│ 계산기 │
└─────────────────┘
1단계: 백엔드 프록시
API 키를 클라이언트에 절대 노출하지 마세요. 백엔드 프록시를 생성하세요:Copy
// app/api/fortune/route.ts
import { SajuClient } from '@sajuapi/sdk';
const client = new SajuClient({
apiKey: process.env.SAJU_API_KEY!
});
export async function POST(request: Request) {
// 사용자 세션 검증
const session = await getSession(request);
if (!session) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
// 사용자별 요청 제한
const rateLimited = await checkRateLimit(session.userId);
if (rateLimited) {
return Response.json({ error: 'Too many requests' }, { status: 429 });
}
const { saju, userName } = await request.json();
const fortune = await client.getDailyFortune({ saju, userName });
return Response.json(fortune);
}
2단계: 클라이언트 사주 계산
API 호출을 줄이기 위해 사주를 로컬에서 계산하세요:Copy
// 클라이언트 계산기 사용 (API 호출 없음)
import { SajuCalculator } from '@sajuapi/sdk';
function useSaju(birthData: BirthData) {
const [saju, setSaju] = useState<Saju | null>(null);
useEffect(() => {
// 클라이언트에서 완전히 실행
const calculated = SajuCalculator.calculate({
year: birthData.year,
month: birthData.month,
day: birthData.day,
hour: birthData.hour,
gender: birthData.gender
});
setSaju(calculated);
}, [birthData]);
return saju;
}
3단계: 캐싱 구현
비용 절감을 위해 운세를 캐싱하세요:Copy
// Redis를 사용한 백엔드 캐싱
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
const CACHE_TTL = 86400; // 24시간
async function getCachedFortune(saju: Saju, userName: string) {
// 일간(日干) + 날짜 기반 캐시 키
const today = new Date().toISOString().split('T')[0];
const cacheKey = `fortune:${saju.dayMaster.stem}:${today}`;
// 캐시 확인
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// API에서 조회
const fortune = await client.getDailyFortune({ saju, userName });
// 결과 캐싱
await redis.setex(cacheKey, CACHE_TTL, JSON.stringify(fortune));
return fortune;
}
4단계: 사용자 요청 제한
사용자별 요청 제한을 구현하세요:Copy
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '1 h'), // 시간당 10회 요청
analytics: true
});
async function checkRateLimit(userId: string): Promise<boolean> {
const { success, limit, remaining, reset } = await ratelimit.limit(userId);
if (!success) {
console.log(`사용자 ${userId} 요청 제한됨. ${reset}에 초기화`);
return true;
}
return false;
}
5단계: 에러 처리
우아한 폴백을 구현하세요:Copy
async function getFortuneWithFallback(saju: Saju, userName: string) {
try {
return await client.getDailyFortune({ saju, userName });
} catch (error) {
if (error instanceof RateLimitError) {
// 캐시된 데이터가 있으면 반환
const cached = await getCachedFortune(saju);
if (cached) return { ...cached, _fromCache: true };
}
// 로컬에서 폴백 운세 생성
return generateFallbackFortune(saju, userName);
}
}
function generateFallbackFortune(saju: Saju, userName: string): FortuneData {
// 일간별 사전 정의된 운세
const fallbacks = {
'병화': { score: 72, analysis: '오늘은 안정적인 하루입니다...' },
'정화': { score: 68, analysis: '신중한 판단이 필요한 날...' },
// ... 다른 일간들
};
return fallbacks[saju.dayMaster.name] || fallbacks['병화'];
}
6단계: 분석 및 모니터링
사용량과 에러를 추적하세요:Copy
async function trackFortuneRequest(userId: string, result: FortuneResult) {
await analytics.track({
event: 'fortune_requested',
userId,
properties: {
model: result.meta.model,
cached: result.meta.cached,
cost: result.meta.cost,
latency: result.meta.latency,
dayMaster: result.data.character.dayMaster
}
});
}
// 에러 추적
client.on('error', (error) => {
Sentry.captureException(error, {
tags: { api: 'sajuapi' },
extra: { requestId: error.requestId }
});
});
프로덕션 체크리스트
1
API 키 보안
- API 키를 환경 변수에 저장
- 클라이언트 코드에 키 노출 금지
- git에 키 커밋 금지
2
백엔드 프록시
- 모든 API 호출을 백엔드를 통해 처리
- 사용자 인증 필수
- 요청 검증 구현
3
캐싱
- Redis 또는 유사 캐시 구성
- 캐시 키에 날짜 포함 (일별 교체)
- 폴백 운세 준비
4
요청 제한
- 사용자별 요청 제한 구성
- 안전망으로 전역 요청 제한 설정
- 제한 도달 시 우아한 폴백
5
모니터링
- 에러 추적 (Sentry 등)
- 사용량 분석
- 비용 모니터링 알림
6
테스팅
- 사주 계산 유닛 테스트
- 테스트 API 키로 통합 테스트
- 예상 트래픽 부하 테스트