@startuml
!theme mono

title Distribution Service 클래스 다이어그램 (상세)

package "com.kt.distribution" {

    package "controller" {
        class DistributionController {
            - distributionService: DistributionService
            + distribute(request: DistributionRequest): ResponseEntity<DistributionResponse>
            + getDistributionStatus(eventId: String): ResponseEntity<DistributionStatusResponse>
        }
    }

    package "service" {
        class DistributionService {
            - channelAdapters: List<ChannelAdapter>
            - kafkaEventPublisher: Optional<KafkaEventPublisher>
            - statusRepository: DistributionStatusRepository
            - executorService: ExecutorService
            + distribute(request: DistributionRequest): DistributionResponse
            + getDistributionStatus(eventId: String): DistributionStatusResponse
            - saveInProgressStatus(eventId: String, channels: List<ChannelType>, startedAt: LocalDateTime): void
            - saveCompletedStatus(eventId: String, results: List<ChannelDistributionResult>, startedAt: LocalDateTime, completedAt: LocalDateTime, successCount: long, failureCount: long): void
            - convertToChannelStatus(result: ChannelDistributionResult, eventId: String, completedAt: LocalDateTime): ChannelStatus
            - publishDistributionCompletedEvent(eventId: String, results: List<ChannelDistributionResult>): void
        }

        class KafkaEventPublisher {
            - kafkaTemplate: KafkaTemplate<String, Object>
            - distributionCompletedTopic: String
            + publishDistributionCompleted(event: DistributionCompletedEvent): void
        }
    }

    package "adapter" {
        interface ChannelAdapter {
            + getChannelType(): ChannelType
            + distribute(request: DistributionRequest): ChannelDistributionResult
        }

        abstract class AbstractChannelAdapter implements ChannelAdapter {
            + distribute(request: DistributionRequest): ChannelDistributionResult
            # executeDistribution(request: DistributionRequest): ChannelDistributionResult
            # fallback(request: DistributionRequest, throwable: Throwable): ChannelDistributionResult
        }

        class UriDongNeTvAdapter extends AbstractChannelAdapter {
            + getChannelType(): ChannelType
            # executeDistribution(request: DistributionRequest): ChannelDistributionResult
        }

        class RingoBizAdapter extends AbstractChannelAdapter {
            + getChannelType(): ChannelType
            # executeDistribution(request: DistributionRequest): ChannelDistributionResult
        }

        class GiniTvAdapter extends AbstractChannelAdapter {
            + getChannelType(): ChannelType
            # executeDistribution(request: DistributionRequest): ChannelDistributionResult
        }

        class InstagramAdapter extends AbstractChannelAdapter {
            + getChannelType(): ChannelType
            # executeDistribution(request: DistributionRequest): ChannelDistributionResult
        }

        class NaverAdapter extends AbstractChannelAdapter {
            + getChannelType(): ChannelType
            # executeDistribution(request: DistributionRequest): ChannelDistributionResult
        }

        class KakaoAdapter extends AbstractChannelAdapter {
            + getChannelType(): ChannelType
            # executeDistribution(request: DistributionRequest): ChannelDistributionResult
        }
    }

    package "dto" {
        class DistributionRequest {
            - eventId: String
            - title: String
            - description: String
            - imageUrl: String
            - channels: List<ChannelType>
            - channelSettings: Map<String, Map<String, Object>>
        }

        class DistributionResponse {
            - eventId: String
            - success: boolean
            - channelResults: List<ChannelDistributionResult>
            - successCount: int
            - failureCount: int
            - completedAt: LocalDateTime
            - totalExecutionTimeMs: long
            - message: String
        }

        class ChannelDistributionResult {
            - channel: ChannelType
            - success: boolean
            - distributionId: String
            - estimatedReach: Integer
            - errorMessage: String
            - executionTimeMs: long
        }

        class DistributionStatusResponse {
            - eventId: String
            - overallStatus: String
            - startedAt: LocalDateTime
            - completedAt: LocalDateTime
            - channels: List<ChannelStatus>
        }

        class ChannelStatus {
            - channel: ChannelType
            - status: String
            - progress: Integer
            - distributionId: String
            - estimatedViews: Integer
            - updateTimestamp: LocalDateTime
            - eventId: String
            - impressionSchedule: List<String>
            - postUrl: String
            - postId: String
            - messageId: String
            - completedAt: LocalDateTime
            - errorMessage: String
            - retries: Integer
            - lastRetryAt: LocalDateTime
        }

        enum ChannelType {
            URIDONGNETV
            RINGOBIZ
            GINITV
            INSTAGRAM
            NAVER
            KAKAO
            - displayName: String
            - category: String
            + getDisplayName(): String
            + getCategory(): String
        }
    }

    package "repository" {
        class DistributionStatusRepository {
            - jpaRepository: DistributionStatusJpaRepository
            - mapper: DistributionStatusMapper
            + save(eventId: String, status: DistributionStatusResponse): void
            + findByEventId(eventId: String): Optional<DistributionStatusResponse>
            + delete(eventId: String): void
            + deleteAll(): void
        }

        interface DistributionStatusJpaRepository {
            + findByEventIdWithChannels(eventId: String): Optional<DistributionStatus>
            + deleteByEventId(eventId: String): void
        }
    }

    package "entity" {
        class DistributionStatus {
            - id: Long
            - eventId: String
            - overallStatus: String
            - startedAt: LocalDateTime
            - completedAt: LocalDateTime
            - channels: List<ChannelStatusEntity>
            - createdAt: LocalDateTime
            - updatedAt: LocalDateTime
            + addChannelStatus(channelStatus: ChannelStatusEntity): void
            + removeChannelStatus(channelStatus: ChannelStatusEntity): void
        }

        class ChannelStatusEntity {
            - id: Long
            - distributionStatus: DistributionStatus
            - channel: ChannelType
            - status: String
            - progress: Integer
            - distributionId: String
            - estimatedViews: Integer
            - updateTimestamp: LocalDateTime
            - eventId: String
            - impressionSchedule: String
            - postUrl: String
            - postId: String
            - messageId: String
            - completedAt: LocalDateTime
            - errorMessage: String
            - retries: Integer
            - lastRetryAt: LocalDateTime
            - createdAt: LocalDateTime
            - updatedAt: LocalDateTime
        }
    }

    package "mapper" {
        class DistributionStatusMapper {
            + toEntity(dto: DistributionStatusResponse): DistributionStatus
            + toDto(entity: DistributionStatus): DistributionStatusResponse
            + toChannelStatusEntity(dto: ChannelStatus): ChannelStatusEntity
            + toChannelStatusDto(entity: ChannelStatusEntity): ChannelStatus
        }
    }

    package "event" {
        class DistributionCompletedEvent {
            - eventId: String
            - distributedChannels: List<DistributedChannelInfo>
            - completedAt: LocalDateTime
        }

        class DistributedChannelInfo {
            - channel: String
            - channelType: String
            - status: String
            - expectedViews: Integer
        }
    }

    package "config" {
        class KafkaConfig {
            + kafkaTemplate(): KafkaTemplate<String, Object>
            + producerFactory(): ProducerFactory<String, Object>
        }

        class OpenApiConfig {
            + openAPI(): OpenAPI
        }

        class WebConfig {
            + corsConfigurer(): WebMvcConfigurer
        }
    }
}

' 관계 정의
DistributionController --> DistributionService : uses
DistributionService --> ChannelAdapter : uses
DistributionService --> KafkaEventPublisher : uses
DistributionService --> DistributionStatusRepository : uses
DistributionService ..> DistributionRequest : uses
DistributionService ..> DistributionResponse : creates
DistributionService ..> DistributionStatusResponse : uses
DistributionService ..> ChannelDistributionResult : uses

AbstractChannelAdapter ..|> ChannelAdapter : implements
UriDongNeTvAdapter --|> AbstractChannelAdapter : extends
RingoBizAdapter --|> AbstractChannelAdapter : extends
GiniTvAdapter --|> AbstractChannelAdapter : extends
InstagramAdapter --|> AbstractChannelAdapter : extends
NaverAdapter --|> AbstractChannelAdapter : extends
KakaoAdapter --|> AbstractChannelAdapter : extends

ChannelAdapter ..> DistributionRequest : uses
ChannelAdapter ..> ChannelDistributionResult : creates

KafkaEventPublisher ..> DistributionCompletedEvent : publishes
DistributionCompletedEvent --> DistributedChannelInfo : contains

DistributionStatusRepository --> DistributionStatusJpaRepository : uses
DistributionStatusRepository --> DistributionStatusMapper : uses
DistributionStatusRepository ..> DistributionStatusResponse : uses
DistributionStatusRepository ..> DistributionStatus : uses

DistributionStatusJpaRepository ..> DistributionStatus : manages
DistributionStatusMapper ..> DistributionStatus : maps
DistributionStatusMapper ..> DistributionStatusResponse : maps
DistributionStatusMapper ..> ChannelStatus : maps
DistributionStatusMapper ..> ChannelStatusEntity : maps

DistributionStatus "1" *-- "many" ChannelStatusEntity : contains
ChannelStatusEntity --> DistributionStatus : belongs to

DistributionRequest --> ChannelType : uses
DistributionResponse --> ChannelDistributionResult : contains
ChannelDistributionResult --> ChannelType : uses
DistributionStatusResponse --> ChannelStatus : contains
ChannelStatus --> ChannelType : uses
ChannelStatusEntity --> ChannelType : uses

note top of DistributionService
  핵심 비즈니스 로직
  - 다중 채널 병렬 배포 실행
  - ExecutorService로 비동기 처리
  - Circuit Breaker 패턴 적용
  - Kafka 이벤트 발행
end note

note top of AbstractChannelAdapter
  Resilience4j 적용
  - @CircuitBreaker
  - @Retry (지수 백오프)
  - @Bulkhead
  - Fallback 처리
end note

note top of DistributionStatusRepository
  배포 상태 영구 저장
  - PostgreSQL 데이터베이스 사용
  - JPA Repository 패턴
  - Entity-DTO 매핑
end note

note top of ChannelType
  배포 채널 종류
  - TV: 우리동네TV, 지니TV
  - CALL: 링고비즈
  - SNS: Instagram, Naver, Kakao
end note

@enduml
