@startuml !theme mono title Content Service - 클래스 다이어그램 요약 (Clean Architecture) ' ============================================ ' 레이어 구조 표시 ' ============================================ package "Domain Layer" <> { class Content { - id: Long - eventId: String - images: List + addImage(image: GeneratedImage): void + getSelectedImages(): List } class GeneratedImage { - id: Long - eventId: String - style: ImageStyle - platform: Platform - cdnUrl: String + select(): void + deselect(): void } class Job { - id: String - eventId: String - status: Status - progress: int + start(): void + updateProgress(progress: int): void + complete(resultMessage: String): void + fail(errorMessage: String): void } enum ImageStyle { FANCY SIMPLE TRENDY } enum Platform { INSTAGRAM FACEBOOK KAKAO BLOG } } package "Application Layer" <> { package "Use Cases (Input Port)" { interface GenerateImagesUseCase { + execute(command: ContentCommand.GenerateImages): JobInfo } interface GetJobStatusUseCase { + execute(jobId: String): JobInfo } interface GetEventContentUseCase { + execute(eventId: String): ContentInfo } interface GetImageListUseCase { + execute(eventId: String, style: ImageStyle, platform: Platform): List } interface DeleteImageUseCase { + execute(imageId: Long): void } interface RegenerateImageUseCase { + execute(command: ContentCommand.RegenerateImage): JobInfo } } package "Ports (Output Port)" { interface ContentReader { + findByEventDraftIdWithImages(eventId: String): Optional } interface ContentWriter { + save(content: Content): Content + saveImage(image: GeneratedImage): GeneratedImage } interface JobReader { + getJob(jobId: String): Optional } interface JobWriter { + saveJob(jobData: RedisJobData, ttlSeconds: long): void + updateJobStatus(jobId: String, status: String, progress: Integer): void } interface CDNUploader { + upload(imageData: byte[], fileName: String): String } } package "Service Implementation" { class StableDiffusionImageGenerator implements GenerateImagesUseCase { - replicateClient: ReplicateApiClient - cdnUploader: CDNUploader - jobWriter: JobWriter - contentWriter: ContentWriter + execute(command: ContentCommand.GenerateImages): JobInfo } class JobManagementService implements GetJobStatusUseCase { - jobReader: JobReader + execute(jobId: String): JobInfo } class GetEventContentService implements GetEventContentUseCase { - contentReader: ContentReader + execute(eventId: String): ContentInfo } class GetImageListService implements GetImageListUseCase { - imageReader: ImageReader + execute(eventId: String, style: ImageStyle, platform: Platform): List } class DeleteImageService implements DeleteImageUseCase { - imageWriter: ImageWriter + execute(imageId: Long): void } class RegenerateImageService implements RegenerateImageUseCase { - imageReader: ImageReader - imageWriter: ImageWriter - jobWriter: JobWriter + execute(command: ContentCommand.RegenerateImage): JobInfo } } } package "Infrastructure Layer" <> { class RedisGateway implements ContentReader, ContentWriter, JobReader, JobWriter { - redisTemplate: RedisTemplate - objectMapper: ObjectMapper + getAIRecommendation(eventId: String): Optional> + saveJob(jobData: RedisJobData, ttlSeconds: long): void + getJob(jobId: String): Optional + save(content: Content): Content + saveImage(image: GeneratedImage): GeneratedImage } class ReplicateApiClient { - apiToken: String - baseUrl: String + createPrediction(request: ReplicateRequest): ReplicateResponse + getPrediction(predictionId: String): ReplicateResponse } class AzureBlobStorageUploader implements CDNUploader { - connectionString: String - containerName: String - circuitBreaker: CircuitBreaker + upload(imageData: byte[], fileName: String): String } } package "Presentation Layer" <> { class ContentController { - generateImagesUseCase: GenerateImagesUseCase - getJobStatusUseCase: GetJobStatusUseCase - getEventContentUseCase: GetEventContentUseCase - getImageListUseCase: GetImageListUseCase - deleteImageUseCase: DeleteImageUseCase - regenerateImageUseCase: RegenerateImageUseCase + generateImages(command: ContentCommand.GenerateImages): ResponseEntity + getJobStatus(jobId: String): ResponseEntity + getContentByEventId(eventId: String): ResponseEntity + deleteImage(imageId: Long): ResponseEntity } } ' ============================================ ' 관계 정의 ' ============================================ Content "1" o-- "0..*" GeneratedImage : contains GeneratedImage --> ImageStyle GeneratedImage --> Platform ContentController ..> GenerateImagesUseCase ContentController ..> GetJobStatusUseCase ContentController ..> GetEventContentUseCase ContentController ..> GetImageListUseCase ContentController ..> DeleteImageUseCase ContentController ..> RegenerateImageUseCase StableDiffusionImageGenerator ..> CDNUploader StableDiffusionImageGenerator ..> JobWriter StableDiffusionImageGenerator ..> ContentWriter StableDiffusionImageGenerator --> ReplicateApiClient JobManagementService ..> JobReader GetEventContentService ..> ContentReader GetImageListService ..> "ImageReader\n(extends ContentReader)" DeleteImageService ..> "ImageWriter\n(extends ContentWriter)" RegenerateImageService ..> "ImageReader\n(extends ContentReader)" RegenerateImageService ..> "ImageWriter\n(extends ContentWriter)" RegenerateImageService ..> JobWriter RedisGateway ..|> ContentReader RedisGateway ..|> ContentWriter RedisGateway ..|> JobReader RedisGateway ..|> JobWriter AzureBlobStorageUploader ..|> CDNUploader ' ============================================ ' 레이어 의존성 방향 ' ============================================ note top of Content : **Domain Layer**\n순수 비즈니스 로직\n외부 의존성 없음 note top of "Use Cases (Input Port)" : **Application Layer**\nUse Case 인터페이스 (Input Port)\n비즈니스 흐름 정의 note top of "Ports (Output Port)" : **Application Layer**\n외부 시스템 추상화 (Output Port)\n의존성 역전 원칙 note top of RedisGateway : **Infrastructure Layer**\nOutput Port 구현체\nRedis 저장소 연동 note top of ContentController : **Presentation Layer**\nREST API 컨트롤러\nHTTP 요청 처리 note bottom of ContentController : **의존성 방향**\nPresentation → Application → Domain\nInfrastructure → Application\n\n**핵심 원칙**\n• Domain은 다른 레이어에 의존하지 않음\n• Application은 Domain에만 의존\n• Infrastructure는 Application Port 구현\n• Presentation은 Application Use Case 호출 @enduml