@startuml !theme mono title Event Service 클래스 다이어그램 (상세) ' ============================== ' Domain Layer (핵심 비즈니스 로직) ' ============================== package "com.kt.event.eventservice.domain" { package "entity" { class Event extends BaseTimeEntity { - eventId: UUID - userId: UUID - storeId: UUID - eventName: String - description: String - objective: String - startDate: LocalDate - endDate: LocalDate - status: EventStatus - selectedImageId: UUID - selectedImageUrl: String - channels: List - generatedImages: Set - aiRecommendations: Set ' 비즈니스 로직 + updateEventName(eventName: String): void + updateDescription(description: String): void + updateEventPeriod(startDate: LocalDate, endDate: LocalDate): void + selectImage(imageId: UUID, imageUrl: String): void + updateChannels(channels: List): void + publish(): void + end(): void + addGeneratedImage(image: GeneratedImage): void + addAiRecommendation(recommendation: AiRecommendation): void + isModifiable(): boolean + isDeletable(): boolean } class AiRecommendation extends BaseTimeEntity { - recommendationId: UUID - event: Event - eventName: String - description: String - promotionType: String - targetAudience: String - isSelected: boolean } class GeneratedImage extends BaseTimeEntity { - imageId: UUID - event: Event - imageUrl: String - style: String - platform: String - isSelected: boolean } class Job extends BaseTimeEntity { - jobId: UUID - eventId: UUID - jobType: JobType - status: JobStatus - progress: int - resultKey: String - errorMessage: String - completedAt: LocalDateTime ' 비즈니스 로직 + start(): void + updateProgress(progress: int): void + complete(resultKey: String): void + fail(errorMessage: String): void } } package "enums" { enum EventStatus { DRAFT PUBLISHED ENDED } enum JobStatus { PENDING PROCESSING COMPLETED FAILED } enum JobType { AI_RECOMMENDATION IMAGE_GENERATION } } package "repository" { interface EventRepository extends JpaRepository { + findByEventIdAndUserId(eventId: UUID, userId: UUID): Optional + findEventsByUser(userId: UUID, status: EventStatus, search: String, objective: String, pageable: Pageable): Page } interface AiRecommendationRepository extends JpaRepository { + findByEvent(event: Event): List } interface GeneratedImageRepository extends JpaRepository { + findByEvent(event: Event): List } interface JobRepository extends JpaRepository { + findByEventId(eventId: UUID): List + findByJobTypeAndStatus(jobType: JobType, status: JobStatus): List } } } ' ============================== ' Application Layer (유스케이스) ' ============================== package "com.kt.event.eventservice.application" { package "service" { class EventService { - eventRepository: EventRepository - jobRepository: JobRepository - contentServiceClient: ContentServiceClient - aiJobKafkaProducer: AIJobKafkaProducer ' 이벤트 생명주기 관리 + createEvent(userId: UUID, storeId: UUID, request: SelectObjectiveRequest): EventCreatedResponse + getEvent(userId: UUID, eventId: UUID): EventDetailResponse + getEvents(userId: UUID, status: EventStatus, search: String, objective: String, pageable: Pageable): Page + deleteEvent(userId: UUID, eventId: UUID): void + publishEvent(userId: UUID, eventId: UUID): void + endEvent(userId: UUID, eventId: UUID): void + updateEvent(userId: UUID, eventId: UUID, request: UpdateEventRequest): EventDetailResponse ' AI 추천 관리 + requestAiRecommendations(userId: UUID, eventId: UUID, request: AiRecommendationRequest): JobAcceptedResponse + selectRecommendation(userId: UUID, eventId: UUID, request: SelectRecommendationRequest): void ' 이미지 관리 + requestImageGeneration(userId: UUID, eventId: UUID, request: ImageGenerationRequest): ImageGenerationResponse + selectImage(userId: UUID, eventId: UUID, imageId: UUID, request: SelectImageRequest): void + editImage(userId: UUID, eventId: UUID, imageId: UUID, request: ImageEditRequest): ImageEditResponse ' 배포 채널 관리 + selectChannels(userId: UUID, eventId: UUID, request: SelectChannelsRequest): void ' Helper Methods - mapToDetailResponse(event: Event): EventDetailResponse } class JobService { - jobRepository: JobRepository + createJob(eventId: UUID, jobType: JobType): Job + getJobStatus(jobId: UUID): JobStatusResponse + updateJobProgress(jobId: UUID, progress: int): void + completeJob(jobId: UUID, resultKey: String): void + failJob(jobId: UUID, errorMessage: String): void - mapToJobStatusResponse(job: Job): JobStatusResponse } } package "dto.request" { class SelectObjectiveRequest { - objective: String } class AiRecommendationRequest { - storeInfo: StoreInfo + StoreInfo { - storeName: String - category: String - description: String } } class SelectRecommendationRequest { - recommendationId: UUID - customizations: Customizations + Customizations { - eventName: String - description: String - startDate: LocalDate - endDate: LocalDate } } class ImageGenerationRequest { - styles: List - platforms: List } class SelectImageRequest { - imageId: UUID - imageUrl: String } class ImageEditRequest { - editInstructions: String } class SelectChannelsRequest { - channels: List } class UpdateEventRequest { - eventName: String - description: String - startDate: LocalDate - endDate: LocalDate } } package "dto.response" { class EventCreatedResponse { - eventId: UUID - status: EventStatus - objective: String - createdAt: LocalDateTime } class EventDetailResponse { - eventId: UUID - userId: UUID - storeId: UUID - eventName: String - description: String - objective: String - startDate: LocalDate - endDate: LocalDate - status: EventStatus - selectedImageId: UUID - selectedImageUrl: String - generatedImages: List - aiRecommendations: List - channels: List - createdAt: LocalDateTime - updatedAt: LocalDateTime + GeneratedImageDto { - imageId: UUID - imageUrl: String - style: String - platform: String - isSelected: boolean - createdAt: LocalDateTime } + AiRecommendationDto { - recommendationId: UUID - eventName: String - description: String - promotionType: String - targetAudience: String - isSelected: boolean } } class JobAcceptedResponse { - jobId: UUID - status: JobStatus - message: String } class JobStatusResponse { - jobId: UUID - jobType: JobType - status: JobStatus - progress: int - resultKey: String - errorMessage: String - createdAt: LocalDateTime - completedAt: LocalDateTime } class ImageGenerationResponse { - jobId: UUID - status: String - message: String - createdAt: LocalDateTime } class ImageEditResponse { - imageId: UUID - imageUrl: String - editedAt: LocalDateTime } } package "dto.kafka" { class AIEventGenerationJobMessage { - jobId: String - userId: Long - status: String - createdAt: LocalDateTime - errorMessage: String } class EventCreatedMessage { - eventId: String - userId: Long - storeId: Long - objective: String - status: String - createdAt: LocalDateTime } class ImageGenerationJobMessage { - jobId: String - eventId: String - styles: List - platforms: List - status: String - createdAt: LocalDateTime } } } ' ============================== ' Infrastructure Layer (기술 구현) ' ============================== package "com.kt.event.eventservice.infrastructure" { package "kafka" { class AIJobKafkaProducer { - kafkaTemplate: KafkaTemplate - aiEventGenerationJobTopic: String + publishAIGenerationJob(jobId: String, userId: Long, eventId: String, storeName: String, storeCategory: String, storeDescription: String, objective: String): void + publishMessage(message: AIEventGenerationJobMessage): void } class AIJobKafkaConsumer { - objectMapper: ObjectMapper + consumeAIEventGenerationJob(payload: String, partition: int, offset: long, acknowledgment: Acknowledgment): void - processAIEventGenerationJob(message: AIEventGenerationJobMessage): void } class ImageJobKafkaConsumer { - objectMapper: ObjectMapper + consumeImageGenerationJob(payload: String, partition: int, offset: long, acknowledgment: Acknowledgment): void - processImageGenerationJob(message: ImageGenerationJobMessage): void } class EventKafkaProducer { - kafkaTemplate: KafkaTemplate - eventCreatedTopic: String + publishEventCreated(event: Event): void } } package "client" { interface ContentServiceClient { + generateImages(request: ContentImageGenerationRequest): ContentJobResponse } } package "client.dto" { class ContentImageGenerationRequest { - eventDraftId: Long - eventTitle: String - eventDescription: String - styles: List - platforms: List } class ContentJobResponse { - id: String - status: String - createdAt: LocalDateTime } } package "config" { class RedisConfig { - host: String - port: int + redisConnectionFactory(): RedisConnectionFactory + redisTemplate(): RedisTemplate } } } ' ============================== ' Presentation Layer (API 엔드포인트) ' ============================== package "com.kt.event.eventservice.presentation" { package "controller" { class EventController { - eventService: EventService + selectObjective(request: SelectObjectiveRequest, userPrincipal: UserPrincipal): ResponseEntity> + getEvents(status: EventStatus, search: String, objective: String, page: int, size: int, sort: String, order: String, userPrincipal: UserPrincipal): ResponseEntity>> + getEvent(eventId: UUID, userPrincipal: UserPrincipal): ResponseEntity> + deleteEvent(eventId: UUID, userPrincipal: UserPrincipal): ResponseEntity> + publishEvent(eventId: UUID, userPrincipal: UserPrincipal): ResponseEntity> + endEvent(eventId: UUID, userPrincipal: UserPrincipal): ResponseEntity> + requestImageGeneration(eventId: UUID, request: ImageGenerationRequest, userPrincipal: UserPrincipal): ResponseEntity> + selectImage(eventId: UUID, imageId: UUID, request: SelectImageRequest, userPrincipal: UserPrincipal): ResponseEntity> + requestAiRecommendations(eventId: UUID, request: AiRecommendationRequest, userPrincipal: UserPrincipal): ResponseEntity> + selectRecommendation(eventId: UUID, request: SelectRecommendationRequest, userPrincipal: UserPrincipal): ResponseEntity> + editImage(eventId: UUID, imageId: UUID, request: ImageEditRequest, userPrincipal: UserPrincipal): ResponseEntity> + selectChannels(eventId: UUID, request: SelectChannelsRequest, userPrincipal: UserPrincipal): ResponseEntity> + updateEvent(eventId: UUID, request: UpdateEventRequest, userPrincipal: UserPrincipal): ResponseEntity> } class JobController { - jobService: JobService + getJobStatus(jobId: UUID): ResponseEntity> } } } ' ============================== ' Config Layer (설정) ' ============================== package "com.kt.event.eventservice.config" { class SecurityConfig { + securityFilterChain(http: HttpSecurity): SecurityFilterChain + corsConfigurationSource(): CorsConfigurationSource } class KafkaConfig { + producerFactory(): ProducerFactory + kafkaTemplate(): KafkaTemplate + consumerFactory(): ConsumerFactory + kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory } class DevAuthenticationFilter extends OncePerRequestFilter { + doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain): void } } ' ============================== ' Common Layer (공통 컴포넌트) ' ============================== package "com.kt.event.common" <> { abstract class BaseTimeEntity { - createdAt: LocalDateTime - updatedAt: LocalDateTime } class "ApiResponse" { - success: boolean - data: T - errorCode: String - message: String - timestamp: LocalDateTime } class "PageResponse" { - content: List - totalElements: long - totalPages: int - number: int - size: int - first: boolean - last: boolean } class BusinessException extends RuntimeException { - errorCode: ErrorCode - details: String } interface ErrorCode { + getCode(): String + getMessage(): String } } ' ============================== ' 관계 정의 ' ============================== ' Domain Layer Relationships Event "1" *-- "many" GeneratedImage : contains > Event "1" *-- "many" AiRecommendation : contains > Event ..> EventStatus : uses Job ..> JobType : uses Job ..> JobStatus : uses EventRepository ..> Event : manages AiRecommendationRepository ..> AiRecommendation : manages GeneratedImageRepository ..> GeneratedImage : manages JobRepository ..> Job : manages ' Application Layer Relationships EventService --> EventRepository : uses EventService --> JobRepository : uses EventService --> ContentServiceClient : uses EventService --> AIJobKafkaProducer : uses JobService --> JobRepository : uses EventService ..> SelectObjectiveRequest : uses EventService ..> AiRecommendationRequest : uses EventService ..> SelectRecommendationRequest : uses EventService ..> ImageGenerationRequest : uses EventService ..> SelectImageRequest : uses EventService ..> SelectChannelsRequest : uses EventService ..> UpdateEventRequest : uses EventService ..> EventCreatedResponse : creates EventService ..> EventDetailResponse : creates EventService ..> JobAcceptedResponse : creates EventService ..> ImageGenerationResponse : creates JobService ..> JobStatusResponse : creates ' Infrastructure Layer Relationships AIJobKafkaProducer ..> AIEventGenerationJobMessage : publishes AIJobKafkaConsumer ..> AIEventGenerationJobMessage : consumes ImageJobKafkaConsumer ..> ImageGenerationJobMessage : consumes EventKafkaProducer ..> EventCreatedMessage : publishes ContentServiceClient ..> ContentImageGenerationRequest : uses ContentServiceClient ..> ContentJobResponse : returns ' Presentation Layer Relationships EventController --> EventService : uses JobController --> JobService : uses EventController ..> ApiResponse : uses EventController ..> PageResponse : uses ' Common Layer Inheritance Event --|> BaseTimeEntity AiRecommendation --|> BaseTimeEntity GeneratedImage --|> BaseTimeEntity Job --|> BaseTimeEntity ' Notes note top of Event **핵심 도메인 엔티티** - 이벤트 생명주기 관리 (DRAFT → PUBLISHED → ENDED) - 상태 머신 패턴 적용 - 비즈니스 규칙 캡슐화 - 불변성 보장 (수정 메서드를 통한 변경) end note note top of EventService **핵심 유스케이스 오케스트레이터** - 이벤트 전체 생명주기 조율 - AI 서비스 연동 (Kafka) - Content 서비스 연동 (Feign) - 트랜잭션 경계 관리 end note note top of AIJobKafkaProducer **비동기 작업 발행자** - AI 추천 생성 작업 발행 - 장시간 작업의 비동기 처리 - Kafka 메시지 발행 end note note bottom of EventController **REST API 엔드포인트** - 이벤트 생성부터 배포까지 전체 API - 인증/인가 처리 - 입력 검증 - 표준 응답 포맷 (ApiResponse) end note @enduml