초기 프로젝트 설정 및 설계 문서 추가

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
cherry2250
2025-10-24 10:10:16 +09:00
commit 3f6e005026
76 changed files with 37842 additions and 0 deletions
+304
View File
@@ -0,0 +1,304 @@
# KT AI 기반 소상공인 이벤트 자동 생성 서비스 - 외부 시퀀스 설계
## 문서 정보
- **작성일**: 2025-10-22
- **작성자**: System Architect
- **버전**: 1.0
- **관련 문서**:
- [유저스토리](../../../userstory.md)
- [논리 아키텍처](../../logical/logical-architecture.md)
- [UI/UX 설계서](../../../uiux/uiux.md)
---
## 개요
본 문서는 KT AI 기반 소상공인 이벤트 자동 생성 서비스의 **외부 시퀀스 설계**를 정의합니다.
외부 시퀀스는 서비스 간의 상호작용과 데이터 흐름을 표현하며, 유저스토리와 논리 아키텍처를 기반으로 설계되었습니다.
### 설계 원칙
1. **유저스토리 기반**: 20개 유저스토리와 정확히 매칭
2. **Event-Driven 아키텍처**: Kafka를 통한 비동기 이벤트 발행/구독
3. **Resilience 패턴**: Circuit Breaker, Retry, Timeout, Fallback 적용
4. **Cache-Aside 패턴**: Redis 캐싱을 통한 성능 최적화
5. **서비스 독립성**: 느슨한 결합과 장애 격리
---
## 외부 시퀀스 플로우 목록
총 **4개의 주요 비즈니스 플로우**로 구성되어 있습니다:
### 1. 사용자 인증 플로우
**파일**: `사용자인증플로우.puml`
**포함된 유저스토리**:
- UFR-USER-010: 회원가입
- UFR-USER-020: 로그인
- UFR-USER-040: 로그아웃
**주요 참여자**:
- Frontend (Web/Mobile)
- API Gateway
- User Service
- Redis Cache
- User DB (PostgreSQL)
- 국세청 API (외부)
**핵심 기능**:
- JWT 기반 인증
- 사업자번호 검증 (Circuit Breaker 적용)
- Redis 캐싱 (사업자번호 검증 결과, TTL 7일)
- 비밀번호 해싱 (bcrypt)
- 사업자번호 암호화 (AES-256)
**Resilience 패턴**:
- Circuit Breaker: 국세청 API (실패율 50% 초과 시 Open)
- Retry: 최대 3회 재시도 (지수 백오프: 1초, 2초, 4초)
- Timeout: 5초
- Fallback: 사업자번호 검증 스킵 (수동 확인 안내)
---
### 2. 이벤트 생성 플로우
**파일**: `이벤트생성플로우.puml`
**포함된 유저스토리**:
- UFR-EVENT-020: 이벤트 목적 선택
- UFR-EVENT-030: AI 이벤트 추천
- UFR-CONT-010: SNS 이미지 생성
- UFR-EVENT-050: 최종 승인 및 배포
**주요 참여자**:
- Frontend
- API Gateway
- Event Service
- AI Service (Kafka 구독)
- Content Service (Kafka 구독)
- Distribution Service (동기 호출)
- Kafka (Event Topics + Job Topics)
- Redis Cache
- Event DB
- 외부 API (AI API, 이미지 생성 API, 배포 채널 APIs)
**핵심 기능**:
1. **이벤트 목적 선택** (동기)
- Event DB에 목적 저장
- EventCreated 이벤트 발행
2. **AI 이벤트 추천** (비동기)
- Kafka ai-job 토픽 발행
- AI Service 구독 및 처리
- Polling 패턴으로 Job 상태 확인 (최대 30초)
- Redis 캐싱 (TTL 24시간)
3. **SNS 이미지 생성** (비동기)
- Kafka image-job 토픽 발행
- Content Service 구독 및 처리
- Polling 패턴으로 Job 상태 확인 (최대 20초)
- CDN 업로드 및 Redis 캐싱 (TTL 7일)
4. **최종 승인 및 배포** (동기)
- Distribution Service REST API 직접 호출
- 다중 채널 병렬 배포 (1분 이내)
- DistributionCompleted 이벤트 발행
**Resilience 패턴**:
- Circuit Breaker: 모든 외부 API 호출 시 적용
- Retry: 최대 3회 재시도 (지수 백오프)
- Timeout: AI API 30초, 이미지 API 20초, 배포 API 10초
- Bulkhead: 채널별 스레드 풀 격리
- Fallback: AI 추천 시 캐시된 이전 결과, 이미지 생성 시 기본 템플릿
---
### 3. 고객 참여 플로우
**파일**: `고객참여플로우.puml`
**포함된 유저스토리**:
- UFR-PART-010: 이벤트 참여
- UFR-PART-030: 당첨자 추첨
**주요 참여자**:
- Frontend (고객용 / 사장님용)
- API Gateway
- Participation Service
- Kafka (Event Topics)
- Participation DB
- Analytics Service (이벤트 구독)
**핵심 기능**:
1. **이벤트 참여**
- 중복 참여 체크 (전화번호 기반)
- 응모 번호 발급
- ParticipantRegistered 이벤트 발행 → Analytics Service 구독
2. **당첨자 추첨**
- 난수 기반 무작위 추첨 (Crypto.randomBytes)
- Fisher-Yates Shuffle 알고리즘
- 매장 방문 고객 가산점 적용 (선택 옵션)
- WinnerSelected 이벤트 발행
**Event-Driven 특징**:
- Analytics Service가 ParticipantRegistered 이벤트 구독하여 실시간 통계 업데이트
- 서비스 간 직접 의존성 없이 이벤트로 느슨한 결합
---
### 4. 성과 분석 플로우
**파일**: `성과분석플로우.puml`
**포함된 유저스토리**:
- UFR-ANAL-010: 실시간 성과분석 대시보드 조회
**주요 참여자**:
- Frontend
- API Gateway
- Analytics Service
- Redis Cache (TTL 5분)
- Analytics DB
- Kafka (Event Topics 구독)
- 외부 API (우리동네TV, 지니TV, SNS APIs)
**핵심 기능**:
1. **대시보드 조회 - Cache HIT** (0.5초)
- Redis에서 캐시된 데이터 즉시 반환
- 히트율 목표: 95%
2. **대시보드 조회 - Cache MISS** (3초)
- Analytics DB 로컬 데이터 조회
- 외부 채널 API 병렬 호출 (Circuit Breaker 적용)
- 데이터 통합 및 ROI 계산
- Redis 캐싱 (TTL 5분)
3. **실시간 업데이트 (Background)**
- EventCreated 구독: 이벤트 기본 정보 초기화
- ParticipantRegistered 구독: 참여자 수 실시간 증가
- DistributionCompleted 구독: 배포 채널 통계 업데이트
**Resilience 패턴**:
- Circuit Breaker: 외부 채널 API 조회 시 (실패율 50% 초과 시 Open)
- Timeout: 10초
- Fallback: 캐시된 이전 데이터 반환 또는 기본값 설정
- 병렬 처리: 외부 채널 API 동시 호출
---
## 설계 특징
### 1. Event-Driven 아키텍처
- **Kafka 통합**: Event Topics와 Job Topics를 Kafka로 통합
- **느슨한 결합**: 서비스 간 직접 의존성 제거
- **장애 격리**: 한 서비스 장애가 다른 서비스에 영향 없음
- **확장 용이**: 새로운 구독자 추가로 기능 확장
### 2. 비동기 처리 패턴
- **Kafka Job Topics**: ai-job, image-job
- **Polling 패턴**: Job 상태 확인 (2-5초 간격)
- **처리 시간**: AI 추천 10초, 이미지 생성 5초
### 3. 동기 처리 패턴
- **Distribution Service**: REST API 직접 호출
- **다중 채널 배포**: 병렬 처리 (1분 이내)
- **Circuit Breaker**: 장애 전파 방지
### 4. Resilience 패턴 전면 적용
- **Circuit Breaker**: 모든 외부 API 호출
- **Retry**: 일시적 장애 자동 복구
- **Timeout**: 응답 시간 제한
- **Bulkhead**: 리소스 격리
- **Fallback**: 장애 시 대체 로직
### 5. Cache-Aside 패턴
- **Redis 캐싱**: 성능 최적화
- **TTL 설정**: 데이터 유효성 관리
- **히트율 목표**: 80-95%
- **응답 시간 개선**: 90-99%
---
## 파일 구조
```
design/backend/sequence/outer/
├── README.md (본 문서)
├── 사용자인증플로우.puml
├── 이벤트생성플로우.puml
├── 고객참여플로우.puml
└── 성과분석플로우.puml
```
---
## 다이어그램 확인 방법
### 1. Online PlantUML Viewer
1. https://www.plantuml.com/plantuml/uml 접속
2. `.puml` 파일 내용 붙여넣기
3. 다이어그램 시각적 확인
### 2. VSCode Extension
1. "PlantUML" 확장 프로그램 설치
2. `.puml` 파일 열기
3. `Alt+D` 또는 `Cmd+D`로 미리보기
### 3. IntelliJ IDEA Plugin
1. "PlantUML integration" 플러그인 설치
2. `.puml` 파일 열기
3. 우측 미리보기 패널에서 확인
---
## 주요 결정사항
1. **Kafka 통합**: Event Bus와 Job Queue를 Kafka로 통합하여 운영 복잡도 감소
2. **비동기 처리**: AI 추천 및 이미지 생성은 Kafka Job Topics를 통한 비동기 처리
3. **동기 배포**: Distribution Service는 REST API 직접 호출하여 동기 처리 (1분 이내)
4. **Resilience 패턴**: 모든 외부 API 호출 시 Circuit Breaker, Retry, Timeout, Fallback 적용
5. **Cache-Aside 패턴**: Redis 캐싱으로 응답 시간 90-99% 개선
6. **Event Topics**: EventCreated, ParticipantRegistered, WinnerSelected, DistributionCompleted
7. **Job Topics**: ai-job, image-job
---
## 검증 사항
### 1. 유저스토리 매칭
✅ 모든 유저스토리가 외부 시퀀스에 정확히 반영됨
- User 서비스: 4개 유저스토리
- Event 서비스: 4개 유저스토리
- Participation 서비스: 2개 유저스토리
- Analytics 서비스: 1개 유저스토리
### 2. 논리 아키텍처 일치성
✅ 논리 아키텍처의 모든 컴포넌트와 통신 패턴 반영
- Core Services: User, Event, Participation, Analytics
- Async Services: AI, Content, Distribution
- Kafka: Event Topics + Job Topics
- External Systems: 국세청 API, AI API, 이미지 생성 API, 배포 채널 APIs
### 3. Resilience 패턴 적용
✅ 모든 외부 API 호출에 Resilience 패턴 적용
- Circuit Breaker, Retry, Timeout, Bulkhead, Fallback
### 4. PlantUML 문법 검증
✅ PlantUML 기본 문법 검증 완료
- `!theme mono` 적용
- 동기/비동기 화살표 구분
- 한글 설명 추가
- 참여자 및 플로우 명확히 표현
---
## 향후 개선 방안
1. **WebSocket 기반 실시간 푸시**: 대시보드 실시간 업데이트 (폴링 대체)
2. **Saga 패턴 적용**: 복잡한 분산 트랜잭션 보상 로직 체계화
3. **Service Mesh 도입**: Istio를 통한 서비스 간 통신 관찰성 및 보안 강화
4. **Dead Letter Queue 고도화**: 실패 이벤트 재처리 및 알림 자동화
---
**문서 버전**: 1.0
**최종 수정일**: 2025-10-22
**작성자**: System Architect
@@ -0,0 +1,164 @@
@startuml 고객참여플로우
!theme mono
title 고객 참여 플로우 - 외부 시퀀스 다이어그램
actor "고객" as Customer
participant "Frontend\n(고객용)" as CustomerFE
participant "API Gateway" as Gateway
participant "Participation\nService" as PartService
participant "Kafka\n(Event Topics)" as Kafka
database "Participation\nDB" as PartDB
participant "Analytics\nService" as Analytics
actor "사장님" as Owner
participant "Frontend\n(사장님용)" as OwnerFE
== UFR-PART-010: 이벤트 참여 ==
Customer -> CustomerFE: 이벤트 참여 화면 접근\n(우리동네TV/SNS/링고비즈)
activate CustomerFE
CustomerFE -> Customer: 참여 정보 입력 폼 표시\n(이름, 전화번호, 참여경로)
Customer -> CustomerFE: 참여 정보 입력 및\n참여 버튼 클릭
CustomerFE -> CustomerFE: 클라이언트 유효성 검증\n(이름 2자 이상, 전화번호 형식)
CustomerFE -> Gateway: POST /api/v1/participations\n{이름, 전화번호, 참여경로, 개인정보동의}
activate Gateway
Gateway -> PartService: POST /participations/register\n{이름, 전화번호, 참여경로, 개인정보동의}
activate PartService
PartService -> PartDB: 참여자 중복 확인\n(전화번호, 이벤트ID로 조회)
activate PartDB
PartDB --> PartService: 중복 참여 여부 반환
deactivate PartDB
alt 중복 참여인 경우
PartService --> Gateway: 409 Conflict\n{message: "이미 참여하신 이벤트입니다"}
Gateway --> CustomerFE: 409 Conflict
CustomerFE -> Customer: 중복 참여 오류 메시지 표시
deactivate PartService
deactivate Gateway
deactivate CustomerFE
else 신규 참여인 경우
PartService -> PartService: 응모 번호 생성\n(UUID 또는 시퀀스 기반)
PartService -> PartDB: 참여자 정보 저장\n(이름, 전화번호, 참여경로,\n응모번호, 참여일시)
activate PartDB
PartDB --> PartService: 저장 완료
deactivate PartDB
PartService -> Kafka: Publish Event\n"ParticipantRegistered"\n{participantId, eventId,\nentryPath, timestamp}
activate Kafka
note right of Kafka
Topic: participant-events
Event: ParticipantRegistered
Data: {
participantId: UUID,
eventId: UUID,
entryPath: string,
timestamp: datetime
}
end note
Kafka --> Analytics: Subscribe Event\n"ParticipantRegistered"
activate Analytics
Analytics -> Analytics: 참여자 데이터 집계\n- 채널별 참여자 수\n- 시간대별 참여 추이\n- 실시간 통계 업데이트
deactivate Analytics
deactivate Kafka
PartService --> Gateway: 201 Created\n{응모번호, 당첨발표일, 참여완료메시지}
deactivate PartService
Gateway --> CustomerFE: 201 Created
deactivate Gateway
CustomerFE -> Customer: 참여 완료 화면 표시\n- 응모번호\n- 당첨 발표일\n- "참여해주셔서 감사합니다"
deactivate CustomerFE
end
== UFR-PART-020: 참여자 목록 조회 ==
Owner -> OwnerFE: 이벤트 상세 화면에서\n"참여자 목록" 탭 클릭
activate OwnerFE
OwnerFE -> Gateway: GET /api/v1/events/{eventId}/participants\n?page=1&size=20
activate Gateway
Gateway -> PartService: GET /events/{eventId}/participants\n?page=1&size=20
activate PartService
PartService -> PartDB: 참여자 목록 조회\n(이벤트ID, 페이지네이션)\nORDER BY 참여일시 DESC
activate PartDB
PartDB --> PartService: 참여자 목록 반환\n(이름, 전화번호, 참여경로,\n응모번호, 참여일시)\n+ 총 참여자 수
deactivate PartDB
PartService --> Gateway: 200 OK\n{participants[], totalCount, page, size}
deactivate PartService
Gateway --> OwnerFE: 200 OK
deactivate Gateway
OwnerFE -> Owner: 참여자 목록 화면 표시\n- 참여자 정보 테이블\n- 페이지네이션\n- 총 참여자 수\n- CSV 다운로드 버튼
deactivate OwnerFE
note right of Owner
참여자 정보:
- 이름 (마스킹: 김**)
- 전화번호 (마스킹: 010-****-1234)
- 참여경로 (우리동네TV, Instagram 등)
- 응모번호
- 참여일시
end note
== UFR-PART-030: 당첨자 추첨 ==
Owner -> OwnerFE: 이벤트 상세 화면에서\n"당첨자 추첨" 버튼 클릭
activate OwnerFE
OwnerFE -> Owner: 추첨 확인 다이얼로그 표시\n"당첨자를 추첨하시겠습니까?"
Owner -> OwnerFE: 확인 버튼 클릭
OwnerFE -> Gateway: POST /api/v1/events/{eventId}/draw-winners\n{당첨인원, 매장방문가산점옵션}
activate Gateway
Gateway -> PartService: POST /events/{eventId}/draw-winners\n{winnerCount, visitBonus}
activate PartService
PartService -> PartDB: 미당첨 참여자 목록 조회\n(이벤트ID로 당첨되지 않은 참여자 조회)
activate PartDB
PartDB --> PartService: 전체 참여자 목록 반환
deactivate PartDB
PartService -> PartService: 당첨자 추첨 알고리즘 실행\n1. 난수 생성 (Crypto.randomBytes)\n2. 매장방문 가산점 적용 (옵션)\n3. Fisher-Yates Shuffle\n4. 당첨인원만큼 선정
PartService -> PartDB: 당첨자 정보 업데이트\n(당첨 여부를 true로 설정, 당첨 일시 기록)
activate PartDB
PartDB --> PartService: 업데이트 완료
deactivate PartDB
PartService -> PartDB: 추첨 로그 저장\n(이벤트ID, 추첨방법, 당첨인원,\n알고리즘, 추첨일시)
activate PartDB
note right of PartDB
추첨 로그 저장:
- 추첨 일시
- 추첨 방법
- 알고리즘 버전
- 가산점 적용 여부
end note
PartDB --> PartService: 로그 저장 완료
deactivate PartDB
PartService --> Gateway: 200 OK\n{당첨자목록, 추첨로그ID}
deactivate PartService
Gateway --> OwnerFE: 200 OK
deactivate Gateway
OwnerFE -> Owner: 당첨자 목록 화면 표시\n- 당첨자 정보 (이름, 전화번호, 응모번호)\n- 추첨 완료 메시지
deactivate OwnerFE
@enduml
@@ -0,0 +1,177 @@
@startuml 사용자인증플로우
!theme mono
title KT AI 기반 소상공인 이벤트 자동 생성 서비스 - 사용자 인증 플로우 (외부 시퀀스)
actor "사용자\n(소상공인)" as User
participant "Frontend\n(Web/Mobile)" as Frontend
participant "API Gateway" as Gateway
participant "User Service" as UserService
database "User DB\n(PostgreSQL)" as UserDB
== UFR-USER-010: 회원가입 플로우 ==
User -> Frontend: 회원가입 화면 접근
activate Frontend
User -> Frontend: 회원 정보 입력\n(이름, 전화번호, 이메일, 비밀번호,\n매장명, 업종, 주소, 사업자번호)
Frontend -> Frontend: 클라이언트 측 유효성 검증\n(이메일 형식, 비밀번호 8자 이상 등)
Frontend -> Gateway: POST /api/users/register\n(회원 정보)
activate Gateway
Gateway -> Gateway: Request 검증\n(필수 필드, 데이터 타입)
Gateway -> UserService: POST /api/users/register\n(회원 정보)
activate UserService
UserService -> UserService: 서버 측 유효성 검증\n(이름 2자 이상, 전화번호 형식 등)
UserService -> UserDB: 이메일로 사용자 조회\n(중복 가입 확인)
activate UserDB
UserDB --> UserService: 기존 사용자 확인 결과
deactivate UserDB
alt 이메일 중복 존재
UserService --> Gateway: 400 Bad Request\n(이미 등록된 이메일)
Gateway --> Frontend: 400 Bad Request
Frontend --> User: "이미 가입된 이메일입니다"
else 이메일 신규
UserService -> UserDB: 전화번호로 사용자 조회\n(중복 가입 확인)
activate UserDB
UserDB --> UserService: 기존 사용자 확인 결과
deactivate UserDB
alt 전화번호 중복 존재
UserService --> Gateway: 400 Bad Request\n(이미 등록된 전화번호)
Gateway --> Frontend: 400 Bad Request
Frontend --> User: "이미 가입된 전화번호입니다"
else 신규 사용자
UserService -> UserService: 비밀번호 해싱\n(bcrypt, Cost Factor 10)
UserService -> UserService: 사업자번호 암호화\n(AES-256)
UserService -> UserDB: 트랜잭션 시작
activate UserDB
UserService -> UserDB: 사용자 정보 저장\n(이름, 전화번호, 이메일,\n비밀번호해시, 생성일시)
UserDB --> UserService: user_id 반환
UserService -> UserDB: 매장 정보 저장\n(사용자ID, 매장명, 업종,\n주소, 암호화된사업자번호,\n영업시간)
UserDB --> UserService: store_id 반환
UserService -> UserDB: 트랜잭션 커밋
deactivate UserDB
UserService -> UserService: JWT 토큰 생성\n(user_id, role=OWNER,\nexp=7일)
UserService --> Gateway: 201 Created\n(JWT 토큰, 사용자 정보)
deactivate UserService
Gateway --> Frontend: 201 Created\n(JWT 토큰, 사용자 정보)
deactivate Gateway
Frontend -> Frontend: JWT 토큰 저장\n(LocalStorage 또는 Cookie)
Frontend --> User: "회원가입이 완료되었습니다"
Frontend -> Gateway: 대시보드 화면으로 이동
deactivate Frontend
end
end
== UFR-USER-020: 로그인 플로우 ==
User -> Frontend: 로그인 화면 접근
activate Frontend
User -> Frontend: 이메일, 비밀번호 입력
Frontend -> Frontend: 클라이언트 측 유효성 검증\n(필수 필드 확인, 이메일 형식)
Frontend -> Gateway: POST /api/users/login\n(이메일, 비밀번호)
activate Gateway
Gateway -> Gateway: Request 검증
Gateway -> UserService: POST /api/users/login\n(이메일, 비밀번호)
activate UserService
UserService -> UserDB: 이메일로 사용자 조회\n(로그인 인증용)
activate UserDB
UserDB --> UserService: 사용자 정보\n(user_id, password_hash, role)
deactivate UserDB
alt 사용자 없음
UserService --> Gateway: 401 Unauthorized\n(인증 실패)
Gateway --> Frontend: 401 Unauthorized
Frontend --> User: "이메일 또는 비밀번호를\n확인해주세요"
else 사용자 존재
UserService -> UserService: 비밀번호 검증\n(bcrypt compare)
alt 비밀번호 불일치
UserService --> Gateway: 401 Unauthorized\n(인증 실패)
Gateway --> Frontend: 401 Unauthorized
Frontend --> User: "이메일 또는 비밀번호를\n확인해주세요"
else 비밀번호 일치
UserService -> UserService: JWT 토큰 생성\n(user_id, role=OWNER,\nexp=7일)
UserService -> UserDB: 최종 로그인 시각 업데이트\n(현재 시각으로 갱신)
activate UserDB
UserDB --> UserService: 업데이트 완료
deactivate UserDB
UserService --> Gateway: 200 OK\n(JWT 토큰, 사용자 정보)
deactivate UserService
Gateway --> Frontend: 200 OK\n(JWT 토큰, 사용자 정보)
deactivate Gateway
Frontend -> Frontend: JWT 토큰 저장\n(LocalStorage 또는 Cookie)
Frontend --> User: 로그인 성공
Frontend -> Gateway: 대시보드 화면으로 이동
deactivate Frontend
end
end
== UFR-USER-040: 로그아웃 플로우 ==
User -> Frontend: 프로필 탭 접근
activate Frontend
User -> Frontend: "로그아웃" 버튼 클릭
Frontend -> Frontend: 확인 다이얼로그 표시\n"로그아웃 하시겠습니까?"
User -> Frontend: "확인" 클릭
Frontend -> Gateway: POST /api/users/logout\nAuthorization: Bearer {JWT}
activate Gateway
Gateway -> Gateway: JWT 토큰 검증
Gateway -> UserService: POST /api/users/logout\n(JWT 토큰)
activate UserService
UserService -> UserService: JWT 토큰 블랙리스트에 추가\n(만료 시까지 유효)
UserService --> Gateway: 200 OK\n(로그아웃 성공)
deactivate UserService
Gateway --> Frontend: 200 OK
deactivate Gateway
Frontend -> Frontend: JWT 토큰 삭제\n(LocalStorage 또는 Cookie)
Frontend --> User: "안전하게 로그아웃되었습니다"
Frontend -> Gateway: 로그인 화면으로 이동
deactivate Frontend
@enduml
@@ -0,0 +1,198 @@
@startuml 성과분석플로우_외부시퀀스
!theme mono
title 성과 분석 플로우 - 외부 시퀀스 다이어그램\n(UFR-ANAL-010: 실시간 성과분석 대시보드 조회)
actor "소상공인" as User
participant "Frontend" as FE
participant "API Gateway" as GW
participant "Analytics Service" as Analytics
participant "Redis Cache\n(TTL 1시간)" as Redis
participant "Analytics DB" as AnalyticsDB
participant "Kafka\n(Event Topics)" as Kafka
note over AnalyticsDB
**배치 처리로 수집된 데이터**
- 외부 채널 통계는 배치 작업으로
주기적으로 수집하여 DB에 저장
- 목업 데이터로 시작, 점진적으로 실제 API 연동
end note
== 1. 대시보드 조회 - Cache HIT 시나리오 ==
User -> FE: 성과분석 대시보드 접근\n(Bottom Nav "분석" 탭 클릭)
activate FE
FE -> GW: GET /api/events/{id}/analytics\n+ Authorization: Bearer {token}
activate GW
GW -> GW: JWT 토큰 검증
GW -> Analytics: GET /api/events/{id}/analytics
activate Analytics
Analytics -> Redis: 대시보드 캐시 조회\n(캐시키: analytics:dashboard:{eventId})
activate Redis
Redis --> Analytics: **Cache HIT**\n캐시된 대시보드 데이터 반환
deactivate Redis
note right of Analytics
**Cache-Aside 패턴**
- TTL: 1시간
- 예상 크기: 5KB
- 히트율 목표: 95%
- 응답 시간: 0.5초
end note
Analytics --> GW: 200 OK\n대시보드 데이터 (JSON)
deactivate Analytics
GW --> FE: 200 OK\n대시보드 데이터
deactivate GW
FE -> FE: 대시보드 렌더링\n- 4개 요약 카드\n- 채널별 성과 차트\n- 시간대별 참여 추이
FE --> User: 실시간 대시보드 표시
deactivate FE
== 2. 대시보드 조회 - Cache MISS 시나리오 ==
User -> FE: 대시보드 새로고침\n또는 첫 조회
activate FE
FE -> GW: GET /api/events/{id}/analytics
activate GW
GW -> Analytics: GET /api/events/{id}/analytics
activate Analytics
Analytics -> Redis: 대시보드 캐시 조회\n(캐시키: analytics:dashboard:{eventId})
activate Redis
Redis --> Analytics: **Cache MISS**\nnull 반환
deactivate Redis
note right of Analytics
**데이터 통합 작업 시작**
- Analytics DB 조회
- 외부 채널 API 병렬 호출
- Circuit Breaker 적용
end note
|||
== 2.1. Analytics DB 조회 (로컬 데이터) ==
Analytics -> AnalyticsDB: 이벤트 통계 조회\n(이벤트ID로 통계 데이터 조회)
activate AnalyticsDB
AnalyticsDB --> Analytics: 이벤트 통계\n- 총 참여자 수\n- 예상 ROI\n- 매출 증가율
deactivate AnalyticsDB
|||
== 2.2. 배치 수집된 채널 통계 데이터 조회 ==
Analytics -> AnalyticsDB: 채널별 통계 조회\n(배치로 수집된 채널 데이터 조회)
activate AnalyticsDB
note right of Analytics
**배치 처리 방식**
- 외부 API는 별도 배치 작업으로 주기적 수집
- 수집된 데이터는 DB에 저장
- 대시보드에서는 DB 데이터만 조회
- 응답 시간 단축 및 외부 API 의존성 제거
end note
AnalyticsDB --> Analytics: 채널별 통계 데이터\n- 우리동네TV: 노출 5,000, 조회 1,200\n- 지니TV: 노출 10,000, 클릭 500\n- Instagram: 좋아요 300, 댓글 50\n- Naver: 조회 2,000\n- Kakao: 공유 100
deactivate AnalyticsDB
note right of Analytics
**목업 데이터 활용**
- 초기에는 목업 데이터로 시작
- 점진적으로 실제 배치 작업 구현
- 배치 주기: 5분마다 수집
end note
|||
== 2.3. 데이터 통합 및 ROI 계산 ==
Analytics -> Analytics: 데이터 통합 및 계산\n- 총 노출 수 = 외부 채널 노출 합계\n- 총 참여자 수 = Analytics DB\n- ROI 계산 = (수익 - 비용) / 비용 × 100\n- 채널별 전환율 계산
note right of Analytics
**ROI 계산 로직**
총 비용 = 경품 비용 + 플랫폼 비용
예상 수익 = 매출 증가액 + 신규 고객 LTV
투자 대비 수익률 = (수익 - 비용) / 비용 × 100
end note
|||
== 2.4. Redis 캐싱 및 응답 ==
Analytics -> Redis: 대시보드 데이터 캐시 저장\n(캐시키: analytics:dashboard:{eventId},\n값: 통합 데이터, TTL: 1시간)
activate Redis
Redis --> Analytics: OK
deactivate Redis
Analytics --> GW: 200 OK\n대시보드 데이터 (JSON)\n{\n 총참여자: 1,234,\n 총노출: 17,200,\n ROI: 250%,\n 채널별성과: [...]\n}
deactivate Analytics
GW --> FE: 200 OK\n대시보드 데이터
deactivate GW
FE -> FE: 대시보드 렌더링\n- 4개 요약 카드 표시\n- 채널별 성과 차트\n- 시간대별 참여 추이\n- 참여자 프로필 분석
FE --> User: 실시간 대시보드 표시\n(응답 시간: 3초)
deactivate FE
|||
== 3. 실시간 업데이트 (Background Event 구독) ==
note over Analytics, Kafka
**Analytics Service는 항상 Background에서
Kafka Event Topics를 구독하여
실시간으로 통계를 업데이트합니다**
end note
Kafka -> Analytics: **EventCreated** 이벤트\n{eventId, storeId, title, objective}
activate Analytics
Analytics -> AnalyticsDB: 이벤트 통계 초기화\n(이벤트 기본 정보 저장)
activate AnalyticsDB
AnalyticsDB --> Analytics: OK
deactivate AnalyticsDB
Analytics -> Redis: 캐시 무효화\n(캐시키 삭제: analytics:dashboard:{eventId})
activate Redis
Redis --> Analytics: OK
deactivate Redis
deactivate Analytics
...참여자 등록 시...
Kafka -> Analytics: **ParticipantRegistered** 이벤트\n{participantId, eventId, phoneNumber}
activate Analytics
Analytics -> AnalyticsDB: 참여자 수 업데이트\n(참여자 수 1 증가)
activate AnalyticsDB
AnalyticsDB --> Analytics: OK
deactivate AnalyticsDB
Analytics -> Redis: 캐시 무효화\n(캐시키 삭제: analytics:dashboard:{eventId})
activate Redis
Redis --> Analytics: OK
deactivate Redis
deactivate Analytics
...배포 완료 시...
Kafka -> Analytics: **DistributionCompleted** 이벤트\n{eventId, distributedChannels, completedAt}
activate Analytics
Analytics -> AnalyticsDB: 채널 통계 저장\n(배포 완료된 채널 정보 저장)
activate AnalyticsDB
AnalyticsDB --> Analytics: OK
deactivate AnalyticsDB
Analytics -> Redis: 캐시 무효화\n(캐시키 삭제: analytics:dashboard:{eventId})
activate Redis
Redis --> Analytics: OK
deactivate Redis
deactivate Analytics
note right of Analytics
**실시간 업데이트 메커니즘**
- EventCreated: 이벤트 기본 정보 초기화
- ParticipantRegistered: 참여자 수 실시간 증가
- DistributionCompleted: 배포 채널 통계 업데이트
- 캐시 무효화: 다음 조회 시 최신 데이터 갱신
end note
@enduml
@@ -0,0 +1,250 @@
@startuml 이벤트생성플로우
!theme mono
title 이벤트 생성 플로우 - 외부 시퀀스 다이어그램
actor "소상공인" as User
participant "Frontend" as FE
participant "API Gateway" as Gateway
participant "Event Service" as Event
participant "User Service" as UserSvc
participant "AI Service" as AI
participant "Content Service" as Content
participant "Distribution Service" as Dist
participant "Kafka" as Kafka
database "Event DB" as EventDB
database "User DB" as UserDB
database "Redis" as Redis
participant "외부 AI API" as AIApi
participant "이미지 생성 API" as ImageApi
participant "배포 채널 APIs" as ChannelApis
== 1. 이벤트 목적 선택 (UFR-EVENT-020) ==
User -> FE: 이벤트 목적 선택
FE -> Gateway: GET /api/users/{userId}/store\n회원 및 매장정보 조회
activate Gateway
Gateway -> UserSvc: GET /api/users/{userId}/store\n회원 및 매장정보 조회
activate UserSvc
UserSvc -> UserDB: 사용자 및 매장 정보 조회
activate UserDB
UserDB --> UserSvc: 사용자, 매장 정보 반환
deactivate UserDB
UserSvc --> Gateway: 200 OK\n{userId, storeName, industry, address}
deactivate UserSvc
Gateway --> FE: 200 OK\n{userId, storeName, industry, address}
deactivate Gateway
FE -> Gateway: POST /events/purposes\n{목적, userId, storeName, industry, address}
Gateway -> Event: 이벤트 목적 저장 요청
Event -> Redis: 이벤트 목적 정보 저장\nKey: draft:event:{eventDraftId}\n(목적, 매장정보 저장)\nTTL: 24시간
activate Redis
Redis --> Event: 저장 완료
deactivate Redis
Event --> Gateway: 저장 완료\n{eventDraftId}
Gateway --> FE: 200 OK
FE --> User: AI 추천 화면으로 이동
== 2. AI 이벤트 추천 - 비동기 처리 (UFR-EVENT-030) ==
User -> FE: AI 추천 요청
FE -> Gateway: POST /api/events/{eventDraftId}/ai-recommendations\n{목적, 업종, 지역}
Gateway -> Event: AI 추천 요청 전달
Event -> Kafka: Publish to ai-job-topic\n{jobId, eventDraftId, 목적, 업종, 지역}
Event --> Gateway: Job 생성 완료\n{jobId, status: PENDING}
Gateway --> FE: 202 Accepted\n{jobId}
FE --> User: "AI가 분석 중입니다..." (로딩)
note over AI: Kafka Consumer\nai 이벤트 생성 topic 구독
Kafka --> AI: Consume Job Message\n{jobId, eventDraftId, ...}
AI -> AIApi: 트렌드 분석 및 이벤트 추천 요청\n{목적, 업종, 지역, 매장정보}\n[Circuit Breaker, Timeout: 5분]
AIApi --> AI: 3가지 추천안 + 트렌드 요약\n(예: "여름철 시원한 음료 선호도 증가")
AI -> Redis: AI 추천 결과 저장\nKey: ai:event:{eventDraftId}\n(3가지 추천안, 트렌드 요약)\nTTL: 24시간
Redis --> AI: 저장 완료
AI -> Redis: Job 상태 업데이트\n(상태를 COMPLETED로 변경)
note over AI, Redis: AI 추천 정보는 Redis에 저장\n- Content Service가 읽기 위함\n- 최종 승인 시 Event DB에 영구 저장
group Polling으로 상태 확인
loop 상태 확인 (최대 30초)
FE -> Gateway: GET /jobs/{jobId}/status
Gateway -> Event: Job 상태 조회
Event -> Redis: Job 상태 조회\n(jobId로 상태 및 결과 조회)
Redis --> Event: {status, result}
alt Job 완료
Event --> Gateway: 200 OK\n{status: COMPLETED, recommendations, trendSummary}
Gateway --> FE: 추천 결과 및 트렌드 요약 반환
FE --> User: 트렌드 요약 표시\n3가지 추천안 표시\n(제목/경품 수정 가능)
else Job 진행중
Event --> Gateway: 200 OK\n{status: PENDING/PROCESSING}
Gateway --> FE: 진행중 상태
note over FE: 2초 후 재요청
end
end
end
User -> FE: 추천안 선택\n(제목/경품 커스텀)
FE -> Gateway: PUT /events/drafts/{eventDraftId}\n{선택한 추천안, 커스텀 정보}
Gateway -> Event: 선택 저장
Event -> Redis: 선택한 추천안 저장\nKey: draft:event:{eventDraftId}\n(이벤트 초안 업데이트)\nTTL: 24시간
activate Redis
Redis --> Event: 업데이트 완료
deactivate Redis
Event --> Gateway: 200 OK
Gateway --> FE: 저장 완료
FE --> User: 콘텐츠 생성 화면으로 이동
== 3. SNS 이미지 생성 - 비동기 처리 (UFR-CONT-010) ==
User -> FE: 이미지 생성 요청
FE -> Gateway: POST /api/content/images/{eventDraftId}/generate
Gateway -> Content: 이미지 생성 요청
Content -> Content: Job 생성\n{jobId, eventDraftId, status: PENDING}
Content --> Gateway: Job 생성 완료\n{jobId, status: PENDING}
Gateway --> FE: 202 Accepted\n{jobId}
FE --> User: "이미지 생성 중..." (로딩)
note over Content: 백그라운드 워커\nRedis 폴링 또는 스케줄러
Content -> Redis: AI 이벤트 데이터 읽기\nKey: ai:event:{eventDraftId}
activate Redis
Redis --> Content: AI 추천 결과\n{선택된 추천안, 이벤트 정보}
deactivate Redis
note over Content: inner sequence 참조:\ncontent-이미지생성.puml
par 심플 스타일
Content -> ImageApi: 심플 스타일 생성 요청\n[Circuit Breaker, Timeout: 5분]
ImageApi --> Content: 심플 이미지 URL
else 화려한 스타일
Content -> ImageApi: 화려한 스타일 생성 요청\n[Circuit Breaker, Timeout: 5분]
ImageApi --> Content: 화려한 이미지 URL
else 트렌디 스타일
Content -> ImageApi: 트렌디 스타일 생성 요청\n[Circuit Breaker, Timeout: 5분]
ImageApi --> Content: 트렌디 이미지 URL
end
Content -> Redis: 이미지 URL 저장\nKey: content:image:{eventDraftId}\n{심플, 화려, 트렌디 URL}\nTTL: 7일
activate Redis
Redis --> Content: 저장 완료
deactivate Redis
Content -> Redis: Job 상태 업데이트\n(상태를 COMPLETED로 변경)
note over Content, Redis: 이미지 URL은 Redis에 저장\n- 최종 승인 시 Event DB에 영구 저장
group Polling으로 상태 확인
loop 상태 확인 (최대 30초)
FE -> Gateway: GET /api/content/jobs/{jobId}/status
Gateway -> Content: Job 상태 조회
Content -> Redis: Job 상태 조회\n(jobId로 상태 및 이미지 URL 조회)
Redis --> Content: {status, imageUrls}
alt Job 완료
Content --> Gateway: 200 OK\n{status: COMPLETED, imageUrls}
Gateway --> FE: 이미지 URL 반환
FE --> User: 3가지 스타일 카드 표시
else Job 진행중
Content --> Gateway: 200 OK\n{status: PENDING/PROCESSING}
Gateway --> FE: 진행중 상태
note over FE: 2초 후 재요청
end
end
end
User -> FE: 스타일 선택 및 편집
FE -> Gateway: PUT /events/drafts/{eventDraftId}/content\n{선택한 이미지, 편집내용}
Gateway -> Event: 콘텐츠 선택 저장
Event -> Redis: 선택한 콘텐츠 저장\nKey: draft:event:{eventDraftId}\n(이벤트 초안 업데이트)\nTTL: 24시간
activate Redis
Redis --> Event: 업데이트 완료
deactivate Redis
Event --> Gateway: 200 OK
Gateway --> FE: 저장 완료
FE --> User: 배포 채널 선택 화면으로 이동
== 4. 최종 승인 및 다중 채널 배포 - 동기 처리 (UFR-EVENT-050) ==
User -> FE: 배포 채널 선택\n최종 승인 요청
FE -> Gateway: POST /api/events/{eventDraftId}/publish\n{선택 채널 목록}
Gateway -> Event: 최종 승인 및 배포 처리
note over Event: Redis 데이터를 Event DB에 영구 저장
Event -> Redis: 이벤트 초안 조회\nKey: draft:event:{eventDraftId}
activate Redis
Redis --> Event: 이벤트 초안 데이터\n(목적, 매장정보, 추천안, 콘텐츠)
deactivate Redis
Event -> Redis: AI 추천 결과 조회\nKey: ai:event:{eventDraftId}
activate Redis
Redis --> Event: AI 추천 결과
deactivate Redis
Event -> Redis: 이미지 URL 조회\nKey: content:image:{eventDraftId}
activate Redis
Redis --> Event: 이미지 URL 목록
deactivate Redis
Event -> EventDB: 이벤트 정보 영구 저장\n(목적, 매장정보, AI 추천, 이미지 URL, 배포 채널 포함)
EventDB --> Event: 저장 완료
Event -> EventDB: 이벤트 상태 변경\n(DRAFT → APPROVED로 업데이트)
EventDB --> Event: 상태 변경 완료
Event -> Kafka: Publish to event-topic\nEventCreated\n{eventId, 이벤트정보}
note over Event: 동기 호출로 배포 진행\ninner sequence 참조:\ndistribution-다중채널배포.puml
Event -> Dist: REST API - 배포 요청\nPOST /api/distribution/distribute\n{eventId, channels[], contentUrls}
note over Dist: Sprint 2: Mock 처리\n- 외부 API 호출 없음\n- 모든 배포 즉시 성공 처리\n- 배포 로그만 DB 기록
Dist -> EventDB: 배포 이력 초기화\n(이벤트ID, 상태: PENDING)
EventDB --> Dist: 배포 이력 ID
Dist -> EventDB: 배포 이력 상태 업데이트\n(상태: IN_PROGRESS)
note over Dist: 다중 채널 Mock 배포\n(내부 처리 상세는 inner sequence 참조)
par 우리동네TV
alt 우리동네TV 선택
Dist -> Dist: Mock 처리\n(즉시 성공)
Dist -> EventDB: 채널 로그 저장\n(우리동네TV, 성공,\n배포ID, 예상노출수)
end
else 링고비즈
alt 링고비즈 선택
Dist -> Dist: Mock 처리\n(즉시 성공)
Dist -> EventDB: 채널 로그 저장\n(링고비즈, 성공,\n업데이트 시각)
end
else 지니TV
alt 지니TV 선택
Dist -> Dist: Mock 처리\n(즉시 성공)
Dist -> EventDB: 채널 로그 저장\n(지니TV, 성공,\n광고ID, 스케줄)
end
else Instagram
alt Instagram 선택
Dist -> Dist: Mock 처리\n(즉시 성공)
Dist -> EventDB: 채널 로그 저장\n(Instagram, 성공,\npostUrl, postId)
end
else Naver Blog
alt Naver Blog 선택
Dist -> Dist: Mock 처리\n(즉시 성공)
Dist -> EventDB: 채널 로그 저장\n(NaverBlog, 성공,\npostUrl)
end
else Kakao Channel
alt Kakao Channel 선택
Dist -> Dist: Mock 처리\n(즉시 성공)
Dist -> EventDB: 채널 로그 저장\n(KakaoChannel, 성공,\nmessageId)
end
end
note over Dist: 모든 채널 배포 완료 (즉시 처리)
Dist -> EventDB: 배포 이력 상태 업데이트\n(상태: COMPLETED, 완료일시)
Dist -> Kafka: Publish to event-topic\nDistributionCompleted\n{eventId, channels[], results[], completedAt}
Dist --> Event: REST API 동기 응답\n200 OK\n{distributionId, status: COMPLETED, results[]}
Event -> EventDB: 이벤트 상태 업데이트\n(APPROVED → ACTIVE로 변경)
EventDB --> Event: 업데이트 완료
Event --> Gateway: 200 OK\n{eventId, 배포결과}
Gateway --> FE: 배포 완료
FE --> User: "이벤트가 배포되었습니다"\n대시보드로 이동
note over Event, Dist: Sprint 2 제약사항\n- 외부 API 호출 없음 (Mock)\n- 모든 배포 즉시 성공 처리\n- Circuit Breaker 미구현\n- Retry 로직 미구현\n\nSprint 3 이후 구현 예정\n- 실제 외부 채널 API 연동\n- Circuit Breaker 패턴\n- Retry 및 실패 처리
@enduml