From b96613c0679f2de48c7c3773759ab27882b67bfa Mon Sep 17 00:00:00 2001 From: merrycoral Date: Wed, 22 Oct 2025 14:56:03 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=EC=99=B8=EB=B6=80/=EB=82=B4=EB=B6=80=20?= =?UTF-8?q?=EC=8B=9C=ED=80=80=EC=8A=A4=20=EC=84=A4=EA=B3=84=20=EC=9D=BC?= =?UTF-8?q?=EA=B4=80=EC=84=B1=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - API 엔드포인트 통일 - AI 추천: POST /api/events/{id}/ai-recommendations - 이미지 생성: POST /api/events/{id}/content-generation - 최종 승인: POST /api/events/{id}/publish - Kafka 이벤트명 구분 - EventDraftCreated: 목적 선택 시 발행 - EventCreated: 최종 승인 시 발행 - 수정 파일 - design/backend/sequence/outer/이벤트생성플로우.puml - design/backend/sequence/inner/event-목적선택.puml 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- design/backend/sequence/inner/event-목적선택.puml | 6 +++--- design/backend/sequence/outer/이벤트생성플로우.puml | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/design/backend/sequence/inner/event-목적선택.puml b/design/backend/sequence/inner/event-목적선택.puml index f3efef1..756032d 100644 --- a/design/backend/sequence/inner/event-목적선택.puml +++ b/design/backend/sequence/inner/event-목적선택.puml @@ -36,9 +36,9 @@ activate Cache Cache --> Service: OK deactivate Cache -Service -> Kafka: publish(EventCreated,\n{eventDraftId, userId, objective, createdAt}) +Service -> Kafka: publish(EventDraftCreated,\n{eventDraftId, userId, objective, createdAt}) 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 deactivate Kafka @@ -46,6 +46,6 @@ Service --> Controller: EventDraftResponse\n{eventDraftId, objective, status} deactivate Service 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 diff --git a/design/backend/sequence/outer/이벤트생성플로우.puml b/design/backend/sequence/outer/이벤트생성플로우.puml index 3aa500d..35e415f 100644 --- a/design/backend/sequence/outer/이벤트생성플로우.puml +++ b/design/backend/sequence/outer/이벤트생성플로우.puml @@ -35,7 +35,7 @@ FE --> User: AI 추천 화면으로 이동 == 2. AI 이벤트 추천 - 비동기 처리 (UFR-EVENT-030) == User -> FE: AI 추천 요청 -FE -> Gateway: POST /events/recommendations\n{eventDraftId, 목적, 업종, 지역} +FE -> Gateway: POST /api/events/{eventDraftId}/ai-recommendations\n{목적, 업종, 지역} Gateway -> Event: AI 추천 요청 전달 Event -> Kafka: Publish to ai-job-topic\n{jobId, eventDraftId, 목적, 업종, 지역} Event --> Gateway: Job 생성 완료\n{jobId, status: PENDING} @@ -93,7 +93,7 @@ FE --> User: 콘텐츠 생성 화면으로 이동 == 3. SNS 이미지 생성 - 비동기 처리 (UFR-CONT-010) == User -> FE: 이미지 생성 요청 -FE -> Gateway: POST /contents/images\n{eventDraftId, 이벤트정보} +FE -> Gateway: POST /api/events/{eventDraftId}/content-generation\n{이벤트정보} Gateway -> Event: 이미지 생성 요청 Event -> Kafka: Publish to image-job-topic\n{jobId, eventDraftId, 이벤트정보} Event --> Gateway: Job 생성 완료\n{jobId, status: PENDING} @@ -149,8 +149,8 @@ FE --> User: 배포 채널 선택 화면으로 이동 == 4. 최종 승인 및 다중 채널 배포 - 동기 처리 (UFR-EVENT-050) == User -> FE: 배포 채널 선택\n최종 승인 요청 -FE -> Gateway: POST /events/{eventDraftId}/approve\n{선택 채널 목록, 승인} -Gateway -> Event: 최종 승인 처리 +FE -> Gateway: POST /api/events/{eventDraftId}/publish\n{선택 채널 목록} +Gateway -> Event: 최종 승인 및 배포 처리 Event -> EventDB: 이벤트 상태 변경\nDRAFT → APPROVED EventDB --> Event: 상태 변경 완료 Event -> Kafka: Publish to event-topic\nEventCreated\n{eventId, 이벤트정보} From 841d6ef1b78653975ef79da7f3e1478cafdfa8a5 Mon Sep 17 00:00:00 2001 From: Hyowon Yang Date: Wed, 22 Oct 2025 14:56:54 +0900 Subject: [PATCH 2/5] =?UTF-8?q?analytics-=EB=8C=80=EC=8B=9C=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=EC=BA=90=EC=8B=9C=ED=9E=88=ED=8A=B8,=EB=AF=B8?= =?UTF-8?q?=EC=8A=A4=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../analytics-대시보드조회-캐시미스.puml | 238 -------------- .../analytics-대시보드조회-캐시히트.puml | 72 ----- .../inner/analytics-대시보드조회.puml | 305 ++++++++++++++++++ 3 files changed, 305 insertions(+), 310 deletions(-) delete mode 100644 design/backend/sequence/inner/analytics-대시보드조회-캐시미스.puml delete mode 100644 design/backend/sequence/inner/analytics-대시보드조회-캐시히트.puml create mode 100644 design/backend/sequence/inner/analytics-대시보드조회.puml diff --git a/design/backend/sequence/inner/analytics-대시보드조회-캐시미스.puml b/design/backend/sequence/inner/analytics-대시보드조회-캐시미스.puml deleted file mode 100644 index 6b5cbff..0000000 --- a/design/backend/sequence/inner/analytics-대시보드조회-캐시미스.puml +++ /dev/null @@ -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 diff --git a/design/backend/sequence/inner/analytics-대시보드조회-캐시히트.puml b/design/backend/sequence/inner/analytics-대시보드조회-캐시히트.puml deleted file mode 100644 index f04b524..0000000 --- a/design/backend/sequence/inner/analytics-대시보드조회-캐시히트.puml +++ /dev/null @@ -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 diff --git a/design/backend/sequence/inner/analytics-대시보드조회.puml b/design/backend/sequence/inner/analytics-대시보드조회.puml new file mode 100644 index 0000000..95a7064 --- /dev/null +++ b/design/backend/sequence/inner/analytics-대시보드조회.puml @@ -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<>" as Redis +database "Analytics DB<>" 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 From 915446c8211ef0d431f1532ca22d102f940761be Mon Sep 17 00:00:00 2001 From: doyeon Date: Wed, 22 Oct 2025 15:02:54 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=EC=B0=B8=EC=97=AC=EC=9E=90=20=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=EC=B0=A8=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inner/participation-당첨자추첨.puml | 63 ++++++++++++------- .../inner/participation-이벤트참여.puml | 5 +- .../inner/participation-참여자목록조회.puml | 13 ++-- 3 files changed, 55 insertions(+), 26 deletions(-) diff --git a/design/backend/sequence/inner/participation-당첨자추첨.puml b/design/backend/sequence/inner/participation-당첨자추첨.puml index c0de56c..8d09789 100644 --- a/design/backend/sequence/inner/participation-당첨자추첨.puml +++ b/design/backend/sequence/inner/participation-당첨자추첨.puml @@ -40,16 +40,36 @@ else JWT 검증 성공 Controller -> Service: drawWinners(eventId, winnerCount, visitBonus) activate Service - 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 - deactivate Repo + Service -> Service: 이벤트 상태 확인\n- 이벤트 종료 여부\n- 이미 추첨 완료 여부 - 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 + 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 + deactivate Repo + + alt 참여자 수 부족 Service --> Controller: InsufficientParticipantsException Controller --> Gateway: 400 Bad Request\n{message: "참여자 수가 부족합니다"} Gateway --> Owner: 400 Bad Request @@ -58,22 +78,14 @@ else JWT 검증 성공 deactivate Gateway 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) activate Lottery note right of Lottery 추첨 알고리즘: + 시간 복잡도: O(n log n) + 공간 복잡도: O(n) + 1. 난수 생성 (Crypto.randomBytes) 2. 매장 방문 가산점 적용 (옵션) - 방문 고객: 가중치 2배 @@ -97,6 +109,15 @@ else JWT 검증 성공 Service -> Service: DB 트랜잭션 시작 + alt DB 저장 실패 시 + note right of Service + 트랜잭션 롤백 처리: + - 당첨자 업데이트 취소 + - 추첨 로그 저장 취소 + - 재시도 가능 상태 유지 + end note + end + Service -> Repo: updateWinners(winnerIds) activate Repo Repo -> DB: UPDATE participants\nSET is_winner = true,\nwon_at = NOW()\nWHERE participant_id IN (?) @@ -155,7 +176,7 @@ else JWT 검증 성공 deactivate Gateway Owner -> Owner: 당첨자 목록 화면 표시\n- 당첨자 정보 테이블\n- 재추첨 버튼\n- 추첨 완료 메시지 - end + end end end end diff --git a/design/backend/sequence/inner/participation-이벤트참여.puml b/design/backend/sequence/inner/participation-이벤트참여.puml index 01cccac..f8d8506 100644 --- a/design/backend/sequence/inner/participation-이벤트참여.puml +++ b/design/backend/sequence/inner/participation-이벤트참여.puml @@ -17,7 +17,10 @@ database "Redis Cache<>" as Cache Customer -> Gateway: POST /api/v1/participations\n{name, phone, eventId, entryPath, consent} activate Gateway -Gateway -> Gateway: JWT 토큰 검증\n(선택사항, 비회원 참여 가능) +note right of Gateway + 비회원 참여 가능 + JWT 검증 불필요 +end note Gateway -> Controller: POST /participations/register\n{name, phone, eventId, entryPath, consent} activate Controller diff --git a/design/backend/sequence/inner/participation-참여자목록조회.puml b/design/backend/sequence/inner/participation-참여자목록조회.puml index 42f4b60..2a5d86f 100644 --- a/design/backend/sequence/inner/participation-참여자목록조회.puml +++ b/design/backend/sequence/inner/participation-참여자목록조회.puml @@ -49,7 +49,7 @@ else JWT 검증 성공 Service --> Controller: ParticipantListResponse\n(캐시된 데이터) note right of Service 캐시된 데이터 반환 - - TTL: 5분 + - TTL: 10분 - 실시간 정확도 vs 성능 트레이드오프 end note Controller --> Gateway: 200 OK\n{participants, totalElements, totalPages} @@ -73,6 +73,10 @@ else JWT 검증 성공 - isWinner 필터 (선택) - name/phone 검색 (선택) - 페이지네이션 (필수) + + 필요 인덱스: + idx_participants_event_filters + (event_id, entry_path, is_winner, participated_at DESC) end note DB --> Repo: 참여자 목록 결과셋 @@ -88,12 +92,13 @@ else JWT 검증 성공 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 note right of Cache 캐시 저장: - - 짧은 TTL (5분) - - 실시간 참여 반영을 위해 + - TTL: 10분 + - 실시간 참여 반영과 성능 균형 + - 이벤트 참여 빈도 고려 end note Cache --> Service: 캐시 저장 완료 deactivate Cache From a275531eef714c0b928cf3a11cb4d009a7d1556b Mon Sep 17 00:00:00 2001 From: cherry2250 Date: Wed, 22 Oct 2025 15:12:15 +0900 Subject: [PATCH 4/5] =?UTF-8?q?content-=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=8B=9C=ED=80=80=EC=8A=A4=20=ED=8F=B4?= =?UTF-8?q?=EB=A7=81=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ContentCreated 이벤트 발행 제거 (Consumer 없음) - 폴링 방식으로 결과 조회하도록 명시 - 불필요한 Kafka Producer 참여자 제거 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- design/backend/sequence/inner/content-이미지생성.puml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/design/backend/sequence/inner/content-이미지생성.puml b/design/backend/sequence/inner/content-이미지생성.puml index 68ab711..60bc5a7 100644 --- a/design/backend/sequence/inner/content-이미지생성.puml +++ b/design/backend/sequence/inner/content-이미지생성.puml @@ -202,7 +202,11 @@ else 캐시 MISS (새로운 이미지 생성) JobStatus --> Handler: 업데이트 완료 deactivate JobStatus - note over Handler: Kafka Event 발행\nContentCreated\n{jobId, eventDraftId, imageUrls} + note over Handler + 폴링 방식으로 결과 조회 + - Event Service는 이미지결과조회 API로 확인 + - Kafka 이벤트 발행 없음 (Consumer 없음) + end note Handler --> Consumer: 처리 완료 end From 0daf0435046bb8b21f39fe8697c412ae17f17f30 Mon Sep 17 00:00:00 2001 From: doyeon Date: Wed, 22 Oct 2025 15:39:49 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=C3=A3delete=C2=85WinnerSelected?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inner/participation-당첨자추첨.puml | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/design/backend/sequence/inner/participation-당첨자추첨.puml b/design/backend/sequence/inner/participation-당첨자추첨.puml index 8d09789..f286b20 100644 --- a/design/backend/sequence/inner/participation-당첨자추첨.puml +++ b/design/backend/sequence/inner/participation-당첨자추첨.puml @@ -11,7 +11,6 @@ participant "LotteryAlgorithm" as Lottery participant "ParticipantRepository" as Repo participant "DrawLogRepository" as LogRepo database "Participation DB" as DB -participant "KafkaProducer" as Kafka == UFR-PART-030: 당첨자 추첨 == @@ -147,25 +146,6 @@ else JWT 검증 성공 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} deactivate Service