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

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