절기별 미션 추천 서비스 - 시스템 아키텍처
1. 시스템 개요
절기가 변경될 때마다 사용자에게 미션을 제공하고 알림을 발송하는 시스템
1.1 미션 유형
| 유형 | 설명 | 대상 |
|---|
| 오늘의 미션 | 큐레이터 픽, 관리자가 미리 지정 | 모든 유저 공통 |
| 개인화 미션 | 온보딩 데이터 기반 추천 | 유저별 맞춤 |
1.2 핵심 설계 원칙 (MVP)
- 오늘의 미션: 관리자가 절기별 15일치 사전 지정 (수동 큐레이션)
- 개인화 미션: 태그 기반 매칭 (45개 미션 풀)
- LLM: 미션 생성 보조 (LLM이 리스트 생성 → 관리자가 선별)
- 날씨 API: MVP 제외 → 2차 고도화
2. 전체 아키텍처
2.1 MVP 아키텍처
┌─────────────────────────────────────────────────────────────────────────────┐
│ │
│ ┌─────────────────────┐ │
│ │ Client App │ │
│ │ (iOS / Android) │ │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Load Balancer │ │
│ │ (Nginx) │ │
│ └──────────┬──────────┘ │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ API Server │ │ API Server │ │ Admin Server │ │
│ │ (Spring) │ │ (Spring) │ │ (Spring) │ │
│ └───────┬──────┘ └───────┬──────┘ └───────┬──────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ │ │
│ ┌───────────────────────┼───────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │ MySQL │ │ Redis │ │ LLM API │ │
│ │ (Main DB) │ │ (Cache) │ │ (Claude/GPT) │ │
│ └─────────────┘ └─────────────┘ └──────────────┘ │
│ ↑ │
│ │ │
│ Admin Server에서 호출 │
│ (미션 생성 보조용) │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Scheduler (Batch) │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ 오늘의미션 │ │ 절기 체크 │ │ 알림 발송 │ │ │
│ │ │ 활성화 Job │ │ Job │ │ Job │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ FCM / APNs │ │
│ │ (Push 알림) │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
2.2 2차 고도화 아키텍처 (추가 요소)
┌─────────────────────────────────────────────────────────────────┐
│ 2차 고도화 시 추가 │
│ │
│ ┌─────────────┐ │
│ │ Weather API │ │
│ │ (날씨 조회) │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ Scheduler 추가 Job │ │
│ │ - WeatherCheckJob (06:00) │ │
│ │ - 날씨 기반 미션 자동 교체 │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
3. 핵심 프로세스
3.1 Phase 1: 미션 사전 준비 (관리자 작업)
LLM으로 미션 리스트 생성 → 관리자가 선별/검수 → DB 저장
┌─────────────────────────────────────────────────────────────────┐
│ Admin Server Flow │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. LLM으로 미션 리스트 생성 │ │
│ │ │ │
│ │ 관리자가 절기 + 조건 입력 │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ "청명 절기에 맞는 미션 50개 생성해줘" │ │ │
│ │ │ "조건: 날씨에 덜 민감하고, 감성적인 미션" │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ LLM API 호출 │ │ │
│ │ │ (Claude / GPT 등) │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ 미션 후보 리스트 50개 반환 │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 2. 관리자 선별/검수 │ │
│ │ │ │
│ │ LLM이 생성한 50개 중 45개 선별 │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ [✓] 맑은 하늘 아래 심호흡하기 │ │ │
│ │ │ [✓] 봄 향기 느끼기 │ │ │
│ │ │ [✗] 벚꽃 축제 가기 ← 날씨 민감, 제외 │ │ │
│ │ │ [✓] 제철 음식 먹어보기 │ │ │
│ │ │ ... │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ - 부적절한 미션 삭제 │ │
│ │ - 미션 문구 수정 │ │
│ │ - 태그 부여 (장소/활동/강도) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 3. 오늘의 미션 스케줄 지정 │ │
│ │ │ │
│ │ 선별된 45개 중 15개를 오늘의 미션으로 지정 │ │
│ │ ┌──────┬─────────────────────────┐ │ │
│ │ │ 날짜 │ 오늘의 미션 │ │ │
│ │ ├──────┼─────────────────────────┤ │ │
│ │ │ 4/4 │ 맑은 하늘 아래 심호흡 │ │ │
│ │ │ 4/5 │ 봄 향기 느끼기 │ │ │
│ │ │ ... │ ... │ │ │
│ │ └──────┴─────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 4. DB 저장 │ │
│ │ │ │
│ │ - Mission 테이블: 미션 풀 (45개) │ │
│ │ - MissionTag 테이블: 태그 정보 │ │
│ │ - DailyMissionSchedule 테이블: 오늘의 미션 스케줄 │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
미션 선별 가이드라인:
| 원칙 | 설명 | 예시 |
|---|
| 날씨 무관 | 실내/실외 모두 가능 | "봄 향기 느끼기" (창문 열어도 OK) |
| 감성 중심 | 행위보다 감각/경험 | "초록색 찾아 사진 찍기" |
| 재사용 가능 | 매년 사용 가능한 형태 | "제철 음식 먹어보기" |
3.2 Phase 2: 오늘의 미션 활성화 (매일 00:00)
┌─────────────────────────────────────────────────────────────────┐
│ Daily Mission Scheduler Flow │
│ │
│ ┌───────────────┐ │
│ │ 매일 00:00 │ │
│ │ DailyMission │ │
│ │ ActivateJob │ │
│ └───────┬───────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────┐ │
│ │ 1. DailyMissionSchedule에서 오늘 날짜 조회 │ │
│ │ 2. 해당 미션을 "오늘의 미션"으로 활성화 │ │
│ │ 3. Redis 캐시 갱신 │ │
│ └───────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
3.3 Phase 3: 절기 변경 감지 및 알림 발송
┌─────────────────────────────────────────────────────────────────┐
│ Solar Term Scheduler Flow │
│ │
│ ┌───────────────┐ │
│ │ 매일 00:00 │ │
│ │ SolarTermCheck│ │
│ │ Job │ │
│ └───────┬───────┘ │
│ │ │
│ ▼ │
│ ┌───────────────┐ NO │
│ │ 오늘 = 절기 │ ──────────→ 종료 │
│ │ 시작일? │ │
│ └───────┬───────┘ │
│ │ YES │
│ ▼ │
│ ┌───────────────────────────────────────────┐ │
│ │ 절기 변경 알림 발송 │ │
│ │ │ │
│ │ "청명이 시작됐어요! 새로운 미션을 확인하세요" │ │
│ └───────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
3.4 Phase 4: 개인화 미션 조회 (실시간)
┌─────────────────────────────────────────────────────────────────┐
│ Personalized Mission Recommendation │
│ │
│ ┌─────────┐ ┌─────────────────────────────────────┐ │
│ │ Client │ ──────→ │ API Server │ │
│ │ 요청 │ GET │ │ │
│ │ │/missions│ 1. 사용자 온보딩 태그 조회 │ │
│ └─────────┘ │ 2. 현재 절기 미션 풀 조회 │ │
│ │ 3. 태그 매칭 점수 계산 │ │
│ │ 4. 이미 완료/스킵한 미션 제외 │ │
│ │ 5. 상위 N개 반환 │ │
│ └─────────────────────────────────────┘ │
│ │
│ [태그 매칭 로직] │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ score = 0 │ │
│ │ if (user.location == mission.location) score += 2 │ │
│ │ if (user.activity == mission.activity) score += 3 │ │
│ │ if (user.intensity == mission.intensity) score += 1 │ │
│ │ return missions.sortByScore().limit(N) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
4. 미션 제공 구조
4.1 화면별 미션 구성
┌─────────────────────────────────────────────────────────────────┐
│ 메인 화면 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 오늘의 미션 (큐레이터 픽) │ │
│ │ │ │
│ │ "맑은 하늘 아래 심호흡하기" │ │
│ │ │ │
│ │ - 모든 유저 동일 │ │
│ │ - 관리자가 미리 지정한 미션 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 개인화 미션 (A/B 테스트) │ │
│ │ │ │
│ │ [A안] 리스트로 여러 개 │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ - 봄나물 비빔밥 만들기 │ │ │
│ │ │ - 창문 열고 환기하기 │ │ │
│ │ │ - 산책하며 새소리 듣기 │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ │ │ │
│ │ [B안] 1개 + 새로고침 │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ 봄나물 비빔밥 만들기 🔄 │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
4.2 미션 데이터 흐름
┌───────────────────────────────────────────────────────────────────────────┐
│ 미션 데이터 흐름 │
│ │
│ [사전 준비] [실시간] │
│ │
│ ┌──────────┐ │
│ │ LLM │ │
│ │ API │ │
│ └────┬─────┘ │
│ │ 미션 후보 50개 생성 │
│ ▼ │
│ ┌──────────┐ │
│ │ 관리자 │ │
│ │ 선별/검수│ │
│ └────┬─────┘ │
│ │ 45개 선정 + 태그 부여 │
│ ▼ │
│ ┌──────────┐ │
│ │ Mission │ │
│ │ 테이블 │ │
│ │ (풀) │ │
│ └────┬─────┘ │
│ │ │
│ ├────────────────┐ │
│ │ ▼ │
│ │ ┌───────────────┐ │
│ │ │ DailyMission │ │
│ │ │ Schedule │ │
│ │ │ (15일치 지정) │ │
│ │ └───────┬───────┘ │
│ │ │ │
│ │ │ 매일 00:00 │
│ │ ▼ │
│ │ ┌───────────────┐ ┌──────────┐ │
│ │ │ 오늘의 미션 │ ───→ │ 모든유저 │ │
│ │ │ 활성화 │ └──────────┘ │
│ │ └───────────────┘ │
│ │ │
│ │ 사용자 요청 시 │
│ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ 태그 매칭 │ ─→ │ 개인화 미션 │ ─→ │ 해당유저 │ │
│ │ (실시간) │ │ 추천 │ └──────────┘ │
│ └──────────────┘ └──────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────┘
5. 서버 구성
5.1 API Server
| 역할 | 설명 |
|---|
| 사용자 인증 | JWT 기반 인증/인가 |
| 오늘의 미션 조회 | 당일 활성화된 미션 반환 |
| 개인화 미션 조회 | 태그 매칭 기반 실시간 추천 |
| 미션 완료/스킵 | 상태 업데이트 |
| 알림 조회 | 인앱 알림 목록 |
5.2 Admin Server
| 역할 | 설명 |
|---|
| LLM 연동 | 미션 후보 리스트 생성 요청 |
| 미션 관리 | 미션 선별/검수, CRUD |
| 태그 관리 | 미션별 태그 설정 |
| 스케줄 관리 | 오늘의 미션 일정 지정 |
| 통계 조회 | 미션 완료율 등 |
5.3 Scheduler (Batch Server)
| Job | 실행 시점 | 역할 |
|---|
| DailyMissionActivateJob | 매일 00:00 | 오늘의 미션 활성화 |
| SolarTermCheckJob | 매일 00:00 | 절기 변경 여부 확인 |
| NotificationJob | 절기 변경 시 | 절기 시작 알림 발송 |
| MissionReminderJob | 매일 09:00 | 미완료 미션 리마인드 (선택) |
6. 데이터 모델
6.1 핵심 테이블
CREATE TABLE mission (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(200) NOT NULL,
description TEXT,
solar_term VARCHAR(20),
season VARCHAR(20),
status ENUM('DRAFT', 'ACTIVE') DEFAULT 'DRAFT',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE mission_tag (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
mission_id BIGINT NOT NULL,
tag_category VARCHAR(50),
tag_value VARCHAR(50),
FOREIGN KEY (mission_id) REFERENCES mission(id)
);
CREATE TABLE daily_mission_schedule (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
solar_term VARCHAR(20) NOT NULL,
target_date DATE NOT NULL,
mission_id BIGINT NOT NULL,
created_by BIGINT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (mission_id) REFERENCES mission(id),
UNIQUE KEY (target_date)
);
CREATE TABLE user_profile (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
location_pref VARCHAR(20),
activity_pref VARCHAR(50),
intensity_pref VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES user(id)
);
CREATE TABLE user_mission (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
mission_id BIGINT NOT NULL,
mission_type ENUM('DAILY', 'PERSONALIZED'),
status ENUM('RECOMMENDED', 'COMPLETED', 'SKIPPED'),
recommended_at TIMESTAMP,
completed_at TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES user(id),
FOREIGN KEY (mission_id) REFERENCES mission(id)
);
CREATE TABLE solar_term (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20) NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
season VARCHAR(20),
year INT NOT NULL
);
6.2 태그 체계
| 카테고리 | 값 | 설명 |
|---|
| location | indoor | 실내 |
| outdoor | 실외 |
| both | 무관 |
| activity | food | 음식/요리 |
| nature | 자연/산책 |
| culture | 문화/감상 |
| exercise | 운동 |
| rest | 휴식 |
| intensity | light | 가벼움 |
| moderate | 보통 |
| active | 활발 |
7. API 목록
7.1 Client API
| Method | Endpoint | 설명 |
|---|
| GET | /api/missions/today | 오늘의 미션 조회 |
| GET | /api/missions/personalized | 개인화 미션 목록 |
| GET | /api/missions/personalized/refresh | 개인화 미션 새로고침 (B안) |
| POST | /api/missions/{id}/complete | 미션 완료 |
| POST | /api/missions/{id}/skip | 미션 스킵 |
| GET | /api/solar-terms/current | 현재 절기 정보 |
| GET | /api/notifications | 알림 목록 |
7.2 Admin API
| Method | Endpoint | 설명 |
|---|
| POST | /admin/missions/generate | LLM으로 미션 후보 생성 |
| GET | /admin/missions/candidates | 생성된 미션 후보 목록 |
| POST | /admin/missions/approve | 미션 선별/승인 |
| PUT | /admin/missions/{id} | 미션 수정 |
| DELETE | /admin/missions/{id} | 미션 삭제 |
| POST | /admin/daily-schedule | 오늘의 미션 스케줄 지정 |
8. 타임라인
┌───────────────────────────────────────────────────────────────────────────┐
│ 운영 타임라인 │
│ │
│ [절기 시작 전 - 관리자 작업] │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 1. Admin에서 LLM 호출 → 미션 후보 50개 생성 │ │
│ │ 2. 관리자가 선별/검수 → 45개 확정 │ │
│ │ 3. 태그 부여 (장소/활동/강도) │ │
│ │ 4. 오늘의 미션 15일치 스케줄 지정 │ │
│ │ │ │
│ │ ※ 1년치 한번에 작업 가능 (24절기 × 45개 = 1,080개) │ │
│ │ ※ 이후 매년 재사용 (필요시 일부 수정) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ [매일 00:00] │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 1. 오늘의 미션 활성화 (DailyMissionActivateJob) │ │
│ │ 2. 절기 변경 체크 (SolarTermCheckJob) │ │
│ │ → 변경 시 푸시 알림 발송 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ [사용자 앱 실행 시] │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 1. 오늘의 미션 조회 (캐시) │ │
│ │ 2. 개인화 미션 조회 (실시간 태그 매칭) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────┘
9. 기술 스택
| 영역 | 기술 | 용도 |
|---|
| API Server | Spring Boot 3.x | REST API |
| Admin Server | Spring Boot 3.x | 관리자 기능 |
| Scheduler | Spring Scheduler | 배치 처리 |
| Database | MySQL 8.0 | 메인 데이터 저장 |
| Cache | Redis | 오늘의 미션 캐싱, 세션 |
| LLM | Claude API / OpenAI | 미션 생성 보조 (Admin에서 사용) |
| Push | FCM / APNs | 모바일 푸시 알림 |
| Infra | AWS (EC2, RDS, ElastiCache) | 클라우드 |
| CI/CD | GitHub Actions + Docker | 배포 자동화 |
| Monitoring | Grafana + Prometheus + Loki | 모니터링 |
10. 장애 대응
| 상황 | 대응 방안 |
|---|
| 스케줄러 장애 | 오늘의 미션 활성화 안 됨 → 수동 트리거 API 제공 |
| 오늘의 미션 없음 | 스케줄 미지정 → 전날 미션 유지 or 기본 미션 |
| LLM API 장애 | 미션 생성 불가 → 이전 절기 미션 복사 후 수정 |
| FCM 장애 | 실패 건 별도 저장 → 재발송 배치 |
| DB 장애 | Read Replica 활용, Redis 캐시 |
11. 2차 고도화 계획
| 기능 | 설명 | 우선순위 |
|---|
| 날씨 API 연동 | 비 오면 실내 미션으로 자동 교체 | 높음 |
| A/B 테스트 결과 반영 | 개인화 미션 노출 방식 확정 | 높음 |
| 미션 이력 기반 추천 | 완료/스킵 패턴 학습 | 낮음 |
11.1 날씨 연동 시 변경 사항
┌─────────────────────────────────────────────────────────────────┐
│ 날씨 연동 추가 시 │
│ │
│ [테이블 추가] │
│ - mission_tag에 weather 카테고리 추가 (sunny/rainy/all) │
│ - daily_mission_schedule에 fallback_mission_id 추가 │
│ │
│ [스케줄러 추가] │
│ - WeatherCheckJob (06:00) │
│ → 날씨 API 조회 → Redis 캐싱 │
│ → 비 예보 시 오늘의 미션 자동 교체 │
│ │
│ [개인화 미션 로직 수정] │
│ - 태그 매칭 시 날씨 태그도 고려 │
│ │
└─────────────────────────────────────────────────────────────────┘
12. 결정된 사항
| 항목 | 결정 |
|---|
| 오늘의 미션 방식 | LLM 생성 → 관리자 선별 (수동 큐레이션) |
| LLM 활용 | MVP에서 사용 (미션 생성 보조) |
| 날씨 API | MVP 제외 → 2차 |
| 개인화 미션 노출 | A/B 테스트로 결정 |
| 미션 풀 크기 | 절기당 45개 (LLM 50개 생성 → 45개 선별) |
13. 미결 사항 (TBD)
| 항목 | 선택지 | 현재 상태 |
|---|
| 예상 사용자 수 | 1천 / 1만 / 10만+ | 미정 |
| 알림 발송 시점 | 오전 / 점심 / 사용자 설정 | 미정 |
| 개인화 미션 개수 | A안 3~5개 / B안 1개 | A/B 테스트 |
| A/B 테스트 비율 | 50:50 / 70:30 | 미정 |
| LLM 서비스 선택 | Claude / GPT / 기타 | 미정 |