mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2026-06-13 11:39:11 +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>
This commit is contained in:
@@ -0,0 +1,528 @@
|
||||
@startuml
|
||||
!theme mono
|
||||
|
||||
title Content Service - 클래스 다이어그램 (Clean Architecture)
|
||||
|
||||
' ============================================
|
||||
' Domain Layer (엔티티 및 비즈니스 로직)
|
||||
' ============================================
|
||||
package "com.kt.event.content.biz.domain" <<Rectangle>> {
|
||||
|
||||
class Content {
|
||||
- id: Long
|
||||
- eventId: String
|
||||
- eventTitle: String
|
||||
- eventDescription: String
|
||||
- images: List<GeneratedImage>
|
||||
- createdAt: LocalDateTime
|
||||
- updatedAt: LocalDateTime
|
||||
+ addImage(image: GeneratedImage): void
|
||||
+ getSelectedImages(): List<GeneratedImage>
|
||||
+ getImagesByStyle(style: ImageStyle): List<GeneratedImage>
|
||||
+ getImagesByPlatform(platform: Platform): List<GeneratedImage>
|
||||
}
|
||||
|
||||
class GeneratedImage {
|
||||
- id: Long
|
||||
- eventId: String
|
||||
- style: ImageStyle
|
||||
- platform: Platform
|
||||
- cdnUrl: String
|
||||
- prompt: String
|
||||
- selected: boolean
|
||||
- createdAt: LocalDateTime
|
||||
- updatedAt: LocalDateTime
|
||||
+ select(): void
|
||||
+ deselect(): void
|
||||
}
|
||||
|
||||
class Job {
|
||||
- id: String
|
||||
- eventId: String
|
||||
- jobType: String
|
||||
- status: Status
|
||||
- progress: int
|
||||
- resultMessage: String
|
||||
- errorMessage: String
|
||||
- createdAt: LocalDateTime
|
||||
- updatedAt: LocalDateTime
|
||||
+ start(): void
|
||||
+ updateProgress(progress: int): void
|
||||
+ complete(resultMessage: String): void
|
||||
+ fail(errorMessage: String): void
|
||||
+ isProcessing(): boolean
|
||||
+ isCompleted(): boolean
|
||||
+ isFailed(): boolean
|
||||
}
|
||||
|
||||
enum JobStatus {
|
||||
PENDING
|
||||
PROCESSING
|
||||
COMPLETED
|
||||
FAILED
|
||||
}
|
||||
|
||||
enum ImageStyle {
|
||||
FANCY
|
||||
SIMPLE
|
||||
TRENDY
|
||||
+ getDescription(): String
|
||||
}
|
||||
|
||||
enum Platform {
|
||||
INSTAGRAM
|
||||
FACEBOOK
|
||||
KAKAO
|
||||
BLOG
|
||||
+ getWidth(): int
|
||||
+ getHeight(): int
|
||||
+ getAspectRatio(): String
|
||||
}
|
||||
}
|
||||
|
||||
' ============================================
|
||||
' Application Layer (Use Cases)
|
||||
' ============================================
|
||||
package "com.kt.event.content.biz.usecase" <<Rectangle>> {
|
||||
|
||||
package "in" {
|
||||
interface GenerateImagesUseCase {
|
||||
+ execute(command: GenerateImagesCommand): JobInfo
|
||||
}
|
||||
|
||||
interface GetJobStatusUseCase {
|
||||
+ execute(jobId: String): JobInfo
|
||||
}
|
||||
|
||||
interface GetEventContentUseCase {
|
||||
+ execute(eventId: String): ContentInfo
|
||||
}
|
||||
|
||||
interface GetImageListUseCase {
|
||||
+ execute(eventId: String, style: ImageStyle, platform: Platform): List<ImageInfo>
|
||||
}
|
||||
|
||||
interface GetImageDetailUseCase {
|
||||
+ execute(imageId: Long): ImageInfo
|
||||
}
|
||||
|
||||
interface RegenerateImageUseCase {
|
||||
+ execute(command: RegenerateImageCommand): JobInfo
|
||||
}
|
||||
|
||||
interface DeleteImageUseCase {
|
||||
+ execute(imageId: Long): void
|
||||
}
|
||||
}
|
||||
|
||||
package "out" {
|
||||
interface ContentReader {
|
||||
+ findByEventDraftIdWithImages(eventId: String): Optional<Content>
|
||||
+ findImageById(imageId: Long): Optional<GeneratedImage>
|
||||
+ findImagesByEventDraftId(eventId: String): List<GeneratedImage>
|
||||
}
|
||||
|
||||
interface ContentWriter {
|
||||
+ save(content: Content): Content
|
||||
+ saveImage(image: GeneratedImage): GeneratedImage
|
||||
+ getImageById(imageId: Long): GeneratedImage
|
||||
+ deleteImageById(imageId: Long): void
|
||||
}
|
||||
|
||||
interface ImageReader {
|
||||
+ getImage(eventId: String, style: ImageStyle, platform: Platform): Optional<RedisImageData>
|
||||
+ getImagesByEventId(eventId: String): List<RedisImageData>
|
||||
}
|
||||
|
||||
interface ImageWriter {
|
||||
+ saveImage(image: GeneratedImage): GeneratedImage
|
||||
+ getImageById(imageId: Long): GeneratedImage
|
||||
+ deleteImageById(imageId: Long): void
|
||||
}
|
||||
|
||||
interface JobReader {
|
||||
+ getJob(jobId: String): Optional<RedisJobData>
|
||||
}
|
||||
|
||||
interface JobWriter {
|
||||
+ saveJob(jobData: RedisJobData, ttlSeconds: long): void
|
||||
+ updateJobStatus(jobId: String, status: String, progress: Integer): void
|
||||
+ updateJobResult(jobId: String, resultMessage: String): void
|
||||
+ updateJobError(jobId: String, errorMessage: String): void
|
||||
}
|
||||
|
||||
interface RedisAIDataReader {
|
||||
+ getAIRecommendation(eventId: String): Optional<Map<String, Object>>
|
||||
}
|
||||
|
||||
interface RedisImageWriter {
|
||||
+ cacheImages(eventId: String, images: List<GeneratedImage>, ttlSeconds: long): void
|
||||
}
|
||||
|
||||
interface CDNUploader {
|
||||
+ upload(imageData: byte[], fileName: String): String
|
||||
}
|
||||
|
||||
interface ImageGeneratorCaller {
|
||||
+ generateImage(prompt: String, width: int, height: int): String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
' ============================================
|
||||
' Application Service Layer
|
||||
' ============================================
|
||||
package "com.kt.event.content.biz.service" <<Rectangle>> {
|
||||
|
||||
class StableDiffusionImageGenerator implements GenerateImagesUseCase {
|
||||
- replicateClient: ReplicateApiClient
|
||||
- cdnUploader: CDNUploader
|
||||
- jobWriter: JobWriter
|
||||
- contentWriter: ContentWriter
|
||||
- circuitBreaker: CircuitBreaker
|
||||
- modelVersion: String
|
||||
+ execute(command: GenerateImagesCommand): JobInfo
|
||||
- processImageGeneration(jobId: String, command: GenerateImagesCommand): void
|
||||
- generateImage(prompt: String, platform: Platform): String
|
||||
- waitForCompletion(predictionId: String): String
|
||||
- buildPrompt(command: GenerateImagesCommand, style: ImageStyle, platform: Platform): String
|
||||
- downloadImage(imageUrl: String): byte[]
|
||||
- createPredictionWithCircuitBreaker(request: ReplicateRequest): ReplicateResponse
|
||||
- getPredictionWithCircuitBreaker(predictionId: String): ReplicateResponse
|
||||
}
|
||||
|
||||
class JobManagementService implements GetJobStatusUseCase {
|
||||
- jobReader: JobReader
|
||||
+ execute(jobId: String): JobInfo
|
||||
}
|
||||
|
||||
class GetEventContentService implements GetEventContentUseCase {
|
||||
- contentReader: ContentReader
|
||||
- redisAIDataReader: RedisAIDataReader
|
||||
+ execute(eventId: String): ContentInfo
|
||||
}
|
||||
|
||||
class GetImageListService implements GetImageListUseCase {
|
||||
- imageReader: ImageReader
|
||||
+ execute(eventId: String, style: ImageStyle, platform: Platform): List<ImageInfo>
|
||||
}
|
||||
|
||||
class GetImageDetailService implements GetImageDetailUseCase {
|
||||
- imageReader: ImageReader
|
||||
+ execute(imageId: Long): ImageInfo
|
||||
}
|
||||
|
||||
class RegenerateImageService implements RegenerateImageUseCase {
|
||||
- imageReader: ImageReader
|
||||
- imageWriter: ImageWriter
|
||||
- jobWriter: JobWriter
|
||||
- cdnUploader: CDNUploader
|
||||
- replicateClient: ReplicateApiClient
|
||||
+ execute(command: RegenerateImageCommand): JobInfo
|
||||
}
|
||||
|
||||
class DeleteImageService implements DeleteImageUseCase {
|
||||
- imageWriter: ImageWriter
|
||||
+ execute(imageId: Long): void
|
||||
}
|
||||
}
|
||||
|
||||
' ============================================
|
||||
' DTO Layer
|
||||
' ============================================
|
||||
package "com.kt.event.content.biz.dto" <<Rectangle>> {
|
||||
|
||||
class ContentCommand
|
||||
|
||||
class GenerateImagesCommand {
|
||||
- eventId: String
|
||||
- eventTitle: String
|
||||
- eventDescription: String
|
||||
- industry: String
|
||||
- location: String
|
||||
- trends: List<String>
|
||||
- styles: List<ImageStyle>
|
||||
- platforms: List<Platform>
|
||||
}
|
||||
|
||||
class RegenerateImageCommand {
|
||||
- imageId: Long
|
||||
- newPrompt: String
|
||||
}
|
||||
|
||||
class ContentInfo {
|
||||
- id: Long
|
||||
- eventId: String
|
||||
- eventTitle: String
|
||||
- eventDescription: String
|
||||
- images: List<ImageInfo>
|
||||
- createdAt: LocalDateTime
|
||||
- updatedAt: LocalDateTime
|
||||
+ {static} from(content: Content): ContentInfo
|
||||
}
|
||||
|
||||
class ImageInfo {
|
||||
- id: Long
|
||||
- eventId: String
|
||||
- style: ImageStyle
|
||||
- platform: Platform
|
||||
- cdnUrl: String
|
||||
- prompt: String
|
||||
- selected: boolean
|
||||
- createdAt: LocalDateTime
|
||||
- updatedAt: LocalDateTime
|
||||
+ {static} from(image: GeneratedImage): ImageInfo
|
||||
}
|
||||
|
||||
class JobInfo {
|
||||
- id: String
|
||||
- eventId: String
|
||||
- jobType: String
|
||||
- status: String
|
||||
- progress: int
|
||||
- resultMessage: String
|
||||
- errorMessage: String
|
||||
- createdAt: LocalDateTime
|
||||
- updatedAt: LocalDateTime
|
||||
+ {static} from(job: Job): JobInfo
|
||||
}
|
||||
|
||||
class RedisJobData {
|
||||
- id: String
|
||||
- eventId: String
|
||||
- jobType: String
|
||||
- status: String
|
||||
- progress: int
|
||||
- resultMessage: String
|
||||
- errorMessage: String
|
||||
- createdAt: LocalDateTime
|
||||
- updatedAt: LocalDateTime
|
||||
}
|
||||
|
||||
class RedisImageData {
|
||||
- eventId: String
|
||||
- style: ImageStyle
|
||||
- platform: Platform
|
||||
- imageUrl: String
|
||||
- prompt: String
|
||||
- createdAt: LocalDateTime
|
||||
}
|
||||
|
||||
class RedisAIEventData {
|
||||
- eventId: String
|
||||
- recommendedStyles: List<String>
|
||||
- recommendedKeywords: List<String>
|
||||
- cachedAt: LocalDateTime
|
||||
}
|
||||
}
|
||||
|
||||
' ============================================
|
||||
' Infrastructure Layer (Gateway & Adapter)
|
||||
' ============================================
|
||||
package "com.kt.event.content.infra.gateway" <<Rectangle>> {
|
||||
|
||||
class RedisGateway implements ContentReader, ContentWriter, ImageReader, ImageWriter, JobReader, JobWriter, RedisAIDataReader, RedisImageWriter {
|
||||
- redisTemplate: RedisTemplate<String, Object>
|
||||
- objectMapper: ObjectMapper
|
||||
- nextContentId: Long
|
||||
- nextImageId: Long
|
||||
+ getAIRecommendation(eventId: String): Optional<Map<String, Object>>
|
||||
+ cacheImages(eventId: String, images: List<GeneratedImage>, ttlSeconds: long): void
|
||||
+ saveImage(imageData: RedisImageData, ttlSeconds: long): void
|
||||
+ getImage(eventId: String, style: ImageStyle, platform: Platform): Optional<RedisImageData>
|
||||
+ getImagesByEventId(eventId: String): List<RedisImageData>
|
||||
+ deleteImage(eventId: String, style: ImageStyle, platform: Platform): void
|
||||
+ saveImages(eventId: String, images: List<RedisImageData>, ttlSeconds: long): void
|
||||
+ saveJob(jobData: RedisJobData, ttlSeconds: long): void
|
||||
+ getJob(jobId: String): Optional<RedisJobData>
|
||||
+ updateJobStatus(jobId: String, status: String, progress: Integer): void
|
||||
+ updateJobResult(jobId: String, resultMessage: String): void
|
||||
+ updateJobError(jobId: String, errorMessage: String): void
|
||||
+ findByEventDraftIdWithImages(eventId: String): Optional<Content>
|
||||
+ findImageById(imageId: Long): Optional<GeneratedImage>
|
||||
+ findImagesByEventDraftId(eventId: String): List<GeneratedImage>
|
||||
+ save(content: Content): Content
|
||||
+ saveImage(image: GeneratedImage): GeneratedImage
|
||||
+ getImageById(imageId: Long): GeneratedImage
|
||||
+ deleteImageById(imageId: Long): void
|
||||
- buildImageKey(eventId: String, style: ImageStyle, platform: Platform): String
|
||||
- getString(map: Map<Object, Object>, key: String): String
|
||||
- getLong(map: Map<Object, Object>, key: String): Long
|
||||
- getInteger(map: Map<Object, Object>, key: String): Integer
|
||||
- getLocalDateTime(map: Map<Object, Object>, key: String): LocalDateTime
|
||||
}
|
||||
|
||||
package "client" {
|
||||
class ReplicateApiClient {
|
||||
- apiToken: String
|
||||
- baseUrl: String
|
||||
- restClient: RestClient
|
||||
+ createPrediction(request: ReplicateRequest): ReplicateResponse
|
||||
+ getPrediction(predictionId: String): ReplicateResponse
|
||||
}
|
||||
|
||||
class AzureBlobStorageUploader implements CDNUploader {
|
||||
- connectionString: String
|
||||
- containerName: String
|
||||
- circuitBreaker: CircuitBreaker
|
||||
- blobServiceClient: BlobServiceClient
|
||||
- containerClient: BlobContainerClient
|
||||
+ init(): void
|
||||
+ upload(imageData: byte[], fileName: String): String
|
||||
- doUpload(imageData: byte[], fileName: String): String
|
||||
- generateBlobName(fileName: String): String
|
||||
}
|
||||
|
||||
class ReplicateRequest {
|
||||
- version: String
|
||||
- input: Input
|
||||
}
|
||||
|
||||
class ReplicateInputRequest {
|
||||
- prompt: String
|
||||
- negativePrompt: String
|
||||
- width: int
|
||||
- height: int
|
||||
- numOutputs: int
|
||||
- guidanceScale: double
|
||||
- numInferenceSteps: int
|
||||
- seed: long
|
||||
}
|
||||
|
||||
class ReplicateResponse {
|
||||
- id: String
|
||||
- status: String
|
||||
- output: List<String>
|
||||
- error: String
|
||||
}
|
||||
|
||||
class ReplicateApiConfig {
|
||||
- apiToken: String
|
||||
- baseUrl: String
|
||||
+ restClient(): RestClient
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
' ============================================
|
||||
' Presentation Layer (REST Controller)
|
||||
' ============================================
|
||||
package "com.kt.event.content.infra.web.controller" <<Rectangle>> {
|
||||
|
||||
class ContentController {
|
||||
- generateImagesUseCase: GenerateImagesUseCase
|
||||
- getJobStatusUseCase: GetJobStatusUseCase
|
||||
- getEventContentUseCase: GetEventContentUseCase
|
||||
- getImageListUseCase: GetImageListUseCase
|
||||
- getImageDetailUseCase: GetImageDetailUseCase
|
||||
- regenerateImageUseCase: RegenerateImageUseCase
|
||||
- deleteImageUseCase: DeleteImageUseCase
|
||||
+ generateImages(command: GenerateImagesCommand): ResponseEntity<JobInfo>
|
||||
+ getJobStatus(jobId: String): ResponseEntity<JobInfo>
|
||||
+ getContentByEventId(eventId: String): ResponseEntity<ContentInfo>
|
||||
+ getImages(eventId: String, style: String, platform: String): ResponseEntity<List<ImageInfo>>
|
||||
+ getImageById(imageId: Long): ResponseEntity<ImageInfo>
|
||||
+ deleteImage(imageId: Long): ResponseEntity<Void>
|
||||
+ regenerateImage(imageId: Long, requestBody: RegenerateImageCommand): ResponseEntity<JobInfo>
|
||||
}
|
||||
}
|
||||
|
||||
' ============================================
|
||||
' Configuration Layer
|
||||
' ============================================
|
||||
package "com.kt.event.content.infra.config" <<Rectangle>> {
|
||||
|
||||
class RedisConfig {
|
||||
- host: String
|
||||
- port: int
|
||||
+ redisConnectionFactory(): RedisConnectionFactory
|
||||
+ redisTemplate(): RedisTemplate<String, Object>
|
||||
+ objectMapper(): ObjectMapper
|
||||
}
|
||||
|
||||
class Resilience4jConfig {
|
||||
+ replicateCircuitBreaker(): CircuitBreaker
|
||||
+ azureCircuitBreaker(): CircuitBreaker
|
||||
}
|
||||
|
||||
class SecurityConfig {
|
||||
+ securityFilterChain(http: HttpSecurity): SecurityFilterChain
|
||||
}
|
||||
|
||||
class SwaggerConfig {
|
||||
+ openAPI(): OpenAPI
|
||||
}
|
||||
}
|
||||
|
||||
' ============================================
|
||||
' 관계 정의 (Domain)
|
||||
' ============================================
|
||||
Content "1" o-- "0..*" GeneratedImage : contains
|
||||
GeneratedImage --> ImageStyle : uses
|
||||
GeneratedImage --> Platform : uses
|
||||
Job --> JobStatus : has
|
||||
|
||||
' ============================================
|
||||
' 관계 정의 (Service → Port)
|
||||
' ============================================
|
||||
StableDiffusionImageGenerator ..> CDNUploader
|
||||
StableDiffusionImageGenerator ..> JobWriter
|
||||
StableDiffusionImageGenerator ..> ContentWriter
|
||||
StableDiffusionImageGenerator --> ReplicateApiClient
|
||||
|
||||
JobManagementService ..> JobReader
|
||||
GetEventContentService ..> ContentReader
|
||||
GetEventContentService ..> RedisAIDataReader
|
||||
GetImageListService ..> ImageReader
|
||||
GetImageDetailService ..> ImageReader
|
||||
RegenerateImageService ..> ImageReader
|
||||
RegenerateImageService ..> ImageWriter
|
||||
RegenerateImageService ..> JobWriter
|
||||
RegenerateImageService ..> CDNUploader
|
||||
DeleteImageService ..> ImageWriter
|
||||
|
||||
' ============================================
|
||||
' 관계 정의 (Gateway → Port Implementation)
|
||||
' ============================================
|
||||
RedisGateway ..|> ContentReader
|
||||
RedisGateway ..|> ContentWriter
|
||||
RedisGateway ..|> ImageReader
|
||||
RedisGateway ..|> ImageWriter
|
||||
RedisGateway ..|> JobReader
|
||||
RedisGateway ..|> JobWriter
|
||||
RedisGateway ..|> RedisAIDataReader
|
||||
RedisGateway ..|> RedisImageWriter
|
||||
|
||||
AzureBlobStorageUploader ..|> CDNUploader
|
||||
|
||||
' ============================================
|
||||
' 관계 정의 (Controller → UseCase)
|
||||
' ============================================
|
||||
ContentController ..> GenerateImagesUseCase
|
||||
ContentController ..> GetJobStatusUseCase
|
||||
ContentController ..> GetEventContentUseCase
|
||||
ContentController ..> GetImageListUseCase
|
||||
ContentController ..> GetImageDetailUseCase
|
||||
ContentController ..> RegenerateImageUseCase
|
||||
ContentController ..> DeleteImageUseCase
|
||||
|
||||
' ============================================
|
||||
' 관계 정의 (DTO)
|
||||
' ============================================
|
||||
ContentInfo ..> ImageInfo
|
||||
ContentCommand ..> GenerateImagesCommand : uses
|
||||
ContentCommand ..> RegenerateImageCommand : uses
|
||||
ReplicateRequest ..> ReplicateInputRequest : contains
|
||||
|
||||
' ============================================
|
||||
' 레이어 노트
|
||||
' ============================================
|
||||
note top of Content : Domain Layer\n순수 비즈니스 로직\n외부 의존성 없음
|
||||
note top of GenerateImagesUseCase : Application Layer (Input Port)\nUse Case 인터페이스
|
||||
note top of ContentReader : Application Layer (Output Port)\n외부 시스템 의존성 추상화
|
||||
note top of StableDiffusionImageGenerator : Application Service Layer\nUse Case 구현체\n비즈니스 로직 오케스트레이션
|
||||
note top of RedisGateway : Infrastructure Layer\nOutput Port 구현체\nRedis 연동
|
||||
note top of ContentController : Presentation Layer\nREST API 엔드포인트
|
||||
note top of RedisConfig : Configuration Layer\n인프라 설정
|
||||
|
||||
@enduml
|
||||
Reference in New Issue
Block a user