kt-event-marketing/design/backend/class/analytics-service.puml
jhbkjh 3075a5d49f 물리아키텍처 설계 완료
 주요 기능
- Azure 기반 물리아키텍처 설계 (개발환경/운영환경)
- 7개 마이크로서비스 물리 구조 설계
- 네트워크 아키텍처 다이어그램 작성 (Mermaid)
- 환경별 비교 분석 및 마스터 인덱스 문서

📁 생성 파일
- design/backend/physical/physical-architecture.md (마스터)
- design/backend/physical/physical-architecture-dev.md (개발환경)
- design/backend/physical/physical-architecture-prod.md (운영환경)
- design/backend/physical/*.mmd (4개 Mermaid 다이어그램)

🎯 핵심 성과
- 비용 최적화: 개발환경 월 $143, 운영환경 월 $2,860
- 확장성: 개발환경 100명 → 운영환경 10,000명 (100배)
- 가용성: 개발환경 95% → 운영환경 99.9%
- 보안: 다층 보안 아키텍처 (L1~L4)

🛠️ 기술 스택
- Azure Kubernetes Service (AKS)
- Azure Database for PostgreSQL Flexible
- Azure Cache for Redis Premium
- Azure Service Bus Premium
- Application Gateway + WAF

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 15:13:01 +09:00

739 lines
26 KiB
Plaintext

@startuml
!theme mono
title Analytics Service 클래스 다이어그램 (상세)
' ============================================================
' Presentation Layer - Controller
' ============================================================
package "com.kt.event.analytics.controller" <<Rectangle>> #F0F8FF {
class AnalyticsDashboardController {
- analyticsService: AnalyticsService
+ getEventAnalytics(eventId: String, startDate: LocalDateTime, endDate: LocalDateTime, refresh: Boolean): ResponseEntity<ApiResponse<AnalyticsDashboardResponse>>
}
class ChannelAnalyticsController {
- channelAnalyticsService: ChannelAnalyticsService
+ getChannelAnalytics(eventId: String, channels: String, sortBy: String, sortOrder: String): ResponseEntity<ApiResponse<ChannelAnalyticsResponse>>
}
class RoiAnalyticsController {
- roiAnalyticsService: RoiAnalyticsService
+ getRoiAnalytics(eventId: String): ResponseEntity<ApiResponse<RoiAnalyticsResponse>>
}
class TimelineAnalyticsController {
- timelineAnalyticsService: TimelineAnalyticsService
+ getTimelineAnalytics(eventId: String, granularity: String, startDate: LocalDateTime, endDate: LocalDateTime): ResponseEntity<ApiResponse<TimelineAnalyticsResponse>>
}
class UserAnalyticsDashboardController {
- userAnalyticsService: UserAnalyticsService
+ getUserEventAnalytics(userId: String, startDate: LocalDateTime, endDate: LocalDateTime): ResponseEntity<ApiResponse<UserAnalyticsDashboardResponse>>
}
class UserChannelAnalyticsController {
- userChannelAnalyticsService: UserChannelAnalyticsService
+ getUserChannelAnalytics(userId: String, channels: String): ResponseEntity<ApiResponse<UserChannelAnalyticsResponse>>
}
class UserRoiAnalyticsController {
- userRoiAnalyticsService: UserRoiAnalyticsService
+ getUserRoiAnalytics(userId: String): ResponseEntity<ApiResponse<UserRoiAnalyticsResponse>>
}
class UserTimelineAnalyticsController {
- userTimelineAnalyticsService: UserTimelineAnalyticsService
+ getUserTimelineAnalytics(userId: String, granularity: String, startDate: LocalDateTime, endDate: LocalDateTime): ResponseEntity<ApiResponse<UserTimelineAnalyticsResponse>>
}
}
' ============================================================
' Business Layer - Service
' ============================================================
package "com.kt.event.analytics.service" <<Rectangle>> #E6F7E6 {
class AnalyticsService {
- eventStatsRepository: EventStatsRepository
- channelStatsRepository: ChannelStatsRepository
- externalChannelService: ExternalChannelService
- roiCalculator: ROICalculator
- redisTemplate: RedisTemplate<String, String>
- objectMapper: ObjectMapper
+ getDashboardData(eventId: String, startDate: LocalDateTime, endDate: LocalDateTime, refresh: boolean): AnalyticsDashboardResponse
- buildDashboardData(eventStats: EventStats, channelStatsList: List<ChannelStats>, startDate: LocalDateTime, endDate: LocalDateTime): AnalyticsDashboardResponse
- buildPeriodInfo(startDate: LocalDateTime, endDate: LocalDateTime): PeriodInfo
- buildAnalyticsSummary(eventStats: EventStats, channelStatsList: List<ChannelStats>): AnalyticsSummary
- buildChannelPerformance(channelStatsList: List<ChannelStats>, totalInvestment: BigDecimal): List<ChannelSummary>
}
class ChannelAnalyticsService {
- channelStatsRepository: ChannelStatsRepository
- externalChannelService: ExternalChannelService
- redisTemplate: RedisTemplate<String, String>
- objectMapper: ObjectMapper
+ getChannelAnalytics(eventId: String, channels: List<String>, sortBy: String, sortOrder: String): ChannelAnalyticsResponse
- buildChannelMetrics(channelStats: ChannelStats): ChannelMetrics
- buildChannelPerformance(channelStats: ChannelStats): ChannelPerformance
- buildChannelComparison(channelStatsList: List<ChannelStats>): ChannelComparison
}
class RoiAnalyticsService {
- eventStatsRepository: EventStatsRepository
- channelStatsRepository: ChannelStatsRepository
- roiCalculator: ROICalculator
- redisTemplate: RedisTemplate<String, String>
- objectMapper: ObjectMapper
+ getRoiAnalytics(eventId: String): RoiAnalyticsResponse
- buildRoiCalculation(eventStats: EventStats, channelStatsList: List<ChannelStats>): RoiCalculation
- buildInvestmentDetails(channelStatsList: List<ChannelStats>): InvestmentDetails
- buildRevenueDetails(eventStats: EventStats): RevenueDetails
}
class TimelineAnalyticsService {
- timelineDataRepository: TimelineDataRepository
- eventStatsRepository: EventStatsRepository
- redisTemplate: RedisTemplate<String, String>
- objectMapper: ObjectMapper
+ getTimelineAnalytics(eventId: String, granularity: String, startDate: LocalDateTime, endDate: LocalDateTime): TimelineAnalyticsResponse
- buildTimelineDataPoints(timelineDataList: List<TimelineData>): List<TimelineDataPoint>
- buildTrendAnalysis(timelineDataList: List<TimelineData>): TrendAnalysis
- buildPeakTimeInfo(timelineDataList: List<TimelineData>): PeakTimeInfo
}
class UserAnalyticsService {
- eventStatsRepository: EventStatsRepository
- channelStatsRepository: ChannelStatsRepository
- roiCalculator: ROICalculator
+ getUserEventAnalytics(userId: String, startDate: LocalDateTime, endDate: LocalDateTime): UserAnalyticsDashboardResponse
- buildUserAnalyticsSummary(eventStatsList: List<EventStats>, channelStatsList: List<ChannelStats>): AnalyticsSummary
}
class UserChannelAnalyticsService {
- channelStatsRepository: ChannelStatsRepository
- eventStatsRepository: EventStatsRepository
+ getUserChannelAnalytics(userId: String, channels: List<String>): UserChannelAnalyticsResponse
}
class UserRoiAnalyticsService {
- eventStatsRepository: EventStatsRepository
- channelStatsRepository: ChannelStatsRepository
- roiCalculator: ROICalculator
+ getUserRoiAnalytics(userId: String): UserRoiAnalyticsResponse
}
class UserTimelineAnalyticsService {
- timelineDataRepository: TimelineDataRepository
- eventStatsRepository: EventStatsRepository
+ getUserTimelineAnalytics(userId: String, granularity: String, startDate: LocalDateTime, endDate: LocalDateTime): UserTimelineAnalyticsResponse
}
class ExternalChannelService {
+ updateChannelStatsFromExternalAPIs(eventId: String, channelStatsList: List<ChannelStats>): void
- updateChannelStatsFromAPI(eventId: String, channelStats: ChannelStats): void
- updateWooriTVStats(eventId: String, channelStats: ChannelStats): void
- wooriTVFallback(eventId: String, channelStats: ChannelStats, e: Exception): void
- updateGenieTVStats(eventId: String, channelStats: ChannelStats): void
- genieTVFallback(eventId: String, channelStats: ChannelStats, e: Exception): void
- updateRingoBizStats(eventId: String, channelStats: ChannelStats): void
- ringoBizFallback(eventId: String, channelStats: ChannelStats, e: Exception): void
- updateSNSStats(eventId: String, channelStats: ChannelStats): void
- snsFallback(eventId: String, channelStats: ChannelStats, e: Exception): void
}
class ROICalculator {
+ calculateRoiSummary(eventStats: EventStats): RoiSummary
+ calculateRoi(investment: BigDecimal, revenue: BigDecimal): BigDecimal
+ calculateCostPerParticipant(totalInvestment: BigDecimal, participants: int): BigDecimal
+ calculateRevenueProjection(currentRevenue: BigDecimal, targetRoi: BigDecimal): RevenueProjection
}
}
' ============================================================
' Data Access Layer - Repository
' ============================================================
package "com.kt.event.analytics.repository" <<Rectangle>> #FFF8DC {
interface EventStatsRepository {
+ findByEventId(eventId: String): Optional<EventStats>
+ findByUserId(userId: String): List<EventStats>
+ save(eventStats: EventStats): EventStats
+ findAll(): List<EventStats>
}
interface ChannelStatsRepository {
+ findByEventId(eventId: String): List<ChannelStats>
+ findByEventIdAndChannelName(eventId: String, channelName: String): Optional<ChannelStats>
+ findByEventIdIn(eventIds: List<String>): List<ChannelStats>
+ save(channelStats: ChannelStats): ChannelStats
}
interface TimelineDataRepository {
+ findByEventIdOrderByTimestampAsc(eventId: String): List<TimelineData>
+ findByEventIdAndTimestampBetween(eventId: String, startDate: LocalDateTime, endDate: LocalDateTime): List<TimelineData>
+ findByEventIdIn(eventIds: List<String>): List<TimelineData>
+ save(timelineData: TimelineData): TimelineData
}
}
' ============================================================
' Domain Layer - Entity
' ============================================================
package "com.kt.event.analytics.entity" <<Rectangle>> #FFFACD {
class EventStats {
- id: Long
- eventId: String
- eventTitle: String
- userId: String
- totalParticipants: Integer
- totalViews: Integer
- estimatedRoi: BigDecimal
- targetRoi: BigDecimal
- salesGrowthRate: BigDecimal
- totalInvestment: BigDecimal
- expectedRevenue: BigDecimal
- status: String
+ incrementParticipants(): void
+ incrementParticipants(count: int): void
}
class ChannelStats {
- id: Long
- eventId: String
- channelName: String
- channelType: String
- impressions: Integer
- views: Integer
- clicks: Integer
- participants: Integer
- conversions: Integer
- distributionCost: BigDecimal
- likes: Integer
- comments: Integer
- shares: Integer
- totalCalls: Integer
- completedCalls: Integer
- averageDuration: Integer
}
class TimelineData {
- id: Long
- eventId: String
- timestamp: LocalDateTime
- participants: Integer
- views: Integer
- engagement: Integer
- conversions: Integer
- cumulativeParticipants: Integer
}
}
' ============================================================
' DTO Layer
' ============================================================
package "com.kt.event.analytics.dto.response" <<Rectangle>> #E6E6FA {
class AnalyticsDashboardResponse {
- eventId: String
- eventTitle: String
- period: PeriodInfo
- summary: AnalyticsSummary
- channelPerformance: List<ChannelSummary>
- roi: RoiSummary
- lastUpdatedAt: LocalDateTime
- dataSource: String
}
class AnalyticsSummary {
- participants: Integer
- participantsDelta: Integer
- totalViews: Integer
- totalReach: Integer
- engagementRate: Double
- conversionRate: Double
- averageEngagementTime: Integer
- targetRoi: Double
- socialInteractions: SocialInteractionStats
}
class ChannelSummary {
- channel: String
- views: Integer
- participants: Integer
- engagementRate: Double
- conversionRate: Double
- roi: Double
}
class PeriodInfo {
- startDate: LocalDateTime
- endDate: LocalDateTime
- durationDays: Integer
}
class SocialInteractionStats {
- likes: Integer
- comments: Integer
- shares: Integer
}
class RoiSummary {
- currentRoi: Double
- targetRoi: Double
- achievementRate: Double
- expectedReturn: BigDecimal
- totalInvestment: BigDecimal
}
class ChannelAnalyticsResponse {
- eventId: String
- eventTitle: String
- totalChannels: Integer
- channels: List<ChannelAnalytics>
- comparison: ChannelComparison
- lastUpdatedAt: LocalDateTime
}
class ChannelAnalytics {
- channelName: String
- channelType: String
- metrics: ChannelMetrics
- performance: ChannelPerformance
- costs: ChannelCosts
- voiceCallStats: VoiceCallStats
- socialStats: SocialInteractionStats
}
class ChannelMetrics {
- impressions: Integer
- views: Integer
- clicks: Integer
- participants: Integer
- conversions: Integer
}
class ChannelPerformance {
- engagementRate: Double
- clickThroughRate: Double
- conversionRate: Double
- participationRate: Double
}
class ChannelCosts {
- distributionCost: BigDecimal
- costPerImpression: BigDecimal
- costPerClick: BigDecimal
- costPerParticipant: BigDecimal
}
class ChannelComparison {
- bestPerformingChannel: String
- mostCostEffectiveChannel: String
- highestEngagementChannel: String
}
class VoiceCallStats {
- totalCalls: Integer
- completedCalls: Integer
- completionRate: Double
- averageDuration: Integer
}
class RoiAnalyticsResponse {
- eventId: String
- eventTitle: String
- roiCalculation: RoiCalculation
- investment: InvestmentDetails
- revenue: RevenueDetails
- costEfficiency: CostEfficiency
- projection: RevenueProjection
- lastUpdatedAt: LocalDateTime
}
class RoiCalculation {
- currentRoi: Double
- targetRoi: Double
- achievementRate: Double
- breakEvenPoint: BigDecimal
}
class InvestmentDetails {
- totalInvestment: BigDecimal
- channelDistribution: Map<String, BigDecimal>
- costPerChannel: Map<String, BigDecimal>
}
class RevenueDetails {
- expectedRevenue: BigDecimal
- currentRevenue: BigDecimal
- salesGrowthRate: Double
}
class CostEfficiency {
- costPerParticipant: BigDecimal
- costPerConversion: BigDecimal
- costPerView: BigDecimal
}
class RevenueProjection {
- projectedRevenue: BigDecimal
- projectedRoi: Double
- estimatedGrowth: Double
}
class TimelineAnalyticsResponse {
- eventId: String
- eventTitle: String
- granularity: String
- period: PeriodInfo
- dataPoints: List<TimelineDataPoint>
- trend: TrendAnalysis
- peakTime: PeakTimeInfo
- lastUpdatedAt: LocalDateTime
}
class TimelineDataPoint {
- timestamp: LocalDateTime
- participants: Integer
- views: Integer
- engagement: Integer
- conversions: Integer
- cumulativeParticipants: Integer
}
class TrendAnalysis {
- growthRate: Double
- averageParticipantsPerHour: Double
- totalEngagement: Integer
- conversionTrend: String
}
class PeakTimeInfo {
- peakTimestamp: LocalDateTime
- peakParticipants: Integer
- peakHour: Integer
}
class UserAnalyticsDashboardResponse {
- userId: String
- totalEvents: Integer
- period: PeriodInfo
- summary: AnalyticsSummary
- events: List<AnalyticsDashboardResponse>
- lastUpdatedAt: LocalDateTime
}
class UserChannelAnalyticsResponse {
- userId: String
- totalEvents: Integer
- channels: List<ChannelAnalytics>
- comparison: ChannelComparison
- lastUpdatedAt: LocalDateTime
}
class UserRoiAnalyticsResponse {
- userId: String
- totalEvents: Integer
- roiCalculation: RoiCalculation
- investment: InvestmentDetails
- revenue: RevenueDetails
- lastUpdatedAt: LocalDateTime
}
class UserTimelineAnalyticsResponse {
- userId: String
- totalEvents: Integer
- granularity: String
- period: PeriodInfo
- dataPoints: List<TimelineDataPoint>
- lastUpdatedAt: LocalDateTime
}
}
' ============================================================
' Messaging Layer - Kafka Consumer
' ============================================================
package "com.kt.event.analytics.messaging.consumer" <<Rectangle>> #FFE4E1 {
class EventCreatedConsumer {
- eventStatsRepository: EventStatsRepository
- objectMapper: ObjectMapper
- redisTemplate: RedisTemplate<String, String>
+ handleEventCreated(message: String): void
}
class ParticipantRegisteredConsumer {
- eventStatsRepository: EventStatsRepository
- timelineDataRepository: TimelineDataRepository
- objectMapper: ObjectMapper
- redisTemplate: RedisTemplate<String, String>
+ handleParticipantRegistered(message: String): void
- updateTimelineData(eventId: String): void
}
class DistributionCompletedConsumer {
- channelStatsRepository: ChannelStatsRepository
- objectMapper: ObjectMapper
- redisTemplate: RedisTemplate<String, String>
+ handleDistributionCompleted(message: String): void
}
}
package "com.kt.event.analytics.messaging.event" <<Rectangle>> #FFE4E1 {
class EventCreatedEvent {
- eventId: String
- eventTitle: String
- storeId: String
- totalInvestment: BigDecimal
- status: String
}
class ParticipantRegisteredEvent {
- eventId: String
- participantId: String
- channelName: String
- registeredAt: LocalDateTime
}
class DistributionCompletedEvent {
- eventId: String
- channelName: String
- distributionCost: BigDecimal
- estimatedReach: Integer
- completedAt: LocalDateTime
}
}
' ============================================================
' Batch Layer
' ============================================================
package "com.kt.event.analytics.batch" <<Rectangle>> #FFF5EE {
class AnalyticsBatchScheduler {
- analyticsService: AnalyticsService
- eventStatsRepository: EventStatsRepository
- redisTemplate: RedisTemplate<String, String>
+ refreshAnalyticsDashboard(): void
+ initialDataLoad(): void
}
}
' ============================================================
' Configuration Layer
' ============================================================
package "com.kt.event.analytics.config" <<Rectangle>> #F5F5F5 {
class RedisConfig {
+ redisConnectionFactory(): RedisConnectionFactory
+ redisTemplate(): RedisTemplate<String, String>
}
class KafkaConsumerConfig {
+ consumerFactory(): ConsumerFactory<String, String>
+ kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory<String, String>
}
class KafkaTopicConfig {
+ sampleEventCreatedTopic(): NewTopic
+ sampleParticipantRegisteredTopic(): NewTopic
+ sampleDistributionCompletedTopic(): NewTopic
}
class Resilience4jConfig {
+ customize(factory: Resilience4JCircuitBreakerFactory): Customizer<Resilience4JCircuitBreakerFactory>
}
class SecurityConfig {
+ securityFilterChain(http: HttpSecurity): SecurityFilterChain
}
class SwaggerConfig {
+ openAPI(): OpenAPI
}
}
' ============================================================
' Common Components (from common-base)
' ============================================================
package "com.kt.event.common" <<Rectangle>> #DCDCDC {
abstract class BaseTimeEntity {
- createdAt: LocalDateTime
- updatedAt: LocalDateTime
}
class "ApiResponse<T>" {
- success: boolean
- data: T
- errorCode: String
- message: String
- timestamp: LocalDateTime
+ success(data: T): ApiResponse<T>
+ success(): ApiResponse<T>
+ error(errorCode: String, message: String): ApiResponse<T>
}
interface ErrorCode {
+ getCode(): String
+ getMessage(): String
}
class BusinessException {
- errorCode: ErrorCode
- details: String
}
}
' ============================================================
' Relationships
' ============================================================
' Controller -> Service
AnalyticsDashboardController --> AnalyticsService : uses
ChannelAnalyticsController --> ChannelAnalyticsService : uses
RoiAnalyticsController --> RoiAnalyticsService : uses
TimelineAnalyticsController --> TimelineAnalyticsService : uses
UserAnalyticsDashboardController --> UserAnalyticsService : uses
UserChannelAnalyticsController --> UserChannelAnalyticsService : uses
UserRoiAnalyticsController --> UserRoiAnalyticsService : uses
UserTimelineAnalyticsController --> UserTimelineAnalyticsService : uses
' Service -> Repository
AnalyticsService --> EventStatsRepository : uses
AnalyticsService --> ChannelStatsRepository : uses
ChannelAnalyticsService --> ChannelStatsRepository : uses
RoiAnalyticsService --> EventStatsRepository : uses
RoiAnalyticsService --> ChannelStatsRepository : uses
TimelineAnalyticsService --> TimelineDataRepository : uses
TimelineAnalyticsService --> EventStatsRepository : uses
UserAnalyticsService --> EventStatsRepository : uses
UserAnalyticsService --> ChannelStatsRepository : uses
UserChannelAnalyticsService --> ChannelStatsRepository : uses
UserChannelAnalyticsService --> EventStatsRepository : uses
UserRoiAnalyticsService --> EventStatsRepository : uses
UserRoiAnalyticsService --> ChannelStatsRepository : uses
UserTimelineAnalyticsService --> TimelineDataRepository : uses
UserTimelineAnalyticsService --> EventStatsRepository : uses
' Service -> Service
AnalyticsService --> ExternalChannelService : uses
AnalyticsService --> ROICalculator : uses
ChannelAnalyticsService --> ExternalChannelService : uses
RoiAnalyticsService --> ROICalculator : uses
UserAnalyticsService --> ROICalculator : uses
UserRoiAnalyticsService --> ROICalculator : uses
' Service -> External Components
ExternalChannelService --> ChannelStats : modifies
' Consumer -> Repository
EventCreatedConsumer --> EventStatsRepository : uses
ParticipantRegisteredConsumer --> EventStatsRepository : uses
ParticipantRegisteredConsumer --> TimelineDataRepository : uses
DistributionCompletedConsumer --> ChannelStatsRepository : uses
' Consumer -> Event
EventCreatedConsumer --> EventCreatedEvent : consumes
ParticipantRegisteredConsumer --> ParticipantRegisteredEvent : consumes
DistributionCompletedConsumer --> DistributionCompletedEvent : consumes
' Batch -> Service/Repository
AnalyticsBatchScheduler --> AnalyticsService : uses
AnalyticsBatchScheduler --> EventStatsRepository : uses
' Repository -> Entity
EventStatsRepository --> EventStats : manages
ChannelStatsRepository --> ChannelStats : manages
TimelineDataRepository --> TimelineData : manages
' Entity -> BaseTimeEntity
EventStats --|> BaseTimeEntity : extends
ChannelStats --|> BaseTimeEntity : extends
TimelineData --|> BaseTimeEntity : extends
' Controller -> DTO
AnalyticsDashboardController --> AnalyticsDashboardResponse : returns
ChannelAnalyticsController --> ChannelAnalyticsResponse : returns
RoiAnalyticsController --> RoiAnalyticsResponse : returns
TimelineAnalyticsController --> TimelineAnalyticsResponse : returns
UserAnalyticsDashboardController --> UserAnalyticsDashboardResponse : returns
UserChannelAnalyticsController --> UserChannelAnalyticsResponse : returns
UserRoiAnalyticsController --> UserRoiAnalyticsResponse : returns
UserTimelineAnalyticsController --> UserTimelineAnalyticsResponse : returns
' Service -> DTO
AnalyticsService --> AnalyticsDashboardResponse : creates
ChannelAnalyticsService --> ChannelAnalyticsResponse : creates
RoiAnalyticsService --> RoiAnalyticsResponse : creates
TimelineAnalyticsService --> TimelineAnalyticsResponse : creates
UserAnalyticsService --> UserAnalyticsDashboardResponse : creates
UserChannelAnalyticsService --> UserChannelAnalyticsResponse : creates
UserRoiAnalyticsService --> UserRoiAnalyticsResponse : creates
UserTimelineAnalyticsService --> UserTimelineAnalyticsResponse : creates
' DTO Composition
AnalyticsDashboardResponse *-- PeriodInfo : contains
AnalyticsDashboardResponse *-- AnalyticsSummary : contains
AnalyticsDashboardResponse *-- RoiSummary : contains
AnalyticsSummary *-- SocialInteractionStats : contains
ChannelAnalyticsResponse *-- ChannelAnalytics : contains
ChannelAnalyticsResponse *-- ChannelComparison : contains
ChannelAnalytics *-- ChannelMetrics : contains
ChannelAnalytics *-- ChannelPerformance : contains
ChannelAnalytics *-- ChannelCosts : contains
ChannelAnalytics *-- VoiceCallStats : contains
RoiAnalyticsResponse *-- RoiCalculation : contains
RoiAnalyticsResponse *-- InvestmentDetails : contains
RoiAnalyticsResponse *-- RevenueDetails : contains
RoiAnalyticsResponse *-- CostEfficiency : contains
RoiAnalyticsResponse *-- RevenueProjection : contains
TimelineAnalyticsResponse *-- PeriodInfo : contains
TimelineAnalyticsResponse *-- TimelineDataPoint : contains
TimelineAnalyticsResponse *-- TrendAnalysis : contains
TimelineAnalyticsResponse *-- PeakTimeInfo : contains
UserAnalyticsDashboardResponse *-- PeriodInfo : contains
UserAnalyticsDashboardResponse *-- AnalyticsSummary : contains
' Common Dependencies
BusinessException --> ErrorCode : uses
AnalyticsDashboardController --> "ApiResponse<T>" : uses
ChannelAnalyticsController --> "ApiResponse<T>" : uses
RoiAnalyticsController --> "ApiResponse<T>" : uses
TimelineAnalyticsController --> "ApiResponse<T>" : uses
note top of AnalyticsService
**핵심 서비스**
- Redis 캐싱 (1시간 TTL)
- 외부 API 병렬 호출
- Circuit Breaker 패턴
- Cache-Aside 패턴
end note
note top of ExternalChannelService
**외부 채널 통합**
- 우리동네TV API
- 지니TV API
- 링고비즈 API
- SNS APIs
- Resilience4j Circuit Breaker
end note
note top of EventCreatedConsumer
**Kafka Event Consumer**
- EventCreated 이벤트 구독
- 멱등성 처리 (Redis Set)
- 캐시 무효화
end note
note top of AnalyticsBatchScheduler
**배치 스케줄러**
- 5분 단위 캐시 갱신
- 초기 데이터 로딩
- 캐시 워밍업
end note
@enduml