mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 12:06:24 +00:00
Distribution 서비스 시퀀스 다이어그램 개선
- 어댑터 패턴 적용: 단일 Channel Adapter로 모든 채널 API 호출 처리 - Sprint 2 버전 추가: 외부 API 호출 없이 배포 로그만 기록하는 간소화 버전 - 복잡한 Circuit Breaker, Retry 로직 제거 (Sprint 2) - Mock 데이터로 즉시 성공 응답 반환 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
65e537761c
@ -1,238 +0,0 @@
|
||||
@startuml analytics-대시보드조회-캐시미스
|
||||
!theme mono
|
||||
|
||||
title Analytics Service - 대시보드 조회 (Cache MISS + 외부 API 병렬 호출) 내부 시퀀스\n(UFR-ANAL-010: 실시간 성과분석 대시보드 조회)
|
||||
|
||||
participant "AnalyticsController" as Controller
|
||||
participant "AnalyticsService" as Service
|
||||
participant "CacheService" as Cache
|
||||
participant "AnalyticsRepository" as Repository
|
||||
participant "ExternalChannelService" as ChannelService
|
||||
participant "ROICalculator" as Calculator
|
||||
participant "CircuitBreaker" as CB
|
||||
participant "Redis" as Redis
|
||||
database "Analytics DB" as DB
|
||||
|
||||
-> Controller: GET /api/events/{id}/analytics
|
||||
activate Controller
|
||||
|
||||
Controller -> Service: getDashboardData(eventId, userId)
|
||||
activate Service
|
||||
|
||||
Service -> Cache: get("analytics:dashboard:{eventId}")
|
||||
activate Cache
|
||||
|
||||
Cache -> Redis: GET analytics:dashboard:{eventId}
|
||||
activate Redis
|
||||
|
||||
Redis --> Cache: **Cache MISS** (null)
|
||||
deactivate Redis
|
||||
|
||||
Cache --> Service: null (캐시 미스)
|
||||
deactivate Cache
|
||||
|
||||
note right of Service
|
||||
**Cache MISS 처리**
|
||||
- 데이터 통합 작업 시작
|
||||
- 로컬 DB 조회 + 외부 API 병렬 호출
|
||||
end note
|
||||
|
||||
|||
|
||||
== 1. Analytics DB 조회 (로컬 데이터) ==
|
||||
|
||||
Service -> Repository: getEventStats(eventId)
|
||||
activate Repository
|
||||
|
||||
Repository -> DB: SELECT event_stats\nWHERE event_id = ?
|
||||
activate DB
|
||||
|
||||
DB --> Repository: EventStatsEntity\n- totalParticipants\n- estimatedROI\n- salesGrowthRate
|
||||
deactivate DB
|
||||
|
||||
Repository --> Service: EventStats
|
||||
deactivate Repository
|
||||
|
||||
note right of Service
|
||||
**로컬 데이터 확보**
|
||||
- 총 참여자 수
|
||||
- 예상 ROI (DB 캐시)
|
||||
- 매출 증가율 (POS 연동)
|
||||
end note
|
||||
|
||||
|||
|
||||
== 2. 외부 채널 API 병렬 호출 (Circuit Breaker 적용) ==
|
||||
|
||||
note right of Service
|
||||
**병렬 처리 시작**
|
||||
- CompletableFuture 3개 생성
|
||||
- 우리동네TV, 지니TV, SNS APIs 동시 호출
|
||||
- Circuit Breaker 적용 (채널별 독립)
|
||||
end note
|
||||
|
||||
par 외부 API 병렬 호출
|
||||
|
||||
Service -> ChannelService: getWooriTVStats(eventId)
|
||||
activate ChannelService
|
||||
|
||||
ChannelService -> CB: execute("wooriTV", () -> callAPI())
|
||||
activate CB
|
||||
|
||||
note right of CB
|
||||
**Circuit Breaker**
|
||||
- State: CLOSED (정상)
|
||||
- Failure Rate: 50% 초과 시 OPEN
|
||||
- Timeout: 10초
|
||||
end note
|
||||
|
||||
CB -> CB: 외부 API 호출\nGET /stats/{eventId}
|
||||
|
||||
alt Circuit Breaker CLOSED (정상)
|
||||
CB --> ChannelService: ChannelStats\n- views: 5000\n- clicks: 1200
|
||||
deactivate CB
|
||||
|
||||
ChannelService --> Service: WooriTVStats
|
||||
deactivate ChannelService
|
||||
else Circuit Breaker OPEN (장애)
|
||||
CB -> CB: **Fallback 실행**\n캐시된 이전 데이터 반환
|
||||
note right of CB
|
||||
Fallback 전략:
|
||||
- Redis에서 이전 통계 조회
|
||||
- 없으면 기본값 (0) 반환
|
||||
- 알림: "일부 채널 데이터 로딩 실패"
|
||||
end note
|
||||
CB --> ChannelService: Fallback 데이터
|
||||
deactivate CB
|
||||
ChannelService --> Service: WooriTVStats (Fallback)
|
||||
deactivate ChannelService
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
Service -> ChannelService: getGenieTVStats(eventId)
|
||||
activate ChannelService
|
||||
|
||||
ChannelService -> CB: execute("genieTV", () -> callAPI())
|
||||
activate CB
|
||||
|
||||
CB -> CB: 외부 API 호출\nGET /campaign/{id}/stats
|
||||
|
||||
alt 정상 응답
|
||||
CB --> ChannelService: ChannelStats\n- adViews: 10000\n- clicks: 500
|
||||
deactivate CB
|
||||
ChannelService --> Service: GenieTVStats
|
||||
deactivate ChannelService
|
||||
else Timeout (10초 초과)
|
||||
CB -> CB: **Timeout 처리**\n기본값 반환
|
||||
note right of CB
|
||||
Timeout 발생:
|
||||
- 리소스 점유 방지
|
||||
- Fallback으로 기본값 (0) 설정
|
||||
- 알림: "지니TV 데이터 로딩 지연"
|
||||
end note
|
||||
CB --> ChannelService: 기본값 (0)
|
||||
deactivate CB
|
||||
ChannelService --> Service: GenieTVStats (기본값)
|
||||
deactivate ChannelService
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
Service -> ChannelService: getSNSStats(eventId)
|
||||
activate ChannelService
|
||||
|
||||
ChannelService -> CB: execute("SNS", () -> callAPIs())
|
||||
activate CB
|
||||
|
||||
note right of CB
|
||||
**SNS APIs 통합 호출**
|
||||
- Instagram API
|
||||
- Naver Blog API
|
||||
- Kakao Channel API
|
||||
- 3개 API 병렬 호출
|
||||
end note
|
||||
|
||||
CB -> CB: 외부 APIs 호출\n(Instagram, Naver, Kakao)
|
||||
|
||||
CB --> ChannelService: SNSStats\n- Instagram: likes 300, comments 50\n- Naver: views 2000\n- Kakao: shares 100
|
||||
deactivate CB
|
||||
|
||||
ChannelService --> Service: SNSStats
|
||||
deactivate ChannelService
|
||||
|
||||
end
|
||||
|
||||
|||
|
||||
== 3. 데이터 통합 및 ROI 계산 ==
|
||||
|
||||
Service -> Service: mergeChannelStats(\n wooriTV, genieTV, sns\n)
|
||||
|
||||
note right of Service
|
||||
**데이터 통합**
|
||||
- 총 노출 수 = 외부 채널 노출 합계
|
||||
- 총 참여자 수 = Analytics DB
|
||||
- 채널별 전환율 = 참여자 수 / 노출 수
|
||||
end note
|
||||
|
||||
Service -> Calculator: calculateROI(\n eventStats, channelStats\n)
|
||||
activate Calculator
|
||||
|
||||
note right of Calculator
|
||||
**ROI 계산 로직**
|
||||
총 비용 = 경품 비용 + 플랫폼 비용
|
||||
예상 수익 = 매출 증가액 + 신규 고객 LTV
|
||||
ROI = (수익 - 비용) / 비용 × 100
|
||||
end note
|
||||
|
||||
Calculator --> Service: ROIData\n- roi: 250%\n- totalCost: 100만원\n- totalRevenue: 350만원\n- breakEvenPoint: 달성
|
||||
deactivate Calculator
|
||||
|
||||
Service -> Service: buildDashboardData(\n eventStats, channelStats, roiData\n)
|
||||
|
||||
note right of Service
|
||||
**대시보드 데이터 구조 생성**
|
||||
- 4개 요약 카드
|
||||
- 채널별 성과 차트 데이터
|
||||
- 시간대별 참여 추이
|
||||
- 참여자 프로필 분석
|
||||
- 비교 분석 (업종 평균, 이전 이벤트)
|
||||
end note
|
||||
|
||||
|||
|
||||
== 4. Redis 캐싱 및 응답 ==
|
||||
|
||||
Service -> Cache: set(\n "analytics:dashboard:{eventId}",\n dashboardData,\n TTL=300\n)
|
||||
activate Cache
|
||||
|
||||
Cache -> Redis: SET analytics:dashboard:{eventId}\nvalue={통합 데이터}\nEX 300
|
||||
activate Redis
|
||||
|
||||
Redis --> Cache: OK
|
||||
deactivate Redis
|
||||
|
||||
Cache --> Service: OK
|
||||
deactivate Cache
|
||||
|
||||
note right of Service
|
||||
**캐싱 완료**
|
||||
- TTL: 300초 (5분)
|
||||
- 다음 조회 시 Cache HIT
|
||||
- 예상 크기: 5KB
|
||||
end note
|
||||
|
||||
Service --> Controller: DashboardResponse\n(200 OK)
|
||||
deactivate Service
|
||||
|
||||
Controller --> : 200 OK\nDashboard Data (JSON)
|
||||
deactivate Controller
|
||||
|
||||
note over Controller, DB
|
||||
**Cache MISS 시나리오 성능**
|
||||
- 응답 시간: 약 3초
|
||||
- Analytics DB 조회: 0.1초
|
||||
- 외부 API 병렬 호출: 2초 (병렬 처리)
|
||||
- ROI 계산: 0.05초
|
||||
- Redis 캐싱: 0.01초
|
||||
- 직렬화/HTTP: 0.84초
|
||||
end note
|
||||
|
||||
@enduml
|
||||
@ -1,72 +0,0 @@
|
||||
@startuml analytics-대시보드조회-캐시히트
|
||||
!theme mono
|
||||
|
||||
title Analytics Service - 대시보드 조회 (Cache HIT) 내부 시퀀스\n(UFR-ANAL-010: 실시간 성과분석 대시보드 조회)
|
||||
|
||||
participant "AnalyticsController" as Controller
|
||||
participant "AnalyticsService" as Service
|
||||
participant "CacheService" as Cache
|
||||
participant "Redis" as Redis
|
||||
|
||||
-> Controller: GET /api/events/{id}/analytics\n+ Authorization: Bearer {token}
|
||||
activate Controller
|
||||
|
||||
Controller -> Service: getDashboardData(eventId, userId)
|
||||
activate Service
|
||||
|
||||
note right of Service
|
||||
**입력 검증**
|
||||
- eventId: UUID 형식 검증
|
||||
- userId: JWT에서 추출
|
||||
- 권한 확인: 매장 소유자 여부
|
||||
end note
|
||||
|
||||
Service -> Cache: get("analytics:dashboard:{eventId}")
|
||||
activate Cache
|
||||
|
||||
note right of Cache
|
||||
**Cache-Aside 패턴**
|
||||
- Redis GET 호출
|
||||
- Cache Key 구조:
|
||||
analytics:dashboard:{eventId}
|
||||
- TTL: 300초 (5분)
|
||||
end note
|
||||
|
||||
Cache -> Redis: GET analytics:dashboard:{eventId}
|
||||
activate Redis
|
||||
|
||||
Redis --> Cache: **Cache HIT**\n캐시된 데이터 반환\n{\n totalParticipants: 1234,\n totalViews: 17200,\n roi: 250,\n channelStats: [...],\n lastUpdated: "2025-10-22T10:30:00Z"\n}
|
||||
deactivate Redis
|
||||
|
||||
Cache --> Service: Dashboard 데이터 (JSON)
|
||||
deactivate Cache
|
||||
|
||||
note right of Service
|
||||
**응답 데이터 구조**
|
||||
- 4개 요약 카드
|
||||
* 총 참여자 수, 달성률
|
||||
* 총 노출 수, 증감률
|
||||
* 예상 ROI, 업종 평균 대비
|
||||
* 매출 증가율
|
||||
- 채널별 성과
|
||||
- 시간대별 참여 추이
|
||||
- 참여자 프로필 분석
|
||||
- 비교 분석 (업종 평균, 이전 이벤트)
|
||||
end note
|
||||
|
||||
Service --> Controller: DashboardResponse\n(200 OK)
|
||||
deactivate Service
|
||||
|
||||
Controller --> : 200 OK\nDashboard Data (JSON)
|
||||
deactivate Controller
|
||||
|
||||
note over Controller, Redis
|
||||
**Cache HIT 시나리오 성능**
|
||||
- 응답 시간: 약 0.5초
|
||||
- Redis 조회 시간: 0.01초
|
||||
- 직렬화/역직렬화: 0.05초
|
||||
- HTTP 오버헤드: 0.44초
|
||||
- 예상 히트율: 95%
|
||||
end note
|
||||
|
||||
@enduml
|
||||
305
design/backend/sequence/inner/analytics-대시보드조회.puml
Normal file
305
design/backend/sequence/inner/analytics-대시보드조회.puml
Normal file
@ -0,0 +1,305 @@
|
||||
@startuml analytics-대시보드조회
|
||||
!theme mono
|
||||
|
||||
title Analytics Service - 대시보드 조회 내부 시퀀스\n(UFR-ANAL-010: 실시간 성과분석 대시보드 조회)
|
||||
|
||||
participant "AnalyticsController" as Controller
|
||||
participant "AnalyticsService" as Service
|
||||
participant "CacheService" as Cache
|
||||
participant "AnalyticsRepository" as Repository
|
||||
participant "ExternalChannelService" as ChannelService
|
||||
participant "ROICalculator" as Calculator
|
||||
participant "CircuitBreaker" as CB
|
||||
participant "Redis<<E>>" as Redis
|
||||
database "Analytics DB<<E>>" as DB
|
||||
|
||||
-> Controller: GET /api/events/{id}/analytics\n+ Authorization: Bearer {token}
|
||||
activate Controller
|
||||
|
||||
Controller -> Service: getDashboardData(eventId, userId)
|
||||
activate Service
|
||||
|
||||
note right of Service
|
||||
**입력 검증**
|
||||
- eventId: UUID 형식 검증
|
||||
- userId: JWT에서 추출
|
||||
- 권한 확인: 매장 소유자 여부
|
||||
end note
|
||||
|
||||
Service -> Cache: get("analytics:dashboard:{eventId}")
|
||||
activate Cache
|
||||
|
||||
note right of Cache
|
||||
**Cache-Aside 패턴**
|
||||
- Redis GET 호출
|
||||
- Cache Key 구조:
|
||||
analytics:dashboard:{eventId}
|
||||
- TTL: 300초 (5분)
|
||||
end note
|
||||
|
||||
Cache -> Redis: GET analytics:dashboard:{eventId}
|
||||
activate Redis
|
||||
|
||||
alt Cache HIT
|
||||
|
||||
Redis --> Cache: **Cache HIT**\n캐시된 데이터 반환\n{\n totalParticipants: 1234,\n totalViews: 17200,\n roi: 250,\n channelStats: [...],\n lastUpdated: "2025-10-22T10:30:00Z"\n}
|
||||
deactivate Redis
|
||||
|
||||
Cache --> Service: Dashboard 데이터 (JSON)
|
||||
deactivate Cache
|
||||
|
||||
note right of Service
|
||||
**응답 데이터 구조**
|
||||
- 4개 요약 카드
|
||||
* 총 참여자 수, 달성률
|
||||
* 총 노출 수, 증감률
|
||||
* 예상 ROI, 업종 평균 대비
|
||||
* 매출 증가율
|
||||
- 채널별 성과
|
||||
- 시간대별 참여 추이
|
||||
- 참여자 프로필 분석
|
||||
- 비교 분석 (업종 평균, 이전 이벤트)
|
||||
end note
|
||||
|
||||
Service --> Controller: DashboardResponse\n(200 OK)
|
||||
deactivate Service
|
||||
|
||||
Controller --> : 200 OK\nDashboard Data (JSON)
|
||||
deactivate Controller
|
||||
|
||||
note over Controller, Redis
|
||||
**Cache HIT 시나리오 성능**
|
||||
- 응답 시간: 약 0.5초
|
||||
- Redis 조회 시간: 0.01초
|
||||
- 직렬화/역직렬화: 0.05초
|
||||
- HTTP 오버헤드: 0.44초
|
||||
- 예상 히트율: 95%
|
||||
end note
|
||||
|
||||
else Cache MISS
|
||||
|
||||
Redis --> Cache: **Cache MISS** (null)
|
||||
deactivate Redis
|
||||
|
||||
Cache --> Service: null (캐시 미스)
|
||||
deactivate Cache
|
||||
|
||||
note right of Service
|
||||
**Cache MISS 처리**
|
||||
- 데이터 통합 작업 시작
|
||||
- 로컬 DB 조회 + 외부 API 병렬 호출
|
||||
end note
|
||||
|
||||
|||
|
||||
== 1. Analytics DB 조회 (로컬 데이터) ==
|
||||
|
||||
Service -> Repository: getEventStats(eventId)
|
||||
activate Repository
|
||||
|
||||
Repository -> DB: SELECT event_stats\nWHERE event_id = ?
|
||||
activate DB
|
||||
|
||||
DB --> Repository: EventStatsEntity\n- totalParticipants\n- estimatedROI\n- salesGrowthRate
|
||||
deactivate DB
|
||||
|
||||
Repository --> Service: EventStats
|
||||
deactivate Repository
|
||||
|
||||
note right of Service
|
||||
**로컬 데이터 확보**
|
||||
- 총 참여자 수
|
||||
- 예상 ROI (DB 캐시)
|
||||
- 매출 증가율 (POS 연동)
|
||||
end note
|
||||
|
||||
|||
|
||||
== 2. 외부 채널 API 병렬 호출 (Circuit Breaker 적용) ==
|
||||
|
||||
note right of Service
|
||||
**병렬 처리 시작**
|
||||
- CompletableFuture 3개 생성
|
||||
- 우리동네TV, 지니TV, SNS APIs 동시 호출
|
||||
- Circuit Breaker 적용 (채널별 독립)
|
||||
end note
|
||||
|
||||
par 외부 API 병렬 호출
|
||||
|
||||
Service -> ChannelService: getWooriTVStats(eventId)
|
||||
activate ChannelService
|
||||
|
||||
ChannelService -> CB: execute("wooriTV", () -> callAPI())
|
||||
activate CB
|
||||
|
||||
note right of CB
|
||||
**Circuit Breaker**
|
||||
- State: CLOSED (정상)
|
||||
- Failure Rate: 50% 초과 시 OPEN
|
||||
- Timeout: 10초
|
||||
end note
|
||||
|
||||
CB -> CB: 외부 API 호출\nGET /stats/{eventId}
|
||||
|
||||
alt Circuit Breaker CLOSED (정상)
|
||||
CB --> ChannelService: ChannelStats\n- views: 5000\n- clicks: 1200
|
||||
deactivate CB
|
||||
|
||||
ChannelService --> Service: WooriTVStats
|
||||
deactivate ChannelService
|
||||
else Circuit Breaker OPEN (장애)
|
||||
CB -> CB: **Fallback 실행**\n캐시된 이전 데이터 반환
|
||||
note right of CB
|
||||
Fallback 전략:
|
||||
- Redis에서 이전 통계 조회
|
||||
- 없으면 기본값 (0) 반환
|
||||
- 알림: "일부 채널 데이터 로딩 실패"
|
||||
end note
|
||||
CB --> ChannelService: Fallback 데이터
|
||||
deactivate CB
|
||||
ChannelService --> Service: WooriTVStats (Fallback)
|
||||
deactivate ChannelService
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
Service -> ChannelService: getGenieTVStats(eventId)
|
||||
activate ChannelService
|
||||
|
||||
ChannelService -> CB: execute("genieTV", () -> callAPI())
|
||||
activate CB
|
||||
|
||||
CB -> CB: 외부 API 호출\nGET /campaign/{id}/stats
|
||||
|
||||
alt 정상 응답
|
||||
CB --> ChannelService: ChannelStats\n- adViews: 10000\n- clicks: 500
|
||||
deactivate CB
|
||||
ChannelService --> Service: GenieTVStats
|
||||
deactivate ChannelService
|
||||
else Timeout (10초 초과)
|
||||
CB -> CB: **Timeout 처리**\n기본값 반환
|
||||
note right of CB
|
||||
Timeout 발생:
|
||||
- 리소스 점유 방지
|
||||
- Fallback으로 기본값 (0) 설정
|
||||
- 알림: "지니TV 데이터 로딩 지연"
|
||||
end note
|
||||
CB --> ChannelService: 기본값 (0)
|
||||
deactivate CB
|
||||
ChannelService --> Service: GenieTVStats (기본값)
|
||||
deactivate ChannelService
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
Service -> ChannelService: getSNSStats(eventId)
|
||||
activate ChannelService
|
||||
|
||||
ChannelService -> CB: execute("SNS", () -> callAPIs())
|
||||
activate CB
|
||||
|
||||
note right of CB
|
||||
**SNS APIs 통합 호출**
|
||||
- Instagram API
|
||||
- Naver Blog API
|
||||
- Kakao Channel API
|
||||
- 3개 API 병렬 호출
|
||||
end note
|
||||
|
||||
CB -> CB: 외부 APIs 호출\n(Instagram, Naver, Kakao)
|
||||
|
||||
alt 정상 응답
|
||||
CB --> ChannelService: SNSStats\n- Instagram: likes 300, comments 50\n- Naver: views 2000\n- Kakao: shares 100
|
||||
deactivate CB
|
||||
ChannelService --> Service: SNSStats
|
||||
deactivate ChannelService
|
||||
else 장애 또는 Timeout
|
||||
CB -> CB: **Fallback 실행**\n기본값 반환
|
||||
note right of CB
|
||||
SNS API 장애:
|
||||
- 기본값 (0) 반환
|
||||
- 알림: "SNS 데이터 로딩 실패"
|
||||
end note
|
||||
CB --> ChannelService: 기본값 (0)
|
||||
deactivate CB
|
||||
ChannelService --> Service: SNSStats (기본값)
|
||||
deactivate ChannelService
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|||
|
||||
== 3. 데이터 통합 및 ROI 계산 ==
|
||||
|
||||
Service -> Service: mergeChannelStats(\n wooriTV, genieTV, sns\n)
|
||||
|
||||
note right of Service
|
||||
**데이터 통합**
|
||||
- 총 노출 수 = 외부 채널 노출 합계
|
||||
- 총 참여자 수 = Analytics DB
|
||||
- 채널별 전환율 = 참여자 수 / 노출 수
|
||||
end note
|
||||
|
||||
Service -> Calculator: calculateROI(\n eventStats, channelStats\n)
|
||||
activate Calculator
|
||||
|
||||
note right of Calculator
|
||||
**ROI 계산 로직**
|
||||
총 비용 = 경품 비용 + 플랫폼 비용
|
||||
예상 수익 = 매출 증가액 + 신규 고객 LTV
|
||||
ROI = (수익 - 비용) / 비용 × 100
|
||||
end note
|
||||
|
||||
Calculator --> Service: ROIData\n- roi: 250%\n- totalCost: 100만원\n- totalRevenue: 350만원\n- breakEvenPoint: 달성
|
||||
deactivate Calculator
|
||||
|
||||
Service -> Service: buildDashboardData(\n eventStats, channelStats, roiData\n)
|
||||
|
||||
note right of Service
|
||||
**대시보드 데이터 구조 생성**
|
||||
- 4개 요약 카드
|
||||
- 채널별 성과 차트 데이터
|
||||
- 시간대별 참여 추이
|
||||
- 참여자 프로필 분석
|
||||
- 비교 분석 (업종 평균, 이전 이벤트)
|
||||
end note
|
||||
|
||||
|||
|
||||
== 4. Redis 캐싱 및 응답 ==
|
||||
|
||||
Service -> Cache: set(\n "analytics:dashboard:{eventId}",\n dashboardData,\n TTL=300\n)
|
||||
activate Cache
|
||||
|
||||
Cache -> Redis: SET analytics:dashboard:{eventId}\nvalue={통합 데이터}\nEX 300
|
||||
activate Redis
|
||||
|
||||
Redis --> Cache: OK
|
||||
deactivate Redis
|
||||
|
||||
Cache --> Service: OK
|
||||
deactivate Cache
|
||||
|
||||
note right of Service
|
||||
**캐싱 완료**
|
||||
- TTL: 300초 (5분)
|
||||
- 다음 조회 시 Cache HIT
|
||||
- 예상 크기: 5KB
|
||||
end note
|
||||
|
||||
Service --> Controller: DashboardResponse\n(200 OK)
|
||||
deactivate Service
|
||||
|
||||
Controller --> : 200 OK\nDashboard Data (JSON)
|
||||
deactivate Controller
|
||||
|
||||
note over Controller, DB
|
||||
**Cache MISS 시나리오 성능**
|
||||
- 응답 시간: 약 3초
|
||||
- Analytics DB 조회: 0.1초
|
||||
- 외부 API 병렬 호출: 2초 (병렬 처리)
|
||||
- ROI 계산: 0.05초
|
||||
- Redis 캐싱: 0.01초
|
||||
- 직렬화/HTTP: 0.84초
|
||||
end note
|
||||
|
||||
end
|
||||
|
||||
@enduml
|
||||
@ -202,7 +202,11 @@ else 캐시 MISS (새로운 이미지 생성)
|
||||
JobStatus --> Handler: 업데이트 완료
|
||||
deactivate JobStatus
|
||||
|
||||
note over Handler: Kafka Event 발행\nContentCreated\n{jobId, eventDraftId, imageUrls}
|
||||
note over Handler
|
||||
폴링 방식으로 결과 조회
|
||||
- Event Service는 이미지결과조회 API로 확인
|
||||
- Kafka 이벤트 발행 없음 (Consumer 없음)
|
||||
end note
|
||||
|
||||
Handler --> Consumer: 처리 완료
|
||||
end
|
||||
|
||||
@ -36,9 +36,9 @@ activate Cache
|
||||
Cache --> Service: OK
|
||||
deactivate Cache
|
||||
|
||||
Service -> Kafka: publish(EventCreated,\n{eventDraftId, userId, objective, createdAt})
|
||||
Service -> Kafka: publish(EventDraftCreated,\n{eventDraftId, userId, objective, createdAt})
|
||||
activate Kafka
|
||||
note right: Kafka Event Topic:\nevent-topic
|
||||
note right: Kafka Event Topic:\nevent-topic\n\nEvent: EventDraftCreated\n(목적 선택 시 발행)\n\n※ EventCreated는\n최종 승인 시 발행
|
||||
Kafka --> Service: ACK
|
||||
deactivate Kafka
|
||||
|
||||
@ -46,6 +46,6 @@ Service --> Controller: EventDraftResponse\n{eventDraftId, objective, status}
|
||||
deactivate Service
|
||||
Controller --> Client: 200 OK\n{eventDraftId}
|
||||
|
||||
note over Controller, Kafka: 캐시 히트 시:\n1. Redis에서 조회 → 즉시 반환\n2. DB 조회 생략
|
||||
note over Controller, Kafka: 캐시 히트 시:\n1. Redis에서 조회 → 즉시 반환\n2. DB 조회 생략\n\n※ EventDraftCreated 이벤트는\nAnalytics Service가 선택적으로 구독\n(통계 초기화는 EventCreated 시)
|
||||
|
||||
@enduml
|
||||
|
||||
@ -11,7 +11,6 @@ participant "LotteryAlgorithm" as Lottery
|
||||
participant "ParticipantRepository" as Repo
|
||||
participant "DrawLogRepository" as LogRepo
|
||||
database "Participation DB" as DB
|
||||
participant "KafkaProducer" as Kafka
|
||||
|
||||
== UFR-PART-030: 당첨자 추첨 ==
|
||||
|
||||
@ -40,6 +39,26 @@ else JWT 검증 성공
|
||||
Controller -> Service: drawWinners(eventId, winnerCount, visitBonus)
|
||||
activate Service
|
||||
|
||||
Service -> Service: 이벤트 상태 확인\n- 이벤트 종료 여부\n- 이미 추첨 완료 여부
|
||||
|
||||
Service -> LogRepo: findByEventId(eventId)
|
||||
activate LogRepo
|
||||
LogRepo -> DB: SELECT * FROM draw_logs\nWHERE event_id = ?
|
||||
activate DB
|
||||
DB --> LogRepo: 추첨 로그 조회
|
||||
deactivate DB
|
||||
LogRepo --> Service: Optional<DrawLog>
|
||||
deactivate LogRepo
|
||||
|
||||
alt 이미 추첨 완료
|
||||
Service --> Controller: AlreadyDrawnException
|
||||
Controller --> Gateway: 409 Conflict\n{message: "이미 추첨이 완료된 이벤트입니다"}
|
||||
Gateway --> Owner: 409 Conflict
|
||||
deactivate Service
|
||||
deactivate Controller
|
||||
deactivate Gateway
|
||||
else 추첨 가능 상태
|
||||
|
||||
Service -> Repo: findAllByEventIdAndIsWinner(eventId, false)
|
||||
activate Repo
|
||||
Repo -> DB: SELECT * FROM participants\nWHERE event_id = ?\nAND is_winner = false\nORDER BY participated_at ASC
|
||||
@ -58,22 +77,14 @@ else JWT 검증 성공
|
||||
deactivate Gateway
|
||||
else 추첨 진행
|
||||
|
||||
Service -> Service: 이벤트 상태 확인\n- 이벤트 종료 여부\n- 이미 추첨 완료 여부
|
||||
|
||||
alt 이벤트가 아직 진행 중
|
||||
Service --> Controller: EventNotEndedException
|
||||
Controller --> Gateway: 400 Bad Request\n{message: "이벤트가 아직 진행 중입니다"}
|
||||
Gateway --> Owner: 400 Bad Request
|
||||
deactivate Service
|
||||
deactivate Controller
|
||||
deactivate Gateway
|
||||
else 추첨 가능 상태
|
||||
|
||||
Service -> Lottery: executeLottery(participants, winnerCount, visitBonus)
|
||||
activate Lottery
|
||||
|
||||
note right of Lottery
|
||||
추첨 알고리즘:
|
||||
시간 복잡도: O(n log n)
|
||||
공간 복잡도: O(n)
|
||||
|
||||
1. 난수 생성 (Crypto.randomBytes)
|
||||
2. 매장 방문 가산점 적용 (옵션)
|
||||
- 방문 고객: 가중치 2배
|
||||
@ -97,6 +108,15 @@ else JWT 검증 성공
|
||||
|
||||
Service -> Service: DB 트랜잭션 시작
|
||||
|
||||
alt DB 저장 실패 시
|
||||
note right of Service
|
||||
트랜잭션 롤백 처리:
|
||||
- 당첨자 업데이트 취소
|
||||
- 추첨 로그 저장 취소
|
||||
- 재시도 가능 상태 유지
|
||||
end note
|
||||
end
|
||||
|
||||
Service -> Repo: updateWinners(winnerIds)
|
||||
activate Repo
|
||||
Repo -> DB: UPDATE participants\nSET is_winner = true,\nwon_at = NOW()\nWHERE participant_id IN (?)
|
||||
@ -126,25 +146,6 @@ else JWT 검증 성공
|
||||
|
||||
Service -> Service: DB 트랜잭션 커밋
|
||||
|
||||
Service -> Kafka: Publish Event\n"WinnerSelected"\nTopic: participant-events
|
||||
activate Kafka
|
||||
note right of Kafka
|
||||
Event Payload:
|
||||
{
|
||||
"eventId": "UUID",
|
||||
"winners": [
|
||||
{
|
||||
"participantId": "UUID",
|
||||
"name": "홍길동",
|
||||
"phone": "010-1234-5678"
|
||||
}
|
||||
],
|
||||
"timestamp": "2025-10-22T15:00:00Z"
|
||||
}
|
||||
end note
|
||||
Kafka --> Service: 이벤트 발행 완료
|
||||
deactivate Kafka
|
||||
|
||||
Service --> Controller: DrawWinnersResponse\n{당첨자목록, 추첨로그ID}
|
||||
deactivate Service
|
||||
|
||||
|
||||
@ -17,7 +17,10 @@ database "Redis Cache<<E>>" as Cache
|
||||
Customer -> Gateway: POST /api/v1/participations\n{name, phone, eventId, entryPath, consent}
|
||||
activate Gateway
|
||||
|
||||
Gateway -> Gateway: JWT 토큰 검증\n(선택사항, 비회원 참여 가능)
|
||||
note right of Gateway
|
||||
비회원 참여 가능
|
||||
JWT 검증 불필요
|
||||
end note
|
||||
|
||||
Gateway -> Controller: POST /participations/register\n{name, phone, eventId, entryPath, consent}
|
||||
activate Controller
|
||||
|
||||
@ -49,7 +49,7 @@ else JWT 검증 성공
|
||||
Service --> Controller: ParticipantListResponse\n(캐시된 데이터)
|
||||
note right of Service
|
||||
캐시된 데이터 반환
|
||||
- TTL: 5분
|
||||
- TTL: 10분
|
||||
- 실시간 정확도 vs 성능 트레이드오프
|
||||
end note
|
||||
Controller --> Gateway: 200 OK\n{participants, totalElements, totalPages}
|
||||
@ -73,6 +73,10 @@ else JWT 검증 성공
|
||||
- isWinner 필터 (선택)
|
||||
- name/phone 검색 (선택)
|
||||
- 페이지네이션 (필수)
|
||||
|
||||
필요 인덱스:
|
||||
idx_participants_event_filters
|
||||
(event_id, entry_path, is_winner, participated_at DESC)
|
||||
end note
|
||||
|
||||
DB --> Repo: 참여자 목록 결과셋
|
||||
@ -88,12 +92,13 @@ else JWT 검증 성공
|
||||
|
||||
Service -> Service: DTO 변환\n- 전화번호 마스킹 (010-****-1234)\n- 응모번호 형식화\n- 당첨 여부 라벨 변환
|
||||
|
||||
Service -> Cache: SET participant_list:{key} = data\nTTL: 5분
|
||||
Service -> Cache: SET participant_list:{key} = data\nTTL: 10분
|
||||
activate Cache
|
||||
note right of Cache
|
||||
캐시 저장:
|
||||
- 짧은 TTL (5분)
|
||||
- 실시간 참여 반영을 위해
|
||||
- TTL: 10분
|
||||
- 실시간 참여 반영과 성능 균형
|
||||
- 이벤트 참여 빈도 고려
|
||||
end note
|
||||
Cache --> Service: 캐시 저장 완료
|
||||
deactivate Cache
|
||||
|
||||
@ -35,7 +35,7 @@ FE --> User: AI 추천 화면으로 이동
|
||||
|
||||
== 2. AI 이벤트 추천 - 비동기 처리 (UFR-EVENT-030) ==
|
||||
User -> FE: AI 추천 요청
|
||||
FE -> Gateway: POST /events/recommendations\n{eventDraftId, 목적, 업종, 지역}
|
||||
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}
|
||||
@ -93,7 +93,7 @@ FE --> User: 콘텐츠 생성 화면으로 이동
|
||||
|
||||
== 3. SNS 이미지 생성 - 비동기 처리 (UFR-CONT-010) ==
|
||||
User -> FE: 이미지 생성 요청
|
||||
FE -> Gateway: POST /contents/images\n{eventDraftId, 이벤트정보}
|
||||
FE -> Gateway: POST /api/events/{eventDraftId}/content-generation\n{이벤트정보}
|
||||
Gateway -> Event: 이미지 생성 요청
|
||||
Event -> Kafka: Publish to image-job-topic\n{jobId, eventDraftId, 이벤트정보}
|
||||
Event --> Gateway: Job 생성 완료\n{jobId, status: PENDING}
|
||||
@ -149,8 +149,8 @@ FE --> User: 배포 채널 선택 화면으로 이동
|
||||
|
||||
== 4. 최종 승인 및 다중 채널 배포 - 동기 처리 (UFR-EVENT-050) ==
|
||||
User -> FE: 배포 채널 선택\n최종 승인 요청
|
||||
FE -> Gateway: POST /events/{eventDraftId}/approve\n{선택 채널 목록, 승인}
|
||||
Gateway -> Event: 최종 승인 처리
|
||||
FE -> Gateway: POST /api/events/{eventDraftId}/publish\n{선택 채널 목록}
|
||||
Gateway -> Event: 최종 승인 및 배포 처리
|
||||
Event -> EventDB: 이벤트 상태 변경\nDRAFT → APPROVED
|
||||
EventDB --> Event: 상태 변경 완료
|
||||
Event -> Kafka: Publish to event-topic\nEventCreated\n{eventId, 이벤트정보}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user