edit outer sequence

This commit is contained in:
cherry2250 2025-10-22 17:32:19 +09:00
parent 7a1fcc96de
commit a8c6397edf
3 changed files with 64 additions and 189 deletions

View File

@ -7,9 +7,7 @@ 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: 회원가입 플로우 ==
@ -40,85 +38,36 @@ alt 중복 사용자 존재
Frontend --> User: "이미 가입된 전화번호입니다"
else 신규 사용자
' 사업자번호 검증 (Circuit Breaker 적용)
UserService -> Redis: GET user:business:{사업자번호}
activate Redis
Redis --> UserService: 캐시된 검증 결과 확인
deactivate Redis
UserService -> UserService: 비밀번호 해싱\n(bcrypt, Cost Factor 10)
alt 캐시 HIT (검증 결과 있음)
UserService -> UserService: 캐시된 검증 결과 사용\n(응답 시간: 0.1초)
else 캐시 MISS (검증 필요)
UserService -> NTSApi: POST /사업자번호_검증\n(사업자번호)\n[Circuit Breaker, Timeout 5초]
activate NTSApi
UserService -> UserService: 사업자번호 암호화\n(AES-256)
alt 국세청 API 정상 응답
NTSApi --> UserService: 200 OK\n(사업자번호 유효, 영업 상태)
deactivate NTSApi
UserService -> UserDB: BEGIN TRANSACTION
activate UserDB
UserService -> Redis: SET user:business:{사업자번호}\n검증 결과 (TTL 7일)
activate Redis
Redis --> UserService: 캐싱 완료
deactivate Redis
UserService -> UserDB: INSERT INTO users\n(name, phone_number, email,\npassword_hash, created_at)
UserDB --> UserService: user_id 반환
else 국세청 API 장애 (Circuit Breaker Open)
NTSApi --> UserService: 500 Internal Server Error\n또는 Timeout
deactivate NTSApi
UserService -> UserDB: INSERT INTO stores\n(user_id, store_name, industry,\naddress, business_number_encrypted,\nbusiness_hours)
UserDB --> UserService: store_id 반환
UserService -> UserService: Fallback 실행:\n사업자번호 검증 스킵\n(수동 확인 안내)
UserService -> UserDB: COMMIT TRANSACTION
deactivate UserDB
note right of UserService
**Resilience 패턴 적용**
- Circuit Breaker: 실패율 50% 초과 시 Open
- Retry: 최대 3회 재시도 (지수 백오프: 1초, 2초, 4초)
- Timeout: 5초
- Fallback: 검증 스킵 (수동 확인 안내)
end note
end
end
UserService -> UserService: JWT 토큰 생성\n(user_id, role=OWNER,\nexp=7일)
alt 사업자번호 검증 실패 (휴폐업 등)
UserService --> Gateway: 400 Bad Request\n(사업자번호 검증 실패)
Gateway --> Frontend: 400 Bad Request
Frontend --> User: "유효하지 않은 사업자번호입니다.\n휴폐업 여부를 확인해주세요."
else 사업자번호 검증 성공
UserService --> Gateway: 201 Created\n(JWT 토큰, 사용자 정보)
deactivate UserService
UserService -> UserService: 비밀번호 해싱\n(bcrypt, Cost Factor 10)
Gateway --> Frontend: 201 Created\n(JWT 토큰, 사용자 정보)
deactivate Gateway
UserService -> UserService: 사업자번호 암호화\n(AES-256)
Frontend -> Frontend: JWT 토큰 저장\n(LocalStorage 또는 Cookie)
UserService -> UserDB: BEGIN TRANSACTION
activate UserDB
Frontend --> User: "회원가입이 완료되었습니다"
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
Frontend -> Gateway: 대시보드 화면으로 이동
deactivate Frontend
end
== UFR-USER-020: 로그인 플로우 ==
@ -159,11 +108,6 @@ 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: 업데이트 완료
@ -203,10 +147,7 @@ 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 -> UserService: JWT 토큰 블랙리스트에 추가\n(만료 시까지 유효)
UserService --> Gateway: 200 OK\n(로그아웃 성공)
deactivate UserService
@ -221,28 +162,4 @@ 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

@ -10,11 +10,13 @@ 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
note over AnalyticsDB
**배치 처리로 수집된 데이터**
- 외부 채널 통계는 배치 작업으로
주기적으로 수집하여 DB에 저장
- 목업 데이터로 시작, 점진적으로 실제 API 연동
end note
== 1. 대시보드 조회 - Cache HIT 시나리오 ==
@ -83,56 +85,28 @@ AnalyticsDB --> Analytics: 이벤트 통계\n- 총 참여자 수\n- 예상 ROI\n
deactivate AnalyticsDB
|||
== 2.2. 외부 채널 API 병렬 호출 (Circuit Breaker 적용) ==
== 2.2. 배치 수집된 채널 통계 데이터 조회 ==
par 병렬 채널 API 호출
Analytics -> WooriAPI: GET /stats/{eventId}\n+ API Key\n[Circuit Breaker]
activate WooriAPI
Analytics -> AnalyticsDB: SELECT channel_stats\nWHERE event_id = {id}
activate AnalyticsDB
alt Circuit Breaker CLOSED (정상)
WooriAPI --> Analytics: 200 OK\n- 노출 수: 5,000\n- 조회 수: 1,200
deactivate WooriAPI
note right of Analytics
**배치 처리 방식**
- 외부 API는 별도 배치 작업으로 주기적 수집
- 수집된 데이터는 DB에 저장
- 대시보드에서는 DB 데이터만 조회
- 응답 시간 단축 및 외부 API 의존성 제거
end note
note right of Analytics
**Resilience 패턴**
- Circuit Breaker: 실패율 50% 초과 시 Open
- Timeout: 10초
- Retry: 최대 3회 (지수 백오프)
- Fallback: 캐시된 이전 데이터 반환
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
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
note right of Analytics
**목업 데이터 활용**
- 초기에는 목업 데이터로 시작
- 점진적으로 실제 배치 작업 구현
- 배치 주기: 10분마다 수집
end note
|||
== 2.3. 데이터 통합 및 ROI 계산 ==

View File

@ -11,7 +11,6 @@ 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
@ -21,14 +20,8 @@ participant "배포 채널 APIs" as ChannelApis
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 -> EventDB: 이벤트 목적 저장
EventDB --> Event: 저장 완료
Event --> Gateway: 저장 완료\n{eventDraftId}
Gateway --> FE: 200 OK
FE --> User: AI 추천 화면으로 이동
@ -42,38 +35,29 @@ Event --> Gateway: Job 생성 완료\n{jobId, status: PENDING}
Gateway --> FE: 202 Accepted\n{jobId}
FE --> User: "AI가 분석 중입니다..." (로딩)
note over AI: Kafka Consumer\nai-job-topic 구독
note over AI: Kafka Consumer\nai 이벤트 생성 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: 추천 결과 저장
AI -> EventDB: 과거 이벤트 데이터 조회
EventDB --> AI: 이벤트 통계 데이터
AI -> AIApi: 트렌드 분석 및 이벤트 추천 요청\n{목적, 업종, 지역, 과거데이터, 매장정보}
AIApi --> AI: 3가지 추천안 + 트렌드 요약\n(예: "여름철 시원한 음료 선호도 증가")
AI -> EventDB: 추천 결과 및 트렌드 요약 저장
EventDB --> AI: 저장 완료
AI -> Cache: Job 상태 업데이트\nkey: job:{jobId}\nstatus: COMPLETED
AI -> Kafka: Publish to event-topic\nEventRecommended\n{jobId, eventDraftId, recommendations}
AI -> EventDB: Job 상태 업데이트\nstatus: COMPLETED
AI -> Kafka: Publish to event-topic\nEventRecommended\n{jobId, eventDraftId, recommendations, trendSummary}
group Polling으로 상태 확인
loop 상태 확인 (최대 30초)
FE -> Gateway: GET /jobs/{jobId}/status
Gateway -> Event: Job 상태 조회
Event -> Cache: 캐시에서 Job 상태 확인
Cache --> Event: {status, result}
Event -> EventDB: Job 상태 및 결과 조회
EventDB --> Event: {status, result}
alt Job 완료
Event --> Gateway: 200 OK\n{status: COMPLETED, recommendations}
Gateway --> FE: 추천 결과 반환
FE --> User: 3가지 추천안 표시\n(제목/경품 수정 가능)
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: 진행중 상태
@ -100,7 +84,7 @@ Event --> Gateway: Job 생성 완료\n{jobId, status: PENDING}
Gateway --> FE: 202 Accepted\n{jobId}
FE --> User: "이미지 생성 중..." (로딩)
note over Content: Kafka Consumer\nimage-job-topic 구독
note over Content: Kafka Consumer\n이미지 생성 topic 구독
Kafka --> Content: Consume Job Message\n{jobId, eventDraftId, ...}
par 심플 스타일
@ -116,15 +100,15 @@ end
Content -> EventDB: 이미지 URL 저장
EventDB --> Content: 저장 완료
Content -> Cache: Job 상태 업데이트\nkey: job:{jobId}\nstatus: COMPLETED
Content -> EventDB: Job 상태 업데이트\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}
Event -> EventDB: Job 상태 및 결과 조회
EventDB --> Event: {status, imageUrls}
alt Job 완료
Event --> Gateway: 200 OK\n{status: COMPLETED, imageUrls}