mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 06:46:25 +00:00
외부 시퀀스 설계 완료
- 4개 주요 비즈니스 플로우 외부 시퀀스 다이어그램 작성 * 사용자인증플로우: 회원가입, 로그인, 로그아웃 * 이벤트생성플로우: AI 추천, 이미지 생성, 다중 채널 배포 * 고객참여플로우: 이벤트 참여, 당첨자 추첨 * 성과분석플로우: 실시간 대시보드 조회 - Event-Driven 아키텍처 반영 (Kafka Event Topics + Job Topics) - Resilience 패턴 전면 적용 (Circuit Breaker, Retry, Timeout, Fallback) - Cache-Aside 패턴 적용 (Redis 캐싱) - 논리 아키텍처 및 유저스토리 기반 설계 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d455dfa9d8
commit
44011cd73a
54
claude/sequence-outer-design.md
Normal file
54
claude/sequence-outer-design.md
Normal file
@ -0,0 +1,54 @@
|
||||
# 외부시퀀스설계가이드
|
||||
|
||||
[요청사항]
|
||||
- <작성원칙>을 준용하여 설계
|
||||
- <작성순서>에 따라 설계
|
||||
- [결과파일] 안내에 따라 파일 작성
|
||||
|
||||
[가이드]
|
||||
<작성원칙>
|
||||
- **유저스토리와 매칭**되어야 함. **불필요한 추가 설계 금지**
|
||||
- **논리아키텍처에 정의한 참여자와 일치**해야 함
|
||||
- UI/UX설계서의 '사용자 플로우'참조하여 설계
|
||||
- **해당 플로우에 참여하는** 프론트엔드, 모든 서비스, 인프라 컴포넌트(예: Gateway, Message Queue, Database), 외부시스템을 참여자로 추가
|
||||
- 플로우에 참여자들의 역할과 책임을 명확히 표시
|
||||
- 플로우 수행에 필요한 프론트엔드부터 End-to-End 호출을 순서대로 표시하고 한글 설명 추가
|
||||
- 마이크로서비스 내부의 처리 흐름은 표시하지 않음
|
||||
- 동기/비동기 통신 구분 (실선/점선)
|
||||
- 캐시, 큐 등 인프라 컴포넌트와의 상호작용 포함
|
||||
<작성순서>
|
||||
- 준비:
|
||||
- 유저스토리, UI/UX설계서, 논리아키텍처 분석 및 이해
|
||||
- "@analyze --play" 프로토타입이 있는 경우 웹브라우저에서 실행하여 서비스 이해
|
||||
- 실행:
|
||||
- <플로우 분류기준>에 따라 최적의 플로우를 결정함
|
||||
- 외부시퀀스설계서 작성
|
||||
- <병렬수행>가이드에 따라 병렬 수행
|
||||
- **PlantUML 스크립트 파일 생성 즉시 검사 실행**: 'PlantUML 문법 검사 가이드' 준용
|
||||
- 검토:
|
||||
- <작성원칙> 준수 검토
|
||||
- 스쿼드 팀원 리뷰: 누락 및 개선 사항 검토
|
||||
- 수정 사항 선택 및 반영
|
||||
<플로우 분류기준>
|
||||
- **핵심 비즈니스 플로우별**: 사용자가 목표를 달성하기 위한 주요 업무 흐름
|
||||
- **통합 패턴별**: 동기/비동기, 캐시 활용, 외부 연동 등 기술적 통합 방식
|
||||
- **사용자 시나리오별**: 엔드투엔드 사용자 경험 기준
|
||||
- **데이터 흐름별**: 데이터의 생성, 변환, 저장 과정
|
||||
<병렬수행>
|
||||
- **서브 에이전트를 활용한 병렬 작성 필수**
|
||||
- 서비스별 독립적인 에이전트가 각 외부시퀀스설계를 동시에 작업
|
||||
- 모든 설계 완료 후 전체 검증
|
||||
|
||||
[참고자료]
|
||||
- 유저스토리
|
||||
- UI/UX설계서
|
||||
- 논리아키텍처
|
||||
- 프로토타입
|
||||
|
||||
[예시]
|
||||
- 링크: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/samples/sample-시퀀스설계서(외부).puml
|
||||
|
||||
[결과파일]
|
||||
- **주요 비즈니스 플로우별로 파일을 분리하여 작성**
|
||||
- 플로우명은 한글로 네이밍
|
||||
- 위치: design/backend/sequence/outer/{플로우명}.puml
|
||||
304
design/backend/sequence/outer/README.md
Normal file
304
design/backend/sequence/outer/README.md
Normal 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
|
||||
150
design/backend/sequence/outer/고객참여플로우.puml
Normal file
150
design/backend/sequence/outer/고객참여플로우.puml
Normal file
@ -0,0 +1,150 @@
|
||||
@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: SELECT * FROM participants\nWHERE phone_number = ? AND event_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: INSERT INTO participants\n(name, phone_number, entry_path,\napplication_number, participated_at)
|
||||
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-030: 당첨자 추첨 ==
|
||||
|
||||
Owner -> OwnerFE: 이벤트 상세 화면에서\n"당첨자 추첨" 버튼 클릭
|
||||
activate OwnerFE
|
||||
|
||||
OwnerFE -> Owner: 추첨 확인 다이얼로그 표시\n"당첨자를 추첨하시겠습니까?"
|
||||
|
||||
Owner -> OwnerFE: 확인 버튼 클릭
|
||||
|
||||
OwnerFE -> Gateway: POST /api/v1/participations/draw\n{eventId, 당첨인원, 매장방문가산점옵션}
|
||||
activate Gateway
|
||||
|
||||
Gateway -> PartService: POST /participations/draw-winners\n{eventId, winnerCount, visitBonus}
|
||||
activate PartService
|
||||
|
||||
PartService -> PartDB: SELECT * FROM participants\nWHERE event_id = ? AND is_winner = false
|
||||
activate PartDB
|
||||
PartDB --> PartService: 전체 참여자 목록 반환
|
||||
deactivate PartDB
|
||||
|
||||
PartService -> PartService: 당첨자 추첨 알고리즘 실행\n1. 난수 생성 (Crypto.randomBytes)\n2. 매장방문 가산점 적용 (옵션)\n3. Fisher-Yates Shuffle\n4. 당첨인원만큼 선정
|
||||
|
||||
PartService -> PartDB: UPDATE participants\nSET is_winner = true, won_at = NOW()\nWHERE participant_id IN (당첨자IDs)
|
||||
activate PartDB
|
||||
PartDB --> PartService: 업데이트 완료
|
||||
deactivate PartDB
|
||||
|
||||
PartService -> PartDB: INSERT INTO draw_logs\n(event_id, draw_method, winner_count,\nalgorithm, drawn_at)
|
||||
activate PartDB
|
||||
note right of PartDB
|
||||
추첨 로그 저장:
|
||||
- 추첨 일시
|
||||
- 추첨 방법
|
||||
- 알고리즘 버전
|
||||
- 가산점 적용 여부
|
||||
end note
|
||||
PartDB --> PartService: 로그 저장 완료
|
||||
deactivate PartDB
|
||||
|
||||
PartService -> Kafka: Publish Event\n"WinnerSelected"\n{eventId, winners[], timestamp}
|
||||
activate Kafka
|
||||
note right of Kafka
|
||||
Topic: participant-events
|
||||
Event: WinnerSelected
|
||||
Data: {
|
||||
eventId: UUID,
|
||||
winners: [
|
||||
{participantId, name, phone}
|
||||
],
|
||||
timestamp: datetime
|
||||
}
|
||||
end note
|
||||
|
||||
Kafka --> Analytics: Subscribe Event\n"WinnerSelected"
|
||||
activate Analytics
|
||||
Analytics -> Analytics: 당첨자 통계 업데이트\n- 당첨자 수 집계\n- 채널별 당첨 분포
|
||||
deactivate Analytics
|
||||
deactivate Kafka
|
||||
|
||||
PartService --> Gateway: 200 OK\n{당첨자목록, 추첨로그ID}
|
||||
deactivate PartService
|
||||
|
||||
Gateway --> OwnerFE: 200 OK
|
||||
deactivate Gateway
|
||||
|
||||
OwnerFE -> Owner: 당첨자 목록 화면 표시\n- 당첨자 정보 (이름, 전화번호, 응모번호)\n- "재추첨" 버튼\n- 추첨 완료 메시지
|
||||
deactivate OwnerFE
|
||||
|
||||
@enduml
|
||||
248
design/backend/sequence/outer/사용자인증플로우.puml
Normal file
248
design/backend/sequence/outer/사용자인증플로우.puml
Normal file
@ -0,0 +1,248 @@
|
||||
@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 "Redis\nCache" as Redis
|
||||
database "User DB\n(PostgreSQL)" as UserDB
|
||||
participant "국세청 API\n(외부)" as NTSApi
|
||||
|
||||
== 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: SELECT users\nWHERE phone_number = ?
|
||||
activate UserDB
|
||||
UserDB --> UserService: 기존 사용자 확인 결과
|
||||
deactivate UserDB
|
||||
|
||||
alt 중복 사용자 존재
|
||||
UserService --> Gateway: 400 Bad Request\n(이미 등록된 전화번호)
|
||||
Gateway --> Frontend: 400 Bad Request
|
||||
Frontend --> User: "이미 가입된 전화번호입니다"
|
||||
else 신규 사용자
|
||||
|
||||
' 사업자번호 검증 (Circuit Breaker 적용)
|
||||
UserService -> Redis: GET user:business:{사업자번호}
|
||||
activate Redis
|
||||
Redis --> UserService: 캐시된 검증 결과 확인
|
||||
deactivate Redis
|
||||
|
||||
alt 캐시 HIT (검증 결과 있음)
|
||||
UserService -> UserService: 캐시된 검증 결과 사용\n(응답 시간: 0.1초)
|
||||
else 캐시 MISS (검증 필요)
|
||||
UserService -> NTSApi: POST /사업자번호_검증\n(사업자번호)\n[Circuit Breaker, Timeout 5초]
|
||||
activate NTSApi
|
||||
|
||||
alt 국세청 API 정상 응답
|
||||
NTSApi --> UserService: 200 OK\n(사업자번호 유효, 영업 상태)
|
||||
deactivate NTSApi
|
||||
|
||||
UserService -> Redis: SET user:business:{사업자번호}\n검증 결과 (TTL 7일)
|
||||
activate Redis
|
||||
Redis --> UserService: 캐싱 완료
|
||||
deactivate Redis
|
||||
|
||||
else 국세청 API 장애 (Circuit Breaker Open)
|
||||
NTSApi --> UserService: 500 Internal Server Error\n또는 Timeout
|
||||
deactivate NTSApi
|
||||
|
||||
UserService -> UserService: Fallback 실행:\n사업자번호 검증 스킵\n(수동 확인 안내)
|
||||
|
||||
note right of UserService
|
||||
**Resilience 패턴 적용**
|
||||
- Circuit Breaker: 실패율 50% 초과 시 Open
|
||||
- Retry: 최대 3회 재시도 (지수 백오프: 1초, 2초, 4초)
|
||||
- Timeout: 5초
|
||||
- Fallback: 검증 스킵 (수동 확인 안내)
|
||||
end note
|
||||
end
|
||||
end
|
||||
|
||||
alt 사업자번호 검증 실패 (휴폐업 등)
|
||||
UserService --> Gateway: 400 Bad Request\n(사업자번호 검증 실패)
|
||||
Gateway --> Frontend: 400 Bad Request
|
||||
Frontend --> User: "유효하지 않은 사업자번호입니다.\n휴폐업 여부를 확인해주세요."
|
||||
else 사업자번호 검증 성공
|
||||
|
||||
UserService -> UserService: 비밀번호 해싱\n(bcrypt, Cost Factor 10)
|
||||
|
||||
UserService -> UserService: 사업자번호 암호화\n(AES-256)
|
||||
|
||||
UserService -> UserDB: BEGIN TRANSACTION
|
||||
activate UserDB
|
||||
|
||||
UserService -> UserDB: INSERT INTO users\n(name, phone_number, email,\npassword_hash, created_at)
|
||||
UserDB --> UserService: user_id 반환
|
||||
|
||||
UserService -> UserDB: INSERT INTO stores\n(user_id, store_name, industry,\naddress, business_number_encrypted,\nbusiness_hours)
|
||||
UserDB --> UserService: store_id 반환
|
||||
|
||||
UserService -> UserDB: COMMIT TRANSACTION
|
||||
deactivate UserDB
|
||||
|
||||
UserService -> UserService: JWT 토큰 생성\n(user_id, role=OWNER,\nexp=7일)
|
||||
|
||||
UserService -> Redis: SET user:session:{token}\n(user_id, role, TTL 7일)
|
||||
activate Redis
|
||||
Redis --> UserService: 세션 저장 완료
|
||||
deactivate Redis
|
||||
|
||||
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: SELECT users\nWHERE phone_number = ?
|
||||
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 -> Redis: SET user:session:{token}\n(user_id, role, TTL 7일)
|
||||
activate Redis
|
||||
Redis --> UserService: 세션 저장 완료
|
||||
deactivate Redis
|
||||
|
||||
UserService -> UserDB: UPDATE users\nSET last_login_at = NOW()\nWHERE user_id = ?
|
||||
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 -> Redis: DEL user:session:{token}
|
||||
activate Redis
|
||||
Redis --> UserService: 세션 삭제 완료
|
||||
deactivate Redis
|
||||
|
||||
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
|
||||
|
||||
note over User, NTSApi
|
||||
**Resilience 패턴 적용 요약**
|
||||
|
||||
**Circuit Breaker (국세청 API)**:
|
||||
- 실패율 50% 초과 시 Open
|
||||
- 30초 후 Half-Open 상태로 전환
|
||||
- 3개 요청 테스트 후 상태 결정
|
||||
|
||||
**Retry Pattern**:
|
||||
- 최대 3회 재시도
|
||||
- 지수 백오프: 1초, 2초, 4초
|
||||
- 재시도 대상: SocketTimeoutException, ConnectException
|
||||
|
||||
**Timeout Pattern**:
|
||||
- 국세청 API: 5초
|
||||
|
||||
**Fallback Pattern**:
|
||||
- 국세청 API 장애 시: 사업자번호 검증 스킵 (수동 확인 안내)
|
||||
|
||||
**Cache-Aside Pattern**:
|
||||
- 사업자번호 검증 결과 캐싱 (TTL 7일)
|
||||
- 캐시 HIT: 0.1초, MISS: 5초 (외부 API 호출)
|
||||
end note
|
||||
|
||||
@enduml
|
||||
224
design/backend/sequence/outer/성과분석플로우.puml
Normal file
224
design/backend/sequence/outer/성과분석플로우.puml
Normal file
@ -0,0 +1,224 @@
|
||||
@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 5분)" as Redis
|
||||
participant "Analytics DB" as AnalyticsDB
|
||||
participant "Kafka\n(Event Topics)" as Kafka
|
||||
box "외부 시스템" #LightGray
|
||||
participant "우리동네TV API" as WooriAPI
|
||||
participant "지니TV API" as GenieAPI
|
||||
participant "SNS APIs\n(Instagram/Naver/Kakao)" as SNSAPI
|
||||
end box
|
||||
|
||||
== 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: GET analytics:dashboard:{eventId}
|
||||
activate Redis
|
||||
Redis --> Analytics: **Cache HIT**\n캐시된 대시보드 데이터 반환
|
||||
deactivate Redis
|
||||
|
||||
note right of Analytics
|
||||
**Cache-Aside 패턴**
|
||||
- TTL: 5분
|
||||
- 예상 크기: 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: GET 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: SELECT event_stats\nWHERE event_id = {id}
|
||||
activate AnalyticsDB
|
||||
AnalyticsDB --> Analytics: 이벤트 통계\n- 총 참여자 수\n- 예상 ROI\n- 매출 증가율
|
||||
deactivate AnalyticsDB
|
||||
|
||||
|||
|
||||
== 2.2. 외부 채널 API 병렬 호출 (Circuit Breaker 적용) ==
|
||||
|
||||
par 병렬 채널 API 호출
|
||||
Analytics -> WooriAPI: GET /stats/{eventId}\n+ API Key\n[Circuit Breaker]
|
||||
activate WooriAPI
|
||||
|
||||
alt Circuit Breaker CLOSED (정상)
|
||||
WooriAPI --> Analytics: 200 OK\n- 노출 수: 5,000\n- 조회 수: 1,200
|
||||
deactivate WooriAPI
|
||||
|
||||
note right of Analytics
|
||||
**Resilience 패턴**
|
||||
- Circuit Breaker: 실패율 50% 초과 시 Open
|
||||
- Timeout: 10초
|
||||
- Retry: 최대 3회 (지수 백오프)
|
||||
- Fallback: 캐시된 이전 데이터 반환
|
||||
end note
|
||||
|
||||
else Circuit Breaker OPEN (장애)
|
||||
Analytics -> Analytics: **Fallback 실행**\n캐시된 이전 데이터 사용
|
||||
note right of Analytics
|
||||
Circuit Breaker OPEN 상태
|
||||
- 빠른 실패로 응답 시간 단축
|
||||
- 30초 후 Half-Open으로 전환
|
||||
end note
|
||||
end
|
||||
|
||||
else
|
||||
Analytics -> GenieAPI: GET /campaign/{eventId}/stats\n+ API Key\n[Circuit Breaker]
|
||||
activate GenieAPI
|
||||
|
||||
alt 정상 응답
|
||||
GenieAPI --> Analytics: 200 OK\n- 광고 노출 수: 10,000\n- 클릭 수: 500
|
||||
deactivate GenieAPI
|
||||
else Timeout (10초 초과)
|
||||
Analytics -> Analytics: **Timeout 처리**\n기본값 반환 (0)
|
||||
note right of Analytics
|
||||
Timeout 발생
|
||||
- 리소스 점유 방지
|
||||
- Fallback으로 기본값 설정
|
||||
end note
|
||||
end
|
||||
|
||||
else
|
||||
Analytics -> SNSAPI: GET /posts/{eventId}/insights\n+ Access Token\n[Circuit Breaker]
|
||||
activate SNSAPI
|
||||
|
||||
SNSAPI --> Analytics: 200 OK\n- Instagram: 좋아요 300, 댓글 50\n- Naver: 조회 수 2,000\n- Kakao: 공유 수 100
|
||||
deactivate SNSAPI
|
||||
end
|
||||
|
||||
|||
|
||||
== 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: SET analytics:dashboard:{eventId}\nvalue={통합 데이터}\nTTL=300초 (5분)
|
||||
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: INSERT INTO event_stats\n이벤트 기본 정보 초기화
|
||||
activate AnalyticsDB
|
||||
AnalyticsDB --> Analytics: OK
|
||||
deactivate AnalyticsDB
|
||||
Analytics -> Redis: DEL analytics:dashboard:{eventId}\n캐시 무효화
|
||||
activate Redis
|
||||
Redis --> Analytics: OK
|
||||
deactivate Redis
|
||||
deactivate Analytics
|
||||
|
||||
...참여자 등록 시...
|
||||
|
||||
Kafka -> Analytics: **ParticipantRegistered** 이벤트\n{participantId, eventId, phoneNumber}
|
||||
activate Analytics
|
||||
Analytics -> AnalyticsDB: UPDATE event_stats\nSET participant_count = participant_count + 1\nWHERE event_id = {eventId}
|
||||
activate AnalyticsDB
|
||||
AnalyticsDB --> Analytics: OK
|
||||
deactivate AnalyticsDB
|
||||
Analytics -> Redis: DEL analytics:dashboard:{eventId}\n캐시 무효화 (다음 조회 시 갱신)
|
||||
activate Redis
|
||||
Redis --> Analytics: OK
|
||||
deactivate Redis
|
||||
deactivate Analytics
|
||||
|
||||
...배포 완료 시...
|
||||
|
||||
Kafka -> Analytics: **DistributionCompleted** 이벤트\n{eventId, distributedChannels, completedAt}
|
||||
activate Analytics
|
||||
Analytics -> AnalyticsDB: INSERT INTO channel_stats\n배포 채널 통계 저장
|
||||
activate AnalyticsDB
|
||||
AnalyticsDB --> Analytics: OK
|
||||
deactivate AnalyticsDB
|
||||
Analytics -> Redis: DEL analytics:dashboard:{eventId}\n캐시 무효화
|
||||
activate Redis
|
||||
Redis --> Analytics: OK
|
||||
deactivate Redis
|
||||
deactivate Analytics
|
||||
|
||||
note right of Analytics
|
||||
**실시간 업데이트 메커니즘**
|
||||
- EventCreated: 이벤트 기본 정보 초기화
|
||||
- ParticipantRegistered: 참여자 수 실시간 증가
|
||||
- DistributionCompleted: 배포 채널 통계 업데이트
|
||||
- 캐시 무효화: 다음 조회 시 최신 데이터 갱신
|
||||
end note
|
||||
|
||||
@enduml
|
||||
210
design/backend/sequence/outer/이벤트생성플로우.puml
Normal file
210
design/backend/sequence/outer/이벤트생성플로우.puml
Normal file
@ -0,0 +1,210 @@
|
||||
@startuml 이벤트생성플로우
|
||||
!theme mono
|
||||
|
||||
title 이벤트 생성 플로우 - 외부 시퀀스 다이어그램
|
||||
|
||||
actor "소상공인" as User
|
||||
participant "Frontend" as FE
|
||||
participant "API Gateway" as Gateway
|
||||
participant "Event Service" as Event
|
||||
participant "AI Service" as AI
|
||||
participant "Content Service" as Content
|
||||
participant "Distribution Service" as Dist
|
||||
participant "Kafka" as Kafka
|
||||
participant "Redis Cache" as Cache
|
||||
database "Event DB" as EventDB
|
||||
participant "외부 AI API" as AIApi
|
||||
participant "이미지 생성 API" as ImageApi
|
||||
participant "배포 채널 APIs" as ChannelApis
|
||||
|
||||
== 1. 이벤트 목적 선택 (UFR-EVENT-020) ==
|
||||
User -> FE: 이벤트 목적 선택
|
||||
FE -> Gateway: POST /events/purposes\n{목적, 매장정보}
|
||||
Gateway -> Event: 이벤트 목적 저장 요청
|
||||
Event -> Cache: 캐시 조회\nkey: purpose:{userId}
|
||||
alt 캐시 히트
|
||||
Cache --> Event: 캐시된 데이터
|
||||
else 캐시 미스
|
||||
Event -> EventDB: 이벤트 목적 저장
|
||||
EventDB --> Event: 저장 완료
|
||||
Event -> Cache: 캐시 저장\nTTL: 30분
|
||||
end
|
||||
Event --> Gateway: 저장 완료\n{eventDraftId}
|
||||
Gateway --> FE: 200 OK
|
||||
FE --> User: AI 추천 화면으로 이동
|
||||
|
||||
== 2. AI 이벤트 추천 - 비동기 처리 (UFR-EVENT-030) ==
|
||||
User -> FE: AI 추천 요청
|
||||
FE -> Gateway: POST /events/recommendations\n{eventDraftId, 목적, 업종, 지역}
|
||||
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-job-topic 구독
|
||||
Kafka --> AI: Consume Job Message\n{jobId, eventDraftId, ...}
|
||||
AI -> Cache: 트렌드 분석 캐시 조회\nkey: trend:{업종}:{지역}
|
||||
|
||||
alt 캐시 히트
|
||||
Cache --> AI: 캐시된 트렌드 데이터
|
||||
else 캐시 미스
|
||||
AI -> EventDB: 과거 이벤트 데이터 조회
|
||||
EventDB --> AI: 이벤트 통계 데이터
|
||||
AI -> AIApi: 트렌드 분석 요청\n{업종, 지역, 과거데이터}
|
||||
AIApi --> AI: 트렌드 분석 결과
|
||||
AI -> Cache: 트렌드 캐시 저장\nTTL: 1시간
|
||||
end
|
||||
|
||||
AI -> AIApi: 이벤트 추천 요청\n{목적, 트렌드, 매장정보}
|
||||
AIApi --> AI: 3가지 추천안 생성
|
||||
AI -> EventDB: 추천 결과 저장
|
||||
EventDB --> AI: 저장 완료
|
||||
AI -> Cache: Job 상태 업데이트\nkey: job:{jobId}\nstatus: COMPLETED
|
||||
AI -> Kafka: Publish to event-topic\nEventRecommended\n{jobId, eventDraftId, recommendations}
|
||||
|
||||
group Polling으로 상태 확인
|
||||
loop 상태 확인 (최대 30초)
|
||||
FE -> Gateway: GET /jobs/{jobId}/status
|
||||
Gateway -> Event: Job 상태 조회
|
||||
Event -> Cache: 캐시에서 Job 상태 확인
|
||||
Cache --> Event: {status, result}
|
||||
|
||||
alt Job 완료
|
||||
Event --> Gateway: 200 OK\n{status: COMPLETED, recommendations}
|
||||
Gateway --> FE: 추천 결과 반환
|
||||
FE --> User: 3가지 추천안 표시\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 -> EventDB: 이벤트 초안 업데이트
|
||||
EventDB --> Event: 업데이트 완료
|
||||
Event --> Gateway: 200 OK
|
||||
Gateway --> FE: 저장 완료
|
||||
FE --> User: 콘텐츠 생성 화면으로 이동
|
||||
|
||||
== 3. SNS 이미지 생성 - 비동기 처리 (UFR-CONT-010) ==
|
||||
User -> FE: 이미지 생성 요청
|
||||
FE -> Gateway: POST /contents/images\n{eventDraftId, 이벤트정보}
|
||||
Gateway -> Event: 이미지 생성 요청
|
||||
Event -> Kafka: Publish to image-job-topic\n{jobId, eventDraftId, 이벤트정보}
|
||||
Event --> Gateway: Job 생성 완료\n{jobId, status: PENDING}
|
||||
Gateway --> FE: 202 Accepted\n{jobId}
|
||||
FE --> User: "이미지 생성 중..." (로딩)
|
||||
|
||||
note over Content: Kafka Consumer\nimage-job-topic 구독
|
||||
Kafka --> Content: Consume Job Message\n{jobId, eventDraftId, ...}
|
||||
|
||||
par 3가지 스타일 병렬 생성
|
||||
Content -> ImageApi: 심플 스타일 생성 요청
|
||||
ImageApi --> Content: 심플 이미지 URL
|
||||
and
|
||||
Content -> ImageApi: 화려한 스타일 생성 요청
|
||||
ImageApi --> Content: 화려한 이미지 URL
|
||||
and
|
||||
Content -> ImageApi: 트렌디 스타일 생성 요청
|
||||
ImageApi --> Content: 트렌디 이미지 URL
|
||||
end
|
||||
|
||||
Content -> EventDB: 이미지 URL 저장
|
||||
EventDB --> Content: 저장 완료
|
||||
Content -> Cache: Job 상태 업데이트\nkey: job:{jobId}\nstatus: COMPLETED
|
||||
Content -> Kafka: Publish to event-topic\nContentCreated\n{jobId, eventDraftId, imageUrls}
|
||||
|
||||
group Polling으로 상태 확인
|
||||
loop 상태 확인 (최대 30초)
|
||||
FE -> Gateway: GET /jobs/{jobId}/status
|
||||
Gateway -> Event: Job 상태 조회
|
||||
Event -> Cache: 캐시에서 Job 상태 확인
|
||||
Cache --> Event: {status, imageUrls}
|
||||
|
||||
alt Job 완료
|
||||
Event --> Gateway: 200 OK\n{status: COMPLETED, imageUrls}
|
||||
Gateway --> FE: 이미지 URL 반환
|
||||
FE --> User: 3가지 스타일 카드 표시
|
||||
else Job 진행중
|
||||
Event --> 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 -> EventDB: 이벤트 초안 업데이트
|
||||
EventDB --> Event: 업데이트 완료
|
||||
Event --> Gateway: 200 OK
|
||||
Gateway --> FE: 저장 완료
|
||||
FE --> User: 배포 채널 선택 화면으로 이동
|
||||
|
||||
== 4. 최종 승인 및 다중 채널 배포 - 동기 처리 (UFR-EVENT-050) ==
|
||||
User -> FE: 배포 채널 선택\n최종 승인 요청
|
||||
FE -> Gateway: POST /events/{eventDraftId}/approve\n{선택 채널 목록, 승인}
|
||||
Gateway -> Event: 최종 승인 처리
|
||||
Event -> EventDB: 이벤트 상태 변경\nDRAFT → APPROVED
|
||||
EventDB --> Event: 상태 변경 완료
|
||||
Event -> Kafka: Publish to event-topic\nEventCreated\n{eventId, 이벤트정보}
|
||||
|
||||
note over Event: 동기 호출로 배포 진행
|
||||
Event -> Dist: REST API - 배포 요청\nPOST /distributions\n{eventId, channels, content}
|
||||
|
||||
note over Dist: Circuit Breaker 적용
|
||||
|
||||
par 다중 채널 병렬 배포
|
||||
alt 우리동네TV 선택
|
||||
Dist -> ChannelApis: 우리동네TV API\n15초 영상 업로드
|
||||
ChannelApis --> Dist: 배포 완료\n{배포ID, 예상노출수}
|
||||
end
|
||||
and
|
||||
alt 링고비즈 선택
|
||||
Dist -> ChannelApis: 링고비즈 API\n연결음 업데이트
|
||||
ChannelApis --> Dist: 업데이트 완료\n{완료시각}
|
||||
end
|
||||
and
|
||||
alt 지니TV 선택
|
||||
Dist -> ChannelApis: 지니TV API\n광고 등록
|
||||
ChannelApis --> Dist: 광고 등록 완료\n{광고ID, 스케줄}
|
||||
end
|
||||
and
|
||||
alt Instagram 선택
|
||||
Dist -> ChannelApis: Instagram API\n포스팅
|
||||
ChannelApis --> Dist: 포스팅 완료\n{postUrl}
|
||||
end
|
||||
and
|
||||
alt Naver Blog 선택
|
||||
Dist -> ChannelApis: Naver API\n블로그 포스팅
|
||||
ChannelApis --> Dist: 포스팅 완료\n{postUrl}
|
||||
end
|
||||
and
|
||||
alt Kakao Channel 선택
|
||||
Dist -> ChannelApis: Kakao API\n채널 포스팅
|
||||
ChannelApis --> Dist: 포스팅 완료\n{postUrl}
|
||||
end
|
||||
end
|
||||
|
||||
Dist -> EventDB: 배포 이력 저장
|
||||
EventDB --> Dist: 저장 완료
|
||||
|
||||
Dist -> Kafka: Publish to event-topic\nDistributionCompleted\n{eventId, 배포결과}
|
||||
|
||||
Dist --> Event: REST API 응답\n200 OK\n{배포결과, 채널별 상태}
|
||||
Event -> EventDB: 이벤트 상태 업데이트\nAPPROVED → ACTIVE
|
||||
EventDB --> Event: 업데이트 완료
|
||||
|
||||
Event --> Gateway: 200 OK\n{eventId, 배포결과}
|
||||
Gateway --> FE: 배포 완료
|
||||
FE --> User: "이벤트가 배포되었습니다"\n대시보드로 이동
|
||||
|
||||
note over Event, Dist: 배포 실패 시\n- 채널별 독립 처리\n- 자동 재시도 (최대 3회)\n- Circuit Breaker로 장애 전파 방지\n- 실패한 채널만 재시도 가능
|
||||
|
||||
@enduml
|
||||
50
tools/check-plantuml.sh
Executable file
50
tools/check-plantuml.sh
Executable file
@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
|
||||
# PlantUML file syntax checker script
|
||||
# Usage: ./check_plantuml.sh <file_to_check>
|
||||
|
||||
# Check parameters
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Usage: $0 <file_to_check>"
|
||||
echo "Example: $0 diagram.puml"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# File to check parameter
|
||||
CHECK_FILE="$1"
|
||||
|
||||
# Check if file exists
|
||||
if [ ! -f "$CHECK_FILE" ]; then
|
||||
echo "Error: File '$CHECK_FILE' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 1. Generate unique filename (prevent conflicts)
|
||||
TEMP_FILE="/tmp/puml_$(date +%s)_$$.puml"
|
||||
|
||||
# 2. Copy file
|
||||
echo "Copying file to Docker container..."
|
||||
docker cp "$CHECK_FILE" plantuml:"$TEMP_FILE"
|
||||
|
||||
# 3. Find JAR file location
|
||||
echo "Finding PlantUML JAR file location..."
|
||||
JAR_PATH=$(docker exec plantuml find / -name "plantuml*.jar" 2>/dev/null | head -1)
|
||||
|
||||
if [ -z "$JAR_PATH" ]; then
|
||||
echo "Error: PlantUML JAR file not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 4. Syntax check
|
||||
echo "Running PlantUML syntax check..."
|
||||
docker exec plantuml java -jar "$JAR_PATH" -checkonly "$TEMP_FILE"
|
||||
|
||||
# 5. Detailed error check (if needed)
|
||||
echo "Checking detailed error information..."
|
||||
docker exec plantuml sh -c "cd /tmp && java -jar $JAR_PATH -failfast -v $TEMP_FILE 2>&1 | grep -E 'Error line'"
|
||||
|
||||
# 6. Clean up temporary file
|
||||
echo "Cleaning up temporary files..."
|
||||
docker exec -u root plantuml rm -f "$TEMP_FILE"
|
||||
|
||||
echo "Check completed."
|
||||
Loading…
x
Reference in New Issue
Block a user