mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 10:46:23 +00:00
277 lines
11 KiB
Plaintext
277 lines
11 KiB
Plaintext
@startuml distribution-다중채널배포
|
|
!theme mono
|
|
|
|
title Distribution Service - 다중 채널 배포 (UFR-DIST-010)
|
|
|
|
actor Client
|
|
participant "Event Service" as EventSvc
|
|
participant "Distribution\nREST API" as API
|
|
participant "Distribution\nController" as Controller
|
|
participant "Distribution\nService" as Service
|
|
participant "Circuit Breaker\nManager" as CB
|
|
participant "Channel\nDistributor" as Distributor
|
|
participant "Retry Handler" as Retry
|
|
database "Event DB" as DB
|
|
queue "Kafka" as Kafka
|
|
participant "우리동네TV API" as WooridongneTV
|
|
participant "링고비즈 API" as RingoBiz
|
|
participant "지니TV API" as GenieTV
|
|
participant "SNS APIs" as SNS
|
|
participant "Redis Cache" as Cache
|
|
|
|
== REST API 동기 호출 수신 ==
|
|
EventSvc -> API: POST /api/distribution/distribute\n{eventId, channels[], contentUrls}
|
|
activate API
|
|
|
|
API -> Controller: distributeToChannels(request)
|
|
activate Controller
|
|
|
|
Controller -> Service: executeDistribution(distributionRequest)
|
|
activate Service
|
|
|
|
Service -> DB: 배포 이력 초기화\nINSERT distribution_logs\n{eventId, status: PENDING}
|
|
DB --> Service: 배포 이력 ID
|
|
|
|
note over Service: 배포 시작 상태로 변경
|
|
Service -> DB: UPDATE distribution_logs\nSET status = 'IN_PROGRESS'
|
|
|
|
== Circuit Breaker 및 Bulkhead 초기화 ==
|
|
Service -> CB: 채널별 Circuit Breaker 상태 확인
|
|
CB --> Service: 모든 Circuit Breaker 상태\n(CLOSED/OPEN/HALF_OPEN)
|
|
|
|
note over Service: Bulkhead 패턴 적용\n채널별 독립 스레드 풀
|
|
|
|
== 다중 채널 병렬 배포 (Parallel) ==
|
|
|
|
par 우리동네TV 배포
|
|
alt 채널 선택됨
|
|
Service -> Distributor: distributeToWooridongneTV\n(eventId, contentUrls)
|
|
activate Distributor
|
|
|
|
Distributor -> CB: checkCircuitBreaker("WooridongneTV")
|
|
alt Circuit Breaker OPEN
|
|
CB --> Distributor: 서킷 오픈 상태\n(즉시 실패)
|
|
Distributor -> DB: 배포 채널 로그 저장\n{channel: WooridongneTV, status: FAILED, reason: Circuit Open}
|
|
Distributor --> Service: 실패 (Circuit Open)
|
|
else Circuit Breaker CLOSED 또는 HALF_OPEN
|
|
CB --> Distributor: 요청 허용
|
|
|
|
Distributor -> Retry: executeWithRetry(() -> callWooridongneTV())
|
|
activate Retry
|
|
|
|
loop Retry 최대 3회 (지수 백오프: 1초, 2초, 4초)
|
|
Retry -> WooridongneTV: POST /api/upload-video\n{eventId, videoUrl, region, schedule}
|
|
activate WooridongneTV
|
|
|
|
alt 성공
|
|
WooridongneTV --> Retry: 200 OK\n{distributionId, estimatedViews}
|
|
deactivate WooridongneTV
|
|
Retry --> Distributor: 배포 성공
|
|
Distributor -> CB: recordSuccess("WooridongneTV")
|
|
Distributor -> DB: 배포 채널 로그 저장\n{channel: WooridongneTV, status: SUCCESS, distributionId}
|
|
Distributor --> Service: 성공\n{channel, distributionId, estimatedViews}
|
|
else 실패 (일시적 오류)
|
|
WooridongneTV --> Retry: 500 Internal Server Error
|
|
deactivate WooridongneTV
|
|
note over Retry: 지수 백오프 대기\n(1초 → 2초 → 4초)
|
|
end
|
|
end
|
|
|
|
alt 3회 모두 실패
|
|
Retry --> Distributor: 배포 실패 (Retry 소진)
|
|
deactivate Retry
|
|
Distributor -> CB: recordFailure("WooridongneTV")
|
|
note over CB: 실패율 50% 초과 시\nCircuit Open (30초)
|
|
Distributor -> DB: 배포 채널 로그 저장\n{channel: WooridongneTV, status: FAILED, retries: 3}
|
|
Distributor --> Service: 실패 (Retry 소진)
|
|
end
|
|
end
|
|
deactivate Distributor
|
|
end
|
|
|
|
alt 채널 선택됨
|
|
Service -> Distributor: distributeToRingoBiz\n(eventId, phoneNumber, audioUrl)
|
|
activate Distributor
|
|
|
|
Distributor -> CB: checkCircuitBreaker("RingoBiz")
|
|
alt Circuit Breaker CLOSED 또는 HALF_OPEN
|
|
CB --> Distributor: 요청 허용
|
|
|
|
Distributor -> Retry: executeWithRetry(() -> callRingoBiz())
|
|
activate Retry
|
|
|
|
loop Retry 최대 3회
|
|
Retry -> RingoBiz: POST /api/update-ringtone\n{phoneNumber, audioUrl}
|
|
activate RingoBiz
|
|
|
|
alt 성공
|
|
RingoBiz --> Retry: 200 OK\n{updateTimestamp}
|
|
deactivate RingoBiz
|
|
Retry --> Distributor: 배포 성공
|
|
deactivate Retry
|
|
Distributor -> CB: recordSuccess("RingoBiz")
|
|
Distributor -> DB: 배포 채널 로그 저장\n{channel: RingoBiz, status: SUCCESS}
|
|
Distributor --> Service: 성공\n{channel, updateTimestamp}
|
|
else 실패
|
|
RingoBiz --> Retry: 500 Error
|
|
deactivate RingoBiz
|
|
end
|
|
end
|
|
|
|
alt 3회 모두 실패
|
|
Retry --> Distributor: 배포 실패
|
|
deactivate Retry
|
|
Distributor -> CB: recordFailure("RingoBiz")
|
|
Distributor -> DB: 배포 채널 로그 저장\n{channel: RingoBiz, status: FAILED}
|
|
Distributor --> Service: 실패
|
|
end
|
|
else Circuit Breaker OPEN
|
|
CB --> Distributor: 서킷 오픈 상태
|
|
Distributor -> DB: 배포 채널 로그 저장\n{channel: RingoBiz, status: FAILED, reason: Circuit Open}
|
|
Distributor --> Service: 실패 (Circuit Open)
|
|
end
|
|
deactivate Distributor
|
|
end
|
|
|
|
alt 채널 선택됨
|
|
Service -> Distributor: distributeToGenieTV\n(eventId, region, schedule, budget)
|
|
activate Distributor
|
|
|
|
Distributor -> CB: checkCircuitBreaker("GenieTV")
|
|
alt Circuit Breaker CLOSED 또는 HALF_OPEN
|
|
CB --> Distributor: 요청 허용
|
|
|
|
Distributor -> Retry: executeWithRetry(() -> callGenieTV())
|
|
activate Retry
|
|
|
|
loop Retry 최대 3회
|
|
Retry -> GenieTV: POST /api/register-ad\n{eventId, contentUrl, region, schedule, budget}
|
|
activate GenieTV
|
|
|
|
alt 성공
|
|
GenieTV --> Retry: 200 OK\n{adId, impressionSchedule}
|
|
deactivate GenieTV
|
|
Retry --> Distributor: 배포 성공
|
|
deactivate Retry
|
|
Distributor -> CB: recordSuccess("GenieTV")
|
|
Distributor -> DB: 배포 채널 로그 저장\n{channel: GenieTV, status: SUCCESS, adId}
|
|
Distributor --> Service: 성공\n{channel, adId, schedule}
|
|
else 실패
|
|
GenieTV --> Retry: 500 Error
|
|
deactivate GenieTV
|
|
end
|
|
end
|
|
|
|
alt 3회 모두 실패
|
|
Retry --> Distributor: 배포 실패
|
|
deactivate Retry
|
|
Distributor -> CB: recordFailure("GenieTV")
|
|
Distributor -> DB: 배포 채널 로그 저장\n{channel: GenieTV, status: FAILED}
|
|
Distributor --> Service: 실패
|
|
end
|
|
else Circuit Breaker OPEN
|
|
CB --> Distributor: 서킷 오픈 상태
|
|
Distributor -> DB: 배포 채널 로그 저장\n{channel: GenieTV, status: FAILED, reason: Circuit Open}
|
|
Distributor --> Service: 실패 (Circuit Open)
|
|
end
|
|
deactivate Distributor
|
|
end
|
|
|
|
alt Instagram 선택됨
|
|
Service -> Distributor: distributeToInstagram\n(eventId, imageUrl, caption, hashtags)
|
|
activate Distributor
|
|
|
|
Distributor -> CB: checkCircuitBreaker("Instagram")
|
|
alt Circuit Breaker CLOSED 또는 HALF_OPEN
|
|
CB --> Distributor: 요청 허용
|
|
|
|
Distributor -> Retry: executeWithRetry(() -> callInstagram())
|
|
activate Retry
|
|
|
|
loop Retry 최대 3회
|
|
Retry -> SNS: POST /instagram/api/posts\n{imageUrl, caption, hashtags}
|
|
activate SNS
|
|
|
|
alt 성공
|
|
SNS --> Retry: 200 OK\n{postUrl, postId}
|
|
deactivate SNS
|
|
Retry --> Distributor: 배포 성공
|
|
deactivate Retry
|
|
Distributor -> CB: recordSuccess("Instagram")
|
|
Distributor -> DB: 배포 채널 로그 저장\n{channel: Instagram, status: SUCCESS, postUrl}
|
|
Distributor --> Service: 성공\n{channel, postUrl}
|
|
else 실패
|
|
SNS --> Retry: 500 Error
|
|
deactivate SNS
|
|
end
|
|
end
|
|
|
|
alt 3회 모두 실패
|
|
Retry --> Distributor: 배포 실패
|
|
deactivate Retry
|
|
Distributor -> CB: recordFailure("Instagram")
|
|
Distributor -> DB: 배포 채널 로그 저장\n{channel: Instagram, status: FAILED}
|
|
Distributor --> Service: 실패
|
|
end
|
|
else Circuit Breaker OPEN
|
|
CB --> Distributor: 서킷 오픈 상태
|
|
Distributor -> DB: 배포 채널 로그 저장\n{channel: Instagram, status: FAILED, reason: Circuit Open}
|
|
Distributor --> Service: 실패 (Circuit Open)
|
|
end
|
|
deactivate Distributor
|
|
end
|
|
|
|
alt Naver Blog 선택됨
|
|
Service -> Distributor: distributeToNaverBlog\n(eventId, imageUrl, content)
|
|
activate Distributor
|
|
note over Distributor: Naver Blog 배포 로직\n(Instagram과 동일 패턴)
|
|
Distributor --> Service: 성공 또는 실패
|
|
deactivate Distributor
|
|
end
|
|
|
|
alt Kakao Channel 선택됨
|
|
Service -> Distributor: distributeToKakaoChannel\n(eventId, imageUrl, message)
|
|
activate Distributor
|
|
note over Distributor: Kakao Channel 배포 로직\n(Instagram과 동일 패턴)
|
|
Distributor --> Service: 성공 또는 실패
|
|
deactivate Distributor
|
|
end
|
|
end
|
|
|
|
note over Service: 모든 채널 배포 완료\n(1분 이내)
|
|
|
|
== 배포 결과 집계 및 저장 ==
|
|
Service -> Service: 채널별 배포 결과 집계\n성공: [list], 실패: [list]
|
|
|
|
alt 모든 채널 성공
|
|
Service -> DB: UPDATE distribution_logs\nSET status = 'COMPLETED', completed_at = NOW()
|
|
else 일부 채널 실패
|
|
Service -> DB: UPDATE distribution_logs\nSET status = 'PARTIAL_FAILURE', completed_at = NOW()
|
|
note over Service: 실패한 채널 정보 저장
|
|
else 모든 채널 실패
|
|
Service -> DB: UPDATE distribution_logs\nSET status = 'FAILED', completed_at = NOW()
|
|
end
|
|
|
|
== Kafka 이벤트 발행 ==
|
|
Service -> Kafka: Publish to event-topic\nDistributionCompleted\n{eventId, channels[], results[], completedAt}
|
|
note over Kafka: Analytics Service 구독\n실시간 통계 업데이트
|
|
|
|
Service -> Cache: 배포 상태 캐싱\nkey: distribution:{eventId}\nvalue: {status, results[]}\nTTL: 1시간
|
|
|
|
== REST API 동기 응답 ==
|
|
Service --> Controller: 배포 완료 응답\n{status, successChannels[], failedChannels[]}
|
|
deactivate Service
|
|
|
|
Controller --> API: DistributionResponse\n{eventId, status, results[]}
|
|
deactivate Controller
|
|
|
|
API --> EventSvc: 200 OK\n{distributionId, status, results[]}
|
|
deactivate API
|
|
|
|
note over EventSvc: 배포 완료 응답 수신\n이벤트 상태 업데이트\nAPPROVED → ACTIVE
|
|
|
|
== 배포 실패 처리 (비동기) ==
|
|
note over Service: 실패한 채널은\n- 수동 재시도 가능\n- 알림 발송 (추후 구현)\n- Circuit Open 시 30초 후\n 자동 Half-Open 전환
|
|
|
|
@enduml
|