edit distribution sequence

This commit is contained in:
sunmingLee 2025-10-22 15:54:34 +09:00
parent 8cb0d7bdbe
commit cc2432a86b

View File

@ -1,22 +1,14 @@
@startuml distribution-다중채널배포 @startuml distribution-다중채널배포-sprint2
!theme mono !theme mono
title Distribution Service - 다중 채널 배포 (UFR-DIST-010) title Distribution Service - 다중 채널 배포 Sprint 2 (UFR-DIST-010)
actor Client
participant "Event Service" as EventSvc participant "Event Service" as EventSvc
participant "Distribution\nREST API" as API participant "Distribution\nREST API" as API
participant "Distribution\nController" as Controller participant "Distribution\nController" as Controller
participant "Distribution\nService" as Service 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 database "Event DB" as DB
queue "Kafka" as Kafka 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 participant "Redis Cache" as Cache
== REST API 동기 호출 수신 == == REST API 동기 호출 수신 ==
@ -35,242 +27,118 @@ DB --> Service: 배포 이력 ID
note over Service: 배포 시작 상태로 변경 note over Service: 배포 시작 상태로 변경
Service -> DB: UPDATE distribution_logs\nSET status = 'IN_PROGRESS' Service -> DB: UPDATE distribution_logs\nSET status = 'IN_PROGRESS'
== Circuit Breaker 및 Bulkhead 초기화 == == 다중 채널 배포 로그 기록 (Sprint 2: Mock 처리) ==
Service -> CB: 채널별 Circuit Breaker 상태 확인
CB --> Service: 모든 Circuit Breaker 상태\n(CLOSED/OPEN/HALF_OPEN)
note over Service: Bulkhead 패턴 적용\n채널별 독립 스레드 풀 note over Service: Sprint 2: 실제 외부 API 호출 없이\n배포 결과만 기록
== 다중 채널 병렬 배포 (Parallel) ==
par 우리동네TV 배포 par 우리동네TV 배포
alt 채널 선택됨 alt 채널 선택됨
Service -> Distributor: distributeToWooridongneTV\n(eventId, contentUrls) Service -> Service: 우리동네TV 배포 처리\n(Mock: 즉시 성공 반환)
activate Distributor activate Service
Distributor -> CB: checkCircuitBreaker("WooridongneTV") note over Service: 배포 요청 검증\n- eventId 유효성\n- contentUrls 존재 여부
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()) Service -> DB: 배포 채널 로그 저장\nINSERT distribution_channel_logs\n{channel: WooridongneTV,\nstatus: SUCCESS,\ndistributionId: generated_uuid,\nestimatedViews: 1000}
activate Retry
loop Retry 최대 3회 (지수 백오프: 1초, 2초, 4초) note over Service: Mock 결과:\n성공 (distributionId 생성)
Retry -> WooridongneTV: POST /api/upload-video\n{eventId, videoUrl, region, schedule}
activate WooridongneTV
alt 성공 deactivate Service
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 end
alt 채널 선택됨 alt 링고비즈 선택됨
Service -> Distributor: distributeToRingoBiz\n(eventId, phoneNumber, audioUrl) Service -> Service: 링고비즈 배포 처리\n(Mock: 즉시 성공 반환)
activate Distributor activate Service
Distributor -> CB: checkCircuitBreaker("RingoBiz") note over Service: 배포 요청 검증\n- phoneNumber 형식\n- audioUrl 존재 여부
alt Circuit Breaker CLOSED 또는 HALF_OPEN
CB --> Distributor: 요청 허용
Distributor -> Retry: executeWithRetry(() -> callRingoBiz()) Service -> DB: 배포 채널 로그 저장\nINSERT distribution_channel_logs\n{channel: RingoBiz,\nstatus: SUCCESS,\nupdateTimestamp: NOW()}
activate Retry
loop Retry 최대 3회 note over Service: Mock 결과:\n성공 (timestamp 기록)
Retry -> RingoBiz: POST /api/update-ringtone\n{phoneNumber, audioUrl}
activate RingoBiz
alt 성공 deactivate Service
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 end
alt 채널 선택됨 alt 지니TV 선택됨
Service -> Distributor: distributeToGenieTV\n(eventId, region, schedule, budget) Service -> Service: 지니TV 배포 처리\n(Mock: 즉시 성공 반환)
activate Distributor activate Service
Distributor -> CB: checkCircuitBreaker("GenieTV") note over Service: 배포 요청 검증\n- region 유효성\n- schedule 형식\n- budget 범위
alt Circuit Breaker CLOSED 또는 HALF_OPEN
CB --> Distributor: 요청 허용
Distributor -> Retry: executeWithRetry(() -> callGenieTV()) Service -> DB: 배포 채널 로그 저장\nINSERT distribution_channel_logs\n{channel: GenieTV,\nstatus: SUCCESS,\nadId: generated_uuid,\nimpressionSchedule: calculated}
activate Retry
loop Retry 최대 3회 note over Service: Mock 결과:\n성공 (adId 생성)
Retry -> GenieTV: POST /api/register-ad\n{eventId, contentUrl, region, schedule, budget}
activate GenieTV
alt 성공 deactivate Service
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 end
alt Instagram 선택됨 alt Instagram 선택됨
Service -> Distributor: distributeToInstagram\n(eventId, imageUrl, caption, hashtags) Service -> Service: Instagram 배포 처리\n(Mock: 즉시 성공 반환)
activate Distributor activate Service
Distributor -> CB: checkCircuitBreaker("Instagram") note over Service: 배포 요청 검증\n- imageUrl 형식\n- caption 길이\n- hashtags 유효성
alt Circuit Breaker CLOSED 또는 HALF_OPEN
CB --> Distributor: 요청 허용
Distributor -> Retry: executeWithRetry(() -> callInstagram()) Service -> DB: 배포 채널 로그 저장\nINSERT distribution_channel_logs\n{channel: Instagram,\nstatus: SUCCESS,\npostUrl: generated_url,\npostId: generated_uuid}
activate Retry
loop Retry 최대 3회 note over Service: Mock 결과:\n성공 (postUrl, postId 생성)
Retry -> SNS: POST /instagram/api/posts\n{imageUrl, caption, hashtags}
activate SNS
alt 성공 deactivate Service
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 end
alt Naver Blog 선택됨 alt Naver Blog 선택됨
Service -> Distributor: distributeToNaverBlog\n(eventId, imageUrl, content) Service -> Service: Naver Blog 배포 처리\n(Mock: 즉시 성공 반환)
activate Distributor activate Service
note over Distributor: Naver Blog 배포 로직\n(Instagram과 동일 패턴)
Distributor --> Service: 성공 또는 실패 note over Service: 배포 요청 검증\n- imageUrl 형식\n- content 길이
deactivate Distributor
Service -> DB: 배포 채널 로그 저장\nINSERT distribution_channel_logs\n{channel: NaverBlog,\nstatus: SUCCESS,\npostUrl: generated_url}
note over Service: Mock 결과:\n성공 (postUrl 생성)
deactivate Service
end end
alt Kakao Channel 선택됨 alt Kakao Channel 선택됨
Service -> Distributor: distributeToKakaoChannel\n(eventId, imageUrl, message) Service -> Service: Kakao Channel 배포 처리\n(Mock: 즉시 성공 반환)
activate Distributor activate Service
note over Distributor: Kakao Channel 배포 로직\n(Instagram과 동일 패턴)
Distributor --> Service: 성공 또는 실패 note over Service: 배포 요청 검증\n- imageUrl 형식\n- message 길이
deactivate Distributor
Service -> DB: 배포 채널 로그 저장\nINSERT distribution_channel_logs\n{channel: KakaoChannel,\nstatus: SUCCESS,\nmessageId: generated_uuid}
note over Service: Mock 결과:\n성공 (messageId 생성)
deactivate Service
end end
end end
note over Service: 모든 채널 배포 완료\n(1분 이내) note over Service: 모든 채널 배포 완료\n(즉시 처리 - 외부 API 호출 없음)
== 배포 결과 집계 및 저장 == == 배포 결과 집계 및 저장 ==
Service -> Service: 채널별 배포 결과 집계\n성공: [list], 실패: [list] Service -> Service: 채널별 배포 결과 집계\n성공: [선택된 모든 채널]
alt 모든 채널 성공 note over Service: Sprint 2에서는\n모든 채널 배포가 성공으로 처리됨
Service -> DB: UPDATE distribution_logs\nSET status = 'COMPLETED', completed_at = NOW()
else 일부 채널 실패 Service -> DB: UPDATE distribution_logs\nSET status = 'COMPLETED',\ncompleted_at = NOW()
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 이벤트 발행 == == Kafka 이벤트 발행 ==
Service -> Kafka: Publish to event-topic\nDistributionCompleted\n{eventId, channels[], results[], completedAt} Service -> Kafka: Publish to event-topic\nDistributionCompleted\n{eventId, channels[], results[], completedAt}
note over Kafka: Analytics Service 구독\n실시간 통계 업데이트 note over Kafka: Analytics Service 구독\n실시간 통계 업데이트
Service -> Cache: 배포 상태 캐싱\nkey: distribution:{eventId}\nvalue: {status, results[]}\nTTL: 1시간 Service -> Cache: 배포 상태 캐싱\nkey: distribution:{eventId}\nvalue: {status: COMPLETED, results[]}\nTTL: 1시간
== REST API 동기 응답 == == REST API 동기 응답 ==
Service --> Controller: 배포 완료 응답\n{status, successChannels[], failedChannels[]} Service --> Controller: 배포 완료 응답\n{status: COMPLETED, successChannels: [all]}
deactivate Service deactivate Service
Controller --> API: DistributionResponse\n{eventId, status, results[]} Controller --> API: DistributionResponse\n{eventId, status: COMPLETED, results: [all success]}
deactivate Controller deactivate Controller
API --> EventSvc: 200 OK\n{distributionId, status, results[]} API --> EventSvc: 200 OK\n{distributionId, status: COMPLETED, results[]}
deactivate API deactivate API
note over EventSvc: 배포 완료 응답 수신\n이벤트 상태 업데이트\nAPPROVED → ACTIVE note over EventSvc: 배포 완료 응답 수신\n이벤트 상태 업데이트\nAPPROVED → ACTIVE
== 배포 실패 처리 (비동기) == == Sprint 2 제약사항 ==
note over Service: 실패한 채널은\n- 수동 재시도 가능\n- 알림 발송 (추후 구현)\n- Circuit Open 시 30초 후\n 자동 Half-Open 전환 note over Service: **Sprint 2 구현 범위**\n- 외부 API 호출 없음 (Mock 처리)\n- 모든 배포 요청은 성공으로 처리\n- 배포 로그만 DB에 기록\n- Circuit Breaker, Retry 미구현\n- 실패 처리 시나리오 미구현\n\n**Sprint 3 이후 구현 예정**\n- 실제 외부 채널 API 연동\n- Circuit Breaker 패턴 적용\n- Retry 로직 구현\n- 실패 처리 및 알림
@enduml @enduml