외부 시퀀스 설계 완료

- 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:
cherry2250 2025-10-22 13:38:41 +09:00
parent d455dfa9d8
commit 44011cd73a
7 changed files with 1240 additions and 0 deletions

View 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

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

View 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

View 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

View 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

View 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
View 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."