mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 20:46:24 +00:00
✨ 주요 기능 - 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>
580 lines
19 KiB
Plaintext
580 lines
19 KiB
Plaintext
@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<String>
|
|
- generatedImages: Set<GeneratedImage>
|
|
- aiRecommendations: Set<AiRecommendation>
|
|
|
|
' 비즈니스 로직
|
|
+ updateEventName(eventName: String): void
|
|
+ updateDescription(description: String): void
|
|
+ updateEventPeriod(startDate: LocalDate, endDate: LocalDate): void
|
|
+ selectImage(imageId: UUID, imageUrl: String): void
|
|
+ updateChannels(channels: List<String>): 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<Event>
|
|
+ findEventsByUser(userId: UUID, status: EventStatus, search: String, objective: String, pageable: Pageable): Page<Event>
|
|
}
|
|
|
|
interface AiRecommendationRepository extends JpaRepository {
|
|
+ findByEvent(event: Event): List<AiRecommendation>
|
|
}
|
|
|
|
interface GeneratedImageRepository extends JpaRepository {
|
|
+ findByEvent(event: Event): List<GeneratedImage>
|
|
}
|
|
|
|
interface JobRepository extends JpaRepository {
|
|
+ findByEventId(eventId: UUID): List<Job>
|
|
+ findByJobTypeAndStatus(jobType: JobType, status: JobStatus): List<Job>
|
|
}
|
|
}
|
|
}
|
|
|
|
' ==============================
|
|
' 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<EventDetailResponse>
|
|
+ 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<String>
|
|
- platforms: List<String>
|
|
}
|
|
|
|
class SelectImageRequest {
|
|
- imageId: UUID
|
|
- imageUrl: String
|
|
}
|
|
|
|
class ImageEditRequest {
|
|
- editInstructions: String
|
|
}
|
|
|
|
class SelectChannelsRequest {
|
|
- channels: List<String>
|
|
}
|
|
|
|
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<GeneratedImageDto>
|
|
- aiRecommendations: List<AiRecommendationDto>
|
|
- channels: List<String>
|
|
- 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<String>
|
|
- platforms: List<String>
|
|
- status: String
|
|
- createdAt: LocalDateTime
|
|
}
|
|
}
|
|
}
|
|
|
|
' ==============================
|
|
' Infrastructure Layer (기술 구현)
|
|
' ==============================
|
|
package "com.kt.event.eventservice.infrastructure" {
|
|
|
|
package "kafka" {
|
|
class AIJobKafkaProducer {
|
|
- kafkaTemplate: KafkaTemplate<String, Object>
|
|
- 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<String, Object>
|
|
- 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<String>
|
|
- platforms: List<String>
|
|
}
|
|
|
|
class ContentJobResponse {
|
|
- id: String
|
|
- status: String
|
|
- createdAt: LocalDateTime
|
|
}
|
|
}
|
|
|
|
package "config" {
|
|
class RedisConfig {
|
|
- host: String
|
|
- port: int
|
|
|
|
+ redisConnectionFactory(): RedisConnectionFactory
|
|
+ redisTemplate(): RedisTemplate<String, Object>
|
|
}
|
|
}
|
|
}
|
|
|
|
' ==============================
|
|
' Presentation Layer (API 엔드포인트)
|
|
' ==============================
|
|
package "com.kt.event.eventservice.presentation" {
|
|
|
|
package "controller" {
|
|
class EventController {
|
|
- eventService: EventService
|
|
|
|
+ selectObjective(request: SelectObjectiveRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<EventCreatedResponse>>
|
|
+ getEvents(status: EventStatus, search: String, objective: String, page: int, size: int, sort: String, order: String, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<PageResponse<EventDetailResponse>>>
|
|
+ getEvent(eventId: UUID, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<EventDetailResponse>>
|
|
+ deleteEvent(eventId: UUID, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<Void>>
|
|
+ publishEvent(eventId: UUID, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<Void>>
|
|
+ endEvent(eventId: UUID, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<Void>>
|
|
+ requestImageGeneration(eventId: UUID, request: ImageGenerationRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<ImageGenerationResponse>>
|
|
+ selectImage(eventId: UUID, imageId: UUID, request: SelectImageRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<Void>>
|
|
+ requestAiRecommendations(eventId: UUID, request: AiRecommendationRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<JobAcceptedResponse>>
|
|
+ selectRecommendation(eventId: UUID, request: SelectRecommendationRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<Void>>
|
|
+ editImage(eventId: UUID, imageId: UUID, request: ImageEditRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<ImageEditResponse>>
|
|
+ selectChannels(eventId: UUID, request: SelectChannelsRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<Void>>
|
|
+ updateEvent(eventId: UUID, request: UpdateEventRequest, userPrincipal: UserPrincipal): ResponseEntity<ApiResponse<EventDetailResponse>>
|
|
}
|
|
|
|
class JobController {
|
|
- jobService: JobService
|
|
|
|
+ getJobStatus(jobId: UUID): ResponseEntity<ApiResponse<JobStatusResponse>>
|
|
}
|
|
}
|
|
}
|
|
|
|
' ==============================
|
|
' Config Layer (설정)
|
|
' ==============================
|
|
package "com.kt.event.eventservice.config" {
|
|
class SecurityConfig {
|
|
+ securityFilterChain(http: HttpSecurity): SecurityFilterChain
|
|
+ corsConfigurationSource(): CorsConfigurationSource
|
|
}
|
|
|
|
class KafkaConfig {
|
|
+ producerFactory(): ProducerFactory<String, Object>
|
|
+ kafkaTemplate(): KafkaTemplate<String, Object>
|
|
+ consumerFactory(): ConsumerFactory<String, String>
|
|
+ kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory<String, String>
|
|
}
|
|
|
|
class DevAuthenticationFilter extends OncePerRequestFilter {
|
|
+ doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain): void
|
|
}
|
|
}
|
|
|
|
' ==============================
|
|
' Common Layer (공통 컴포넌트)
|
|
' ==============================
|
|
package "com.kt.event.common" <<external>> {
|
|
abstract class BaseTimeEntity {
|
|
- createdAt: LocalDateTime
|
|
- updatedAt: LocalDateTime
|
|
}
|
|
|
|
class "ApiResponse<T>" {
|
|
- success: boolean
|
|
- data: T
|
|
- errorCode: String
|
|
- message: String
|
|
- timestamp: LocalDateTime
|
|
}
|
|
|
|
class "PageResponse<T>" {
|
|
- content: List<T>
|
|
- 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
|