@startuml !theme mono title Distribution Service 클래스 다이어그램 (상세) package "com.kt.distribution" { package "controller" { class DistributionController { - distributionService: DistributionService + distribute(request: DistributionRequest): ResponseEntity + getDistributionStatus(eventId: String): ResponseEntity } } package "service" { class DistributionService { - channelAdapters: List - kafkaEventPublisher: Optional - statusRepository: DistributionStatusRepository - executorService: ExecutorService + distribute(request: DistributionRequest): DistributionResponse + getDistributionStatus(eventId: String): DistributionStatusResponse - saveInProgressStatus(eventId: String, channels: List, startedAt: LocalDateTime): void - saveCompletedStatus(eventId: String, results: List, startedAt: LocalDateTime, completedAt: LocalDateTime, successCount: long, failureCount: long): void - convertToChannelStatus(result: ChannelDistributionResult, eventId: String, completedAt: LocalDateTime): ChannelStatus - publishDistributionCompletedEvent(eventId: String, results: List): void } class KafkaEventPublisher { - kafkaTemplate: KafkaTemplate - 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 - channelSettings: Map> } class DistributionResponse { - eventId: String - success: boolean - channelResults: List - 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 } class ChannelStatus { - channel: ChannelType - status: String - progress: Integer - distributionId: String - estimatedViews: Integer - updateTimestamp: LocalDateTime - eventId: String - impressionSchedule: List - 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 + delete(eventId: String): void + deleteAll(): void } interface DistributionStatusJpaRepository { + findByEventIdWithChannels(eventId: String): Optional + deleteByEventId(eventId: String): void } } package "entity" { class DistributionStatus { - id: Long - eventId: String - overallStatus: String - startedAt: LocalDateTime - completedAt: LocalDateTime - channels: List - 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 - completedAt: LocalDateTime } class DistributedChannelInfo { - channel: String - channelType: String - status: String - expectedViews: Integer } } package "config" { class KafkaConfig { + kafkaTemplate(): KafkaTemplate + producerFactory(): ProducerFactory } 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