From cc2432a86b12a37fe2401222553e43c2954aeebe Mon Sep 17 00:00:00 2001 From: sunmingLee <25thbam@gmail.com> Date: Wed, 22 Oct 2025 15:54:34 +0900 Subject: [PATCH] edit distribution sequence --- .../inner/distribution-다중채널배포.puml | 254 +++++------------- 1 file changed, 61 insertions(+), 193 deletions(-) diff --git a/design/backend/sequence/inner/distribution-다중채널배포.puml b/design/backend/sequence/inner/distribution-다중채널배포.puml index ad90445..06d7345 100644 --- a/design/backend/sequence/inner/distribution-다중채널배포.puml +++ b/design/backend/sequence/inner/distribution-다중채널배포.puml @@ -1,22 +1,14 @@ -@startuml distribution-다중채널배포 +@startuml distribution-다중채널배포-sprint2 !theme mono -title Distribution Service - 다중 채널 배포 (UFR-DIST-010) +title Distribution Service - 다중 채널 배포 Sprint 2 (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 동기 호출 수신 == @@ -35,242 +27,118 @@ 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) +== 다중 채널 배포 로그 기록 (Sprint 2: Mock 처리) == -note over Service: Bulkhead 패턴 적용\n채널별 독립 스레드 풀 - -== 다중 채널 병렬 배포 (Parallel) == +note over Service: Sprint 2: 실제 외부 API 호출 없이\n배포 결과만 기록 par 우리동네TV 배포 alt 채널 선택됨 - Service -> Distributor: distributeToWooridongneTV\n(eventId, contentUrls) - activate Distributor + Service -> Service: 우리동네TV 배포 처리\n(Mock: 즉시 성공 반환) + activate Service - 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: 요청 허용 + note over Service: 배포 요청 검증\n- eventId 유효성\n- contentUrls 존재 여부 - Distributor -> Retry: executeWithRetry(() -> callWooridongneTV()) - activate Retry + Service -> DB: 배포 채널 로그 저장\nINSERT distribution_channel_logs\n{channel: WooridongneTV,\nstatus: SUCCESS,\ndistributionId: generated_uuid,\nestimatedViews: 1000} - loop Retry 최대 3회 (지수 백오프: 1초, 2초, 4초) - Retry -> WooridongneTV: POST /api/upload-video\n{eventId, videoUrl, region, schedule} - activate WooridongneTV + note over Service: Mock 결과:\n성공 (distributionId 생성) - 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 + deactivate Service end - alt 채널 선택됨 - Service -> Distributor: distributeToRingoBiz\n(eventId, phoneNumber, audioUrl) - activate Distributor + alt 링고비즈 선택됨 + Service -> Service: 링고비즈 배포 처리\n(Mock: 즉시 성공 반환) + activate Service - Distributor -> CB: checkCircuitBreaker("RingoBiz") - alt Circuit Breaker CLOSED 또는 HALF_OPEN - CB --> Distributor: 요청 허용 + note over Service: 배포 요청 검증\n- phoneNumber 형식\n- audioUrl 존재 여부 - Distributor -> Retry: executeWithRetry(() -> callRingoBiz()) - activate Retry + Service -> DB: 배포 채널 로그 저장\nINSERT distribution_channel_logs\n{channel: RingoBiz,\nstatus: SUCCESS,\nupdateTimestamp: NOW()} - loop Retry 최대 3회 - Retry -> RingoBiz: POST /api/update-ringtone\n{phoneNumber, audioUrl} - activate RingoBiz + note over Service: Mock 결과:\n성공 (timestamp 기록) - 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 + deactivate Service end - alt 채널 선택됨 - Service -> Distributor: distributeToGenieTV\n(eventId, region, schedule, budget) - activate Distributor + alt 지니TV 선택됨 + Service -> Service: 지니TV 배포 처리\n(Mock: 즉시 성공 반환) + activate Service - Distributor -> CB: checkCircuitBreaker("GenieTV") - alt Circuit Breaker CLOSED 또는 HALF_OPEN - CB --> Distributor: 요청 허용 + note over Service: 배포 요청 검증\n- region 유효성\n- schedule 형식\n- budget 범위 - Distributor -> Retry: executeWithRetry(() -> callGenieTV()) - activate Retry + Service -> DB: 배포 채널 로그 저장\nINSERT distribution_channel_logs\n{channel: GenieTV,\nstatus: SUCCESS,\nadId: generated_uuid,\nimpressionSchedule: calculated} - loop Retry 최대 3회 - Retry -> GenieTV: POST /api/register-ad\n{eventId, contentUrl, region, schedule, budget} - activate GenieTV + note over Service: Mock 결과:\n성공 (adId 생성) - 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 + deactivate Service end alt Instagram 선택됨 - Service -> Distributor: distributeToInstagram\n(eventId, imageUrl, caption, hashtags) - activate Distributor + Service -> Service: Instagram 배포 처리\n(Mock: 즉시 성공 반환) + activate Service - Distributor -> CB: checkCircuitBreaker("Instagram") - alt Circuit Breaker CLOSED 또는 HALF_OPEN - CB --> Distributor: 요청 허용 + note over Service: 배포 요청 검증\n- imageUrl 형식\n- caption 길이\n- hashtags 유효성 - Distributor -> Retry: executeWithRetry(() -> callInstagram()) - activate Retry + Service -> DB: 배포 채널 로그 저장\nINSERT distribution_channel_logs\n{channel: Instagram,\nstatus: SUCCESS,\npostUrl: generated_url,\npostId: generated_uuid} - loop Retry 최대 3회 - Retry -> SNS: POST /instagram/api/posts\n{imageUrl, caption, hashtags} - activate SNS + note over Service: Mock 결과:\n성공 (postUrl, postId 생성) - 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 + deactivate Service end alt Naver Blog 선택됨 - Service -> Distributor: distributeToNaverBlog\n(eventId, imageUrl, content) - activate Distributor - note over Distributor: Naver Blog 배포 로직\n(Instagram과 동일 패턴) - Distributor --> Service: 성공 또는 실패 - deactivate Distributor + Service -> Service: Naver Blog 배포 처리\n(Mock: 즉시 성공 반환) + activate Service + + note over Service: 배포 요청 검증\n- imageUrl 형식\n- content 길이 + + Service -> DB: 배포 채널 로그 저장\nINSERT distribution_channel_logs\n{channel: NaverBlog,\nstatus: SUCCESS,\npostUrl: generated_url} + + note over Service: Mock 결과:\n성공 (postUrl 생성) + + deactivate Service end alt Kakao Channel 선택됨 - Service -> Distributor: distributeToKakaoChannel\n(eventId, imageUrl, message) - activate Distributor - note over Distributor: Kakao Channel 배포 로직\n(Instagram과 동일 패턴) - Distributor --> Service: 성공 또는 실패 - deactivate Distributor + Service -> Service: Kakao Channel 배포 처리\n(Mock: 즉시 성공 반환) + activate Service + + note over Service: 배포 요청 검증\n- imageUrl 형식\n- message 길이 + + Service -> DB: 배포 채널 로그 저장\nINSERT distribution_channel_logs\n{channel: KakaoChannel,\nstatus: SUCCESS,\nmessageId: generated_uuid} + + note over Service: Mock 결과:\n성공 (messageId 생성) + + deactivate Service end end -note over Service: 모든 채널 배포 완료\n(1분 이내) +note over Service: 모든 채널 배포 완료\n(즉시 처리 - 외부 API 호출 없음) == 배포 결과 집계 및 저장 == -Service -> Service: 채널별 배포 결과 집계\n성공: [list], 실패: [list] +Service -> Service: 채널별 배포 결과 집계\n성공: [선택된 모든 채널] -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 +note over Service: Sprint 2에서는\n모든 채널 배포가 성공으로 처리됨 + +Service -> DB: UPDATE distribution_logs\nSET status = 'COMPLETED',\ncompleted_at = NOW() == 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시간 +Service -> Cache: 배포 상태 캐싱\nkey: distribution:{eventId}\nvalue: {status: COMPLETED, results[]}\nTTL: 1시간 == REST API 동기 응답 == -Service --> Controller: 배포 완료 응답\n{status, successChannels[], failedChannels[]} +Service --> Controller: 배포 완료 응답\n{status: COMPLETED, successChannels: [all]} deactivate Service -Controller --> API: DistributionResponse\n{eventId, status, results[]} +Controller --> API: DistributionResponse\n{eventId, status: COMPLETED, results: [all success]} deactivate Controller -API --> EventSvc: 200 OK\n{distributionId, status, results[]} +API --> EventSvc: 200 OK\n{distributionId, status: COMPLETED, results[]} deactivate API note over EventSvc: 배포 완료 응답 수신\n이벤트 상태 업데이트\nAPPROVED → ACTIVE -== 배포 실패 처리 (비동기) == -note over Service: 실패한 채널은\n- 수동 재시도 가능\n- 알림 발송 (추후 구현)\n- Circuit Open 시 30초 후\n 자동 Half-Open 전환 +== Sprint 2 제약사항 == +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