kt-event-marketing/design/backend/class/distribution-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

319 lines
11 KiB
Plaintext

@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