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:
sunmingLee 2025-10-22 15:56:52 +09:00
commit 65e537761c
9 changed files with 372 additions and 364 deletions

View File

@ -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

View File

@ -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

View 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

View File

@ -202,7 +202,11 @@ else 캐시 MISS (새로운 이미지 생성)
JobStatus --> Handler: 업데이트 완료 JobStatus --> Handler: 업데이트 완료
deactivate JobStatus deactivate JobStatus
note over Handler: Kafka Event 발행\nContentCreated\n{jobId, eventDraftId, imageUrls} note over Handler
폴링 방식으로 결과 조회
- Event Service는 이미지결과조회 API로 확인
- Kafka 이벤트 발행 없음 (Consumer 없음)
end note
Handler --> Consumer: 처리 완료 Handler --> Consumer: 처리 완료
end end

View File

@ -36,9 +36,9 @@ activate Cache
Cache --> Service: OK Cache --> Service: OK
deactivate Cache deactivate Cache
Service -> Kafka: publish(EventCreated,\n{eventDraftId, userId, objective, createdAt}) Service -> Kafka: publish(EventDraftCreated,\n{eventDraftId, userId, objective, createdAt})
activate Kafka 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 Kafka --> Service: ACK
deactivate Kafka deactivate Kafka
@ -46,6 +46,6 @@ Service --> Controller: EventDraftResponse\n{eventDraftId, objective, status}
deactivate Service deactivate Service
Controller --> Client: 200 OK\n{eventDraftId} 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 @enduml

View File

@ -11,7 +11,6 @@ participant "LotteryAlgorithm" as Lottery
participant "ParticipantRepository" as Repo participant "ParticipantRepository" as Repo
participant "DrawLogRepository" as LogRepo participant "DrawLogRepository" as LogRepo
database "Participation DB" as DB database "Participation DB" as DB
participant "KafkaProducer" as Kafka
== UFR-PART-030: 당첨자 추첨 == == UFR-PART-030: 당첨자 추첨 ==
@ -40,16 +39,36 @@ else JWT 검증 성공
Controller -> Service: drawWinners(eventId, winnerCount, visitBonus) Controller -> Service: drawWinners(eventId, winnerCount, visitBonus)
activate Service activate Service
Service -> Repo: findAllByEventIdAndIsWinner(eventId, false) Service -> Service: 이벤트 상태 확인\n- 이벤트 종료 여부\n- 이미 추첨 완료 여부
activate Repo
Repo -> DB: SELECT * FROM participants\nWHERE event_id = ?\nAND is_winner = false\nORDER BY participated_at ASC
activate DB
DB --> Repo: 전체 참여자 목록
deactivate DB
Repo --> Service: List<Participant>
deactivate Repo
alt 참여자 수 부족 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
activate DB
DB --> Repo: 전체 참여자 목록
deactivate DB
Repo --> Service: List<Participant>
deactivate Repo
alt 참여자 수 부족
Service --> Controller: InsufficientParticipantsException Service --> Controller: InsufficientParticipantsException
Controller --> Gateway: 400 Bad Request\n{message: "참여자 수가 부족합니다"} Controller --> Gateway: 400 Bad Request\n{message: "참여자 수가 부족합니다"}
Gateway --> Owner: 400 Bad Request Gateway --> Owner: 400 Bad Request
@ -58,22 +77,14 @@ else JWT 검증 성공
deactivate Gateway deactivate Gateway
else 추첨 진행 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) Service -> Lottery: executeLottery(participants, winnerCount, visitBonus)
activate Lottery activate Lottery
note right of Lottery note right of Lottery
추첨 알고리즘: 추첨 알고리즘:
시간 복잡도: O(n log n)
공간 복잡도: O(n)
1. 난수 생성 (Crypto.randomBytes) 1. 난수 생성 (Crypto.randomBytes)
2. 매장 방문 가산점 적용 (옵션) 2. 매장 방문 가산점 적용 (옵션)
- 방문 고객: 가중치 2배 - 방문 고객: 가중치 2배
@ -97,6 +108,15 @@ else JWT 검증 성공
Service -> Service: DB 트랜잭션 시작 Service -> Service: DB 트랜잭션 시작
alt DB 저장 실패 시
note right of Service
트랜잭션 롤백 처리:
- 당첨자 업데이트 취소
- 추첨 로그 저장 취소
- 재시도 가능 상태 유지
end note
end
Service -> Repo: updateWinners(winnerIds) Service -> Repo: updateWinners(winnerIds)
activate Repo activate Repo
Repo -> DB: UPDATE participants\nSET is_winner = true,\nwon_at = NOW()\nWHERE participant_id IN (?) 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 -> 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} Service --> Controller: DrawWinnersResponse\n{당첨자목록, 추첨로그ID}
deactivate Service deactivate Service
@ -155,7 +156,7 @@ else JWT 검증 성공
deactivate Gateway deactivate Gateway
Owner -> Owner: 당첨자 목록 화면 표시\n- 당첨자 정보 테이블\n- 재추첨 버튼\n- 추첨 완료 메시지 Owner -> Owner: 당첨자 목록 화면 표시\n- 당첨자 정보 테이블\n- 재추첨 버튼\n- 추첨 완료 메시지
end end
end end
end end
end end

View File

@ -17,7 +17,10 @@ database "Redis Cache<<E>>" as Cache
Customer -> Gateway: POST /api/v1/participations\n{name, phone, eventId, entryPath, consent} Customer -> Gateway: POST /api/v1/participations\n{name, phone, eventId, entryPath, consent}
activate Gateway activate Gateway
Gateway -> Gateway: JWT 토큰 검증\n(선택사항, 비회원 참여 가능) note right of Gateway
비회원 참여 가능
JWT 검증 불필요
end note
Gateway -> Controller: POST /participations/register\n{name, phone, eventId, entryPath, consent} Gateway -> Controller: POST /participations/register\n{name, phone, eventId, entryPath, consent}
activate Controller activate Controller

View File

@ -49,7 +49,7 @@ else JWT 검증 성공
Service --> Controller: ParticipantListResponse\n(캐시된 데이터) Service --> Controller: ParticipantListResponse\n(캐시된 데이터)
note right of Service note right of Service
캐시된 데이터 반환 캐시된 데이터 반환
- TTL: 5 - TTL: 10
- 실시간 정확도 vs 성능 트레이드오프 - 실시간 정확도 vs 성능 트레이드오프
end note end note
Controller --> Gateway: 200 OK\n{participants, totalElements, totalPages} Controller --> Gateway: 200 OK\n{participants, totalElements, totalPages}
@ -73,6 +73,10 @@ else JWT 검증 성공
- isWinner 필터 (선택) - isWinner 필터 (선택)
- name/phone 검색 (선택) - name/phone 검색 (선택)
- 페이지네이션 (필수) - 페이지네이션 (필수)
필요 인덱스:
idx_participants_event_filters
(event_id, entry_path, is_winner, participated_at DESC)
end note end note
DB --> Repo: 참여자 목록 결과셋 DB --> Repo: 참여자 목록 결과셋
@ -88,12 +92,13 @@ else JWT 검증 성공
Service -> Service: DTO 변환\n- 전화번호 마스킹 (010-****-1234)\n- 응모번호 형식화\n- 당첨 여부 라벨 변환 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 activate Cache
note right of Cache note right of Cache
캐시 저장: 캐시 저장:
- 짧은 TTL (5분) - TTL: 10분
- 실시간 참여 반영을 위해 - 실시간 참여 반영과 성능 균형
- 이벤트 참여 빈도 고려
end note end note
Cache --> Service: 캐시 저장 완료 Cache --> Service: 캐시 저장 완료
deactivate Cache deactivate Cache

View File

@ -35,7 +35,7 @@ FE --> User: AI 추천 화면으로 이동
== 2. AI 이벤트 추천 - 비동기 처리 (UFR-EVENT-030) == == 2. AI 이벤트 추천 - 비동기 처리 (UFR-EVENT-030) ==
User -> FE: AI 추천 요청 User -> FE: AI 추천 요청
FE -> Gateway: POST /events/recommendations\n{eventDraftId, 목적, 업종, 지역} FE -> Gateway: POST /api/events/{eventDraftId}/ai-recommendations\n{목적, 업종, 지역}
Gateway -> Event: AI 추천 요청 전달 Gateway -> Event: AI 추천 요청 전달
Event -> Kafka: Publish to ai-job-topic\n{jobId, eventDraftId, 목적, 업종, 지역} Event -> Kafka: Publish to ai-job-topic\n{jobId, eventDraftId, 목적, 업종, 지역}
Event --> Gateway: Job 생성 완료\n{jobId, status: PENDING} Event --> Gateway: Job 생성 완료\n{jobId, status: PENDING}
@ -93,7 +93,7 @@ FE --> User: 콘텐츠 생성 화면으로 이동
== 3. SNS 이미지 생성 - 비동기 처리 (UFR-CONT-010) == == 3. SNS 이미지 생성 - 비동기 처리 (UFR-CONT-010) ==
User -> FE: 이미지 생성 요청 User -> FE: 이미지 생성 요청
FE -> Gateway: POST /contents/images\n{eventDraftId, 이벤트정보} FE -> Gateway: POST /api/events/{eventDraftId}/content-generation\n{이벤트정보}
Gateway -> Event: 이미지 생성 요청 Gateway -> Event: 이미지 생성 요청
Event -> Kafka: Publish to image-job-topic\n{jobId, eventDraftId, 이벤트정보} Event -> Kafka: Publish to image-job-topic\n{jobId, eventDraftId, 이벤트정보}
Event --> Gateway: Job 생성 완료\n{jobId, status: PENDING} Event --> Gateway: Job 생성 완료\n{jobId, status: PENDING}
@ -149,8 +149,8 @@ FE --> User: 배포 채널 선택 화면으로 이동
== 4. 최종 승인 및 다중 채널 배포 - 동기 처리 (UFR-EVENT-050) == == 4. 최종 승인 및 다중 채널 배포 - 동기 처리 (UFR-EVENT-050) ==
User -> FE: 배포 채널 선택\n최종 승인 요청 User -> FE: 배포 채널 선택\n최종 승인 요청
FE -> Gateway: POST /events/{eventDraftId}/approve\n{선택 채널 목록, 승인} FE -> Gateway: POST /api/events/{eventDraftId}/publish\n{선택 채널 목록}
Gateway -> Event: 최종 승인 처리 Gateway -> Event: 최종 승인 및 배포 처리
Event -> EventDB: 이벤트 상태 변경\nDRAFT → APPROVED Event -> EventDB: 이벤트 상태 변경\nDRAFT → APPROVED
EventDB --> Event: 상태 변경 완료 EventDB --> Event: 상태 변경 완료
Event -> Kafka: Publish to event-topic\nEventCreated\n{eventId, 이벤트정보} Event -> Kafka: Publish to event-topic\nEventCreated\n{eventId, 이벤트정보}