From e321eacde42fbf6e360b6eac01ee6b2bc1797be7 Mon Sep 17 00:00:00 2001 From: doyeon Date: Thu, 23 Oct 2025 10:01:48 +0900 Subject: [PATCH] =?UTF-8?q?=EC=8B=9C=ED=80=80=EC=8A=A4=20=EB=8B=A4?= =?UTF-8?q?=EC=9D=B4=EC=96=B4=EA=B7=B8=EB=9E=A8=20=EC=88=98=EC=A0=95:=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=83=9D=EC=84=B1=20=ED=9B=84=20?= =?UTF-8?q?DB=20=EC=A0=80=EC=9E=A5=20=ED=94=84=EB=A1=9C=EC=84=B8=EC=8A=A4?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 다중채널배포 outer sequence 수정 - inner sequence 참조 명시 (distribution-다중채널배포.puml) - Sprint 2 Mock 처리 반영 - API 엔드포인트 일관성 유지 2. 이미지 생성 프로세스 개선 - Content Service: 이미지 생성 후 Kafka 이벤트 발행 추가 - Event Service: 새로운 Kafka Consumer 추가 (event-콘텐츠생성완료구독.puml) - Event DB에 이미지 URL 영구 저장 - Redis 캐시와 DB 간 데이터 정합성 보장 3. 아키텍처 개선 - 서비스 독립성 향상 (Kafka 기반 이벤트 통신) - 느슨한 결합 구조 - 데이터 흐름 명확화 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../sequence/inner/content-이미지생성.puml | 15 ++-- .../inner/event-콘텐츠생성완료구독.puml | 70 +++++++++++++++++++ .../sequence/outer/이벤트생성플로우.puml | 65 ++++++++++------- 3 files changed, 121 insertions(+), 29 deletions(-) create mode 100644 design/backend/sequence/inner/event-콘텐츠생성완료구독.puml diff --git a/design/backend/sequence/inner/content-이미지생성.puml b/design/backend/sequence/inner/content-이미지생성.puml index 60bc5a7..ce39e02 100644 --- a/design/backend/sequence/inner/content-이미지생성.puml +++ b/design/backend/sequence/inner/content-이미지생성.puml @@ -202,11 +202,18 @@ else 캐시 MISS (새로운 이미지 생성) JobStatus --> Handler: 업데이트 완료 deactivate JobStatus - note over Handler - 폴링 방식으로 결과 조회 - - Event Service는 이미지결과조회 API로 확인 - - Kafka 이벤트 발행 없음 (Consumer 없음) + == Kafka 이벤트 발행 (이미지 생성 완료) == + note over Handler: Kafka 이벤트 발행하여\nEvent Service에 결과 전달 + + Handler -> Consumer: Kafka 이벤트 발행 + activate Consumer + Consumer -> Consumer: Kafka Producer로\nevent-topic 발행\nContentCreated\n{jobId, eventDraftId, imageUrls} + note right + Event Service는 Kafka Consumer로 + ContentCreated 이벤트를 구독하여 + Event DB에 이미지 URL 저장 end note + deactivate Consumer Handler --> Consumer: 처리 완료 end diff --git a/design/backend/sequence/inner/event-콘텐츠생성완료구독.puml b/design/backend/sequence/inner/event-콘텐츠생성완료구독.puml new file mode 100644 index 0000000..ac6835d --- /dev/null +++ b/design/backend/sequence/inner/event-콘텐츠생성완료구독.puml @@ -0,0 +1,70 @@ +@startuml event-콘텐츠생성완료구독 +!theme mono + +title Event Service - 콘텐츠 생성 완료 이벤트 구독 (Kafka Consumer) + +participant "Kafka\nevent-topic\nConsumer" as Consumer +participant "EventHandler" as Handler +participant "EventRepository" as Repo +database "Event DB" as DB +participant "Redis Cache" as Cache + +note over Consumer: Kafka 구독\nevent-topic\n이벤트 타입: ContentCreated + +== ContentCreated 이벤트 수신 == +Consumer -> Handler: ContentCreated 이벤트 수신\n{jobId, eventDraftId, imageUrls} +activate Handler + +Handler -> Handler: 이벤트 검증\n- eventDraftId 유효성\n- imageUrls 존재 여부 +note right: 3가지 스타일 확인\n(simple, fancy, trendy) + +== Event DB에 이미지 URL 저장 == +Handler -> Repo: updateContentImages(eventDraftId, imageUrls) +activate Repo + +Repo -> DB: 이벤트 초안 업데이트\nUPDATE event_drafts\nSET content_images = ?,\n updated_at = NOW()\nWHERE event_draft_id = ? +activate DB + +note over DB + 저장 데이터: + { + "simple": "https://cdn.../simple.png", + "fancy": "https://cdn.../fancy.png", + "trendy": "https://cdn.../trendy.png" + } +end note + +DB --> Repo: 업데이트 완료 +deactivate DB + +Repo --> Handler: 저장 완료 +deactivate Repo + +== 캐시 무효화 == +Handler -> Cache: 이벤트 초안 캐시 무효화\nDEL event:draft:{eventDraftId} +activate Cache +Cache --> Handler: 삭제 완료 +deactivate Cache + +note over Handler: 캐시 무효화로\n다음 조회 시 최신 데이터 반영 + +Handler --> Consumer: 처리 완료 +deactivate Handler + +note over Consumer, DB +**처리 전략** +- At-Least-Once 전달 보장 +- 멱등성 처리 (중복 이벤트 무시) +- 실패 시 자동 재시도 (최대 3회) +- Dead Letter Queue로 실패 이벤트 이동 + +**저장 위치** +- Event DB: 영구 저장 (이벤트 초안 테이블) +- Redis Cache: 임시 저장 (7일 TTL) + +**데이터 정합성** +- DB 저장 후 캐시 무효화 +- 다음 조회 시 DB에서 최신 데이터 로드 +end note + +@enduml diff --git a/design/backend/sequence/outer/이벤트생성플로우.puml b/design/backend/sequence/outer/이벤트생성플로우.puml index fd2bed0..9bd9e58 100644 --- a/design/backend/sequence/outer/이벤트생성플로우.puml +++ b/design/backend/sequence/outer/이벤트생성플로우.puml @@ -87,6 +87,8 @@ FE --> User: "이미지 생성 중..." (로딩) note over Content: Kafka Consumer\n이미지 생성 topic 구독 Kafka --> Content: Consume Job Message\n{jobId, eventDraftId, ...} +note over Content: inner sequence 참조:\ncontent-이미지생성.puml + par 심플 스타일 Content -> ImageApi: 심플 스타일 생성 요청 ImageApi --> Content: 심플 이미지 URL @@ -98,17 +100,22 @@ else 트렌디 스타일 ImageApi --> Content: 트렌디 이미지 URL end -Content -> EventDB: 생성된 이미지 URL 저장\n(3가지 스타일 이미지 URL 저장) -EventDB --> Content: 저장 완료 -Content -> EventDB: Job 상태 업데이트\n(상태를 COMPLETED로 변경) +note over Content: Redis 캐시에 이미지 URL 저장\nJob 상태를 COMPLETED로 업데이트 + Content -> Kafka: Publish to event-topic\nContentCreated\n{jobId, eventDraftId, imageUrls} +note over Event: Kafka Consumer 구독\ninner sequence 참조:\nevent-콘텐츠생성완료구독.puml +Kafka --> Event: ContentCreated 이벤트 수신 +Event -> EventDB: 이미지 URL 저장\n(이벤트 초안 업데이트) +EventDB --> Event: 저장 완료 + group Polling으로 상태 확인 loop 상태 확인 (최대 30초) FE -> Gateway: GET /jobs/{jobId}/status Gateway -> Event: Job 상태 조회 - Event -> EventDB: Job 상태 조회\n(jobId로 상태 및 이미지 URL 조회) - EventDB --> Event: {status, imageUrls} + note over Event: Redis 캐시 조회\n(jobId로 상태 확인) + Event -> Event: Redis에서 Job 상태 조회 + Event --> Event: {status, imageUrls} alt Job 완료 Event --> Gateway: 200 OK\n{status: COMPLETED, imageUrls} @@ -139,49 +146,57 @@ Event -> EventDB: 이벤트 상태 변경\n(DRAFT → APPROVED로 업데이트) EventDB --> Event: 상태 변경 완료 Event -> Kafka: Publish to event-topic\nEventCreated\n{eventId, 이벤트정보} -note over Event: 동기 호출로 배포 진행 -Event -> Dist: REST API - 배포 요청\nPOST /distributions\n{eventId, channels, content} +note over Event: 동기 호출로 배포 진행\ninner sequence 참조:\ndistribution-다중채널배포.puml +Event -> Dist: REST API - 배포 요청\nPOST /api/distribution/distribute\n{eventId, channels[], contentUrls} -note over Dist: Circuit Breaker 적용 +note over Dist: Sprint 2: Mock 처리\n- 외부 API 호출 없음\n- 모든 배포 즉시 성공 처리\n- 배포 로그만 DB 기록 + +Dist -> EventDB: 배포 이력 초기화\n(이벤트ID, 상태: PENDING) +EventDB --> Dist: 배포 이력 ID +Dist -> EventDB: 배포 이력 상태 업데이트\n(상태: IN_PROGRESS) + +note over Dist: 다중 채널 Mock 배포\n(내부 처리 상세는 inner sequence 참조) par 우리동네TV alt 우리동네TV 선택 - Dist -> ChannelApis: 우리동네TV API\n15초 영상 업로드 - ChannelApis --> Dist: 배포 완료\n{배포ID, 예상노출수} + Dist -> Dist: Mock 처리\n(즉시 성공) + Dist -> EventDB: 채널 로그 저장\n(우리동네TV, 성공,\n배포ID, 예상노출수) end else 링고비즈 alt 링고비즈 선택 - Dist -> ChannelApis: 링고비즈 API\n연결음 업데이트 - ChannelApis --> Dist: 업데이트 완료\n{완료시각} + Dist -> Dist: Mock 처리\n(즉시 성공) + Dist -> EventDB: 채널 로그 저장\n(링고비즈, 성공,\n업데이트 시각) end else 지니TV alt 지니TV 선택 - Dist -> ChannelApis: 지니TV API\n광고 등록 - ChannelApis --> Dist: 광고 등록 완료\n{광고ID, 스케줄} + Dist -> Dist: Mock 처리\n(즉시 성공) + Dist -> EventDB: 채널 로그 저장\n(지니TV, 성공,\n광고ID, 스케줄) end else Instagram alt Instagram 선택 - Dist -> ChannelApis: Instagram API\n포스팅 - ChannelApis --> Dist: 포스팅 완료\n{postUrl} + Dist -> Dist: Mock 처리\n(즉시 성공) + Dist -> EventDB: 채널 로그 저장\n(Instagram, 성공,\npostUrl, postId) end else Naver Blog alt Naver Blog 선택 - Dist -> ChannelApis: Naver API\n블로그 포스팅 - ChannelApis --> Dist: 포스팅 완료\n{postUrl} + Dist -> Dist: Mock 처리\n(즉시 성공) + Dist -> EventDB: 채널 로그 저장\n(NaverBlog, 성공,\npostUrl) end else Kakao Channel alt Kakao Channel 선택 - Dist -> ChannelApis: Kakao API\n채널 포스팅 - ChannelApis --> Dist: 포스팅 완료\n{postUrl} + Dist -> Dist: Mock 처리\n(즉시 성공) + Dist -> EventDB: 채널 로그 저장\n(KakaoChannel, 성공,\nmessageId) end end -Dist -> EventDB: 배포 이력 저장\n(채널별 배포 결과 저장) -EventDB --> Dist: 저장 완료 +note over Dist: 모든 채널 배포 완료 (즉시 처리) -Dist -> Kafka: Publish to event-topic\nDistributionCompleted\n{eventId, 배포결과} +Dist -> EventDB: 배포 이력 상태 업데이트\n(상태: COMPLETED, 완료일시) + +Dist -> Kafka: Publish to event-topic\nDistributionCompleted\n{eventId, channels[], results[], completedAt} + +Dist --> Event: REST API 동기 응답\n200 OK\n{distributionId, status: COMPLETED, results[]} -Dist --> Event: REST API 응답\n200 OK\n{배포결과, 채널별 상태} Event -> EventDB: 이벤트 상태 업데이트\n(APPROVED → ACTIVE로 변경) EventDB --> Event: 업데이트 완료 @@ -189,6 +204,6 @@ Event --> Gateway: 200 OK\n{eventId, 배포결과} Gateway --> FE: 배포 완료 FE --> User: "이벤트가 배포되었습니다"\n대시보드로 이동 -note over Event, Dist: 배포 실패 시\n- 채널별 독립 처리\n- 자동 재시도 (최대 3회)\n- Circuit Breaker로 장애 전파 방지\n- 실패한 채널만 재시도 가능 +note over Event, Dist: Sprint 2 제약사항\n- 외부 API 호출 없음 (Mock)\n- 모든 배포 즉시 성공 처리\n- Circuit Breaker 미구현\n- Retry 로직 미구현\n\nSprint 3 이후 구현 예정\n- 실제 외부 채널 API 연동\n- Circuit Breaker 패턴\n- Retry 및 실패 처리 @enduml