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

530 lines
17 KiB
Plaintext

@startuml
!theme mono
title AI Service 클래스 다이어그램 (Clean Architecture)
' ===== Presentation Layer (Interface Adapters) =====
package "com.kt.ai.controller" <<Rectangle>> #E8F5E9 {
class HealthController {
+ checkHealth(): ResponseEntity<HealthCheckResponse>
- getServiceStatus(): ServiceStatus
- checkRedisConnection(): boolean
}
class InternalRecommendationController {
- aiRecommendationService: AIRecommendationService
- cacheService: CacheService
- redisTemplate: RedisTemplate<String, Object>
+ getRecommendation(eventId: String): ResponseEntity<AIRecommendationResult>
+ debugRedisKeys(): ResponseEntity<Map<String, Object>>
+ debugRedisKey(key: String): ResponseEntity<Map<String, Object>>
+ searchAllDatabases(): ResponseEntity<Map<String, Object>>
+ createTestData(eventId: String): ResponseEntity<Map<String, Object>>
}
class InternalJobController {
- jobStatusService: JobStatusService
- cacheService: CacheService
+ getJobStatus(jobId: String): ResponseEntity<JobStatusResponse>
+ createTestJob(jobId: String): ResponseEntity<Map<String, Object>>
}
}
' ===== Application Layer (Use Cases) =====
package "com.kt.ai.service" <<Rectangle>> #FFF9C4 {
class AIRecommendationService {
- cacheService: CacheService
- jobStatusService: JobStatusService
- trendAnalysisService: TrendAnalysisService
- claudeApiClient: ClaudeApiClient
- circuitBreakerManager: CircuitBreakerManager
- fallback: AIServiceFallback
- objectMapper: ObjectMapper
- aiProvider: String
- apiKey: String
- anthropicVersion: String
- model: String
- maxTokens: Integer
- temperature: Double
+ getRecommendation(eventId: String): AIRecommendationResult
+ generateRecommendations(message: AIJobMessage): void
- analyzeTrend(message: AIJobMessage): TrendAnalysis
- createRecommendations(message: AIJobMessage, trendAnalysis: TrendAnalysis): List<EventRecommendation>
- callClaudeApiForRecommendations(message: AIJobMessage, trendAnalysis: TrendAnalysis): List<EventRecommendation>
- buildRecommendationPrompt(message: AIJobMessage, trendAnalysis: TrendAnalysis): String
- parseRecommendationResponse(responseText: String): List<EventRecommendation>
- parseEventRecommendation(node: JsonNode): EventRecommendation
- parseRange(node: JsonNode): ExpectedMetrics.Range
- extractJsonFromMarkdown(text: String): String
}
class TrendAnalysisService {
- claudeApiClient: ClaudeApiClient
- circuitBreakerManager: CircuitBreakerManager
- fallback: AIServiceFallback
- objectMapper: ObjectMapper
- apiKey: String
- anthropicVersion: String
- model: String
- maxTokens: Integer
- temperature: Double
+ analyzeTrend(industry: String, region: String): TrendAnalysis
- callClaudeApi(industry: String, region: String): TrendAnalysis
- buildPrompt(industry: String, region: String): String
- parseResponse(responseText: String): TrendAnalysis
- extractJsonFromMarkdown(text: String): String
- parseTrendKeywords(arrayNode: JsonNode): List<TrendAnalysis.TrendKeyword>
}
class JobStatusService {
- cacheService: CacheService
- objectMapper: ObjectMapper
+ getJobStatus(jobId: String): JobStatusResponse
+ updateJobStatus(jobId: String, status: JobStatus, message: String): void
- calculateProgress(status: JobStatus): int
}
class CacheService {
- redisTemplate: RedisTemplate<String, Object>
- recommendationTtl: long
- jobStatusTtl: long
- trendTtl: long
+ set(key: String, value: Object, ttlSeconds: long): void
+ get(key: String): Object
+ delete(key: String): void
+ saveJobStatus(jobId: String, status: Object): void
+ getJobStatus(jobId: String): Object
+ saveRecommendation(eventId: String, recommendation: Object): void
+ getRecommendation(eventId: String): Object
+ saveTrend(industry: String, region: String, trend: Object): void
+ getTrend(industry: String, region: String): Object
}
}
' ===== Domain Layer (Entities & Business Logic) =====
package "com.kt.ai.model" <<Rectangle>> #E1BEE7 {
package "dto.response" {
class AIRecommendationResult {
- eventId: String
- trendAnalysis: TrendAnalysis
- recommendations: List<EventRecommendation>
- generatedAt: LocalDateTime
- expiresAt: LocalDateTime
- aiProvider: AIProvider
}
class TrendAnalysis {
- industryTrends: List<TrendKeyword>
- regionalTrends: List<TrendKeyword>
- seasonalTrends: List<TrendKeyword>
}
class "TrendAnalysis.TrendKeyword" as TrendKeyword {
- keyword: String
- relevance: Double
- description: String
}
class EventRecommendation {
- optionNumber: Integer
- concept: String
- title: String
- description: String
- targetAudience: String
- duration: Duration
- mechanics: Mechanics
- promotionChannels: List<String>
- estimatedCost: EstimatedCost
- expectedMetrics: ExpectedMetrics
- differentiator: String
}
class "EventRecommendation.Duration" as Duration {
- recommendedDays: Integer
- recommendedPeriod: String
}
class "EventRecommendation.Mechanics" as Mechanics {
- type: EventMechanicsType
- details: String
}
class "EventRecommendation.EstimatedCost" as EstimatedCost {
- min: Integer
- max: Integer
- breakdown: Map<String, Integer>
}
class ExpectedMetrics {
- newCustomers: Range
- revenueIncrease: Range
- roi: Range
}
class "ExpectedMetrics.Range" as Range {
- min: Double
- max: Double
}
class JobStatusResponse {
- jobId: String
- status: JobStatus
- progress: Integer
- message: String
- createdAt: LocalDateTime
}
class HealthCheckResponse {
- status: ServiceStatus
- timestamp: LocalDateTime
- redisConnected: boolean
}
class ErrorResponse {
- success: boolean
- errorCode: String
- message: String
- timestamp: LocalDateTime
- details: Map<String, Object>
}
}
package "enums" {
enum AIProvider {
CLAUDE
GPT_4
}
enum JobStatus {
PENDING
PROCESSING
COMPLETED
FAILED
}
enum EventMechanicsType {
DISCOUNT
GIFT
STAMP
EXPERIENCE
LOTTERY
COMBO
}
enum ServiceStatus {
UP
DOWN
DEGRADED
}
enum CircuitBreakerState {
CLOSED
OPEN
HALF_OPEN
}
}
}
' ===== Infrastructure Layer (External Interfaces) =====
package "com.kt.ai.client" <<Rectangle>> #FFCCBC {
interface ClaudeApiClient {
+ sendMessage(apiKey: String, anthropicVersion: String, request: ClaudeRequest): ClaudeResponse
}
package "dto" {
class ClaudeRequest {
- model: String
- messages: List<Message>
- maxTokens: Integer
- temperature: Double
- system: String
}
class "ClaudeRequest.Message" as Message {
- role: String
- content: String
}
class ClaudeResponse {
- id: String
- type: String
- role: String
- content: List<Content>
- model: String
- stopReason: String
- stopSequence: String
- usage: Usage
+ extractText(): String
}
class "ClaudeResponse.Content" as Content {
- type: String
- text: String
}
class "ClaudeResponse.Usage" as Usage {
- inputTokens: Integer
- outputTokens: Integer
}
}
package "config" {
class FeignClientConfig {
+ feignEncoder(): Encoder
+ feignDecoder(): Decoder
+ feignLoggerLevel(): Logger.Level
+ feignErrorDecoder(): ErrorDecoder
}
}
}
package "com.kt.ai.circuitbreaker" <<Rectangle>> #FFCCBC {
class CircuitBreakerManager {
- circuitBreakerRegistry: CircuitBreakerRegistry
+ executeWithCircuitBreaker(circuitBreakerName: String, supplier: Supplier<T>, fallback: Supplier<T>): T
+ executeWithCircuitBreaker(circuitBreakerName: String, supplier: Supplier<T>): T
+ getCircuitBreakerState(circuitBreakerName: String): CircuitBreaker.State
}
package "fallback" {
class AIServiceFallback {
+ getDefaultTrendAnalysis(industry: String, region: String): TrendAnalysis
+ getDefaultRecommendations(objective: String, industry: String): List<EventRecommendation>
- createDefaultTrendKeyword(keyword: String, relevance: Double, description: String): TrendAnalysis.TrendKeyword
- createDefaultRecommendation(optionNumber: Integer, concept: String, title: String): EventRecommendation
}
}
}
package "com.kt.ai.kafka" <<Rectangle>> #FFCCBC {
package "consumer" {
class AIJobConsumer {
- aiRecommendationService: AIRecommendationService
- objectMapper: ObjectMapper
+ consumeAIJobMessage(message: String): void
- parseAIJobMessage(message: String): AIJobMessage
}
}
package "message" {
class AIJobMessage {
- jobId: String
- eventId: String
- storeName: String
- industry: String
- region: String
- objective: String
- targetAudience: String
- budget: Integer
- requestedAt: LocalDateTime
}
}
}
' ===== Exception Layer =====
package "com.kt.ai.exception" <<Rectangle>> #FFEBEE {
class GlobalExceptionHandler {
+ handleBusinessException(e: BusinessException): ResponseEntity<ErrorResponse>
+ handleJobNotFoundException(e: JobNotFoundException): ResponseEntity<ErrorResponse>
+ handleRecommendationNotFoundException(e: RecommendationNotFoundException): ResponseEntity<ErrorResponse>
+ handleCircuitBreakerOpenException(e: CircuitBreakerOpenException): ResponseEntity<ErrorResponse>
+ handleAIServiceException(e: AIServiceException): ResponseEntity<ErrorResponse>
+ handleException(e: Exception): ResponseEntity<ErrorResponse>
- buildErrorResponse(errorCode: String, message: String): ErrorResponse
}
class AIServiceException {
- errorCode: String
- details: String
+ AIServiceException(message: String)
+ AIServiceException(message: String, cause: Throwable)
+ AIServiceException(errorCode: String, message: String)
+ AIServiceException(errorCode: String, message: String, details: String)
}
class JobNotFoundException {
- jobId: String
+ JobNotFoundException(jobId: String)
+ JobNotFoundException(jobId: String, cause: Throwable)
}
class RecommendationNotFoundException {
- eventId: String
+ RecommendationNotFoundException(eventId: String)
+ RecommendationNotFoundException(eventId: String, cause: Throwable)
}
class CircuitBreakerOpenException {
- circuitBreakerName: String
+ CircuitBreakerOpenException(circuitBreakerName: String)
+ CircuitBreakerOpenException(circuitBreakerName: String, cause: Throwable)
}
}
' ===== Configuration Layer =====
package "com.kt.ai.config" <<Rectangle>> #E3F2FD {
class SecurityConfig {
+ securityFilterChain(http: HttpSecurity): SecurityFilterChain
+ passwordEncoder(): PasswordEncoder
}
class RedisConfig {
- host: String
- port: int
- password: String
+ redisConnectionFactory(): LettuceConnectionFactory
+ redisTemplate(): RedisTemplate<String, Object>
}
class CircuitBreakerConfig {
+ circuitBreakerRegistry(): CircuitBreakerRegistry
+ circuitBreakerConfigCustomizer(): Customizer<CircuitBreakerConfigurationProperties.InstanceProperties>
}
class KafkaConsumerConfig {
- bootstrapServers: String
- groupId: String
+ consumerFactory(): ConsumerFactory<String, String>
+ kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory<String, String>
}
class JacksonConfig {
+ objectMapper(): ObjectMapper
}
class SwaggerConfig {
+ openAPI(): OpenAPI
}
}
' ===== Main Application =====
package "com.kt.ai" <<Rectangle>> #F3E5F5 {
class AiServiceApplication {
+ {static} main(args: String[]): void
}
}
' ===== 관계 정의 =====
' Controller → Service
InternalRecommendationController --> AIRecommendationService : uses
InternalRecommendationController --> CacheService : uses
InternalJobController --> JobStatusService : uses
InternalJobController --> CacheService : uses
' Service → Service
AIRecommendationService --> TrendAnalysisService : uses
AIRecommendationService --> JobStatusService : uses
AIRecommendationService --> CacheService : uses
JobStatusService --> CacheService : uses
' Service → Client
AIRecommendationService --> ClaudeApiClient : uses
TrendAnalysisService --> ClaudeApiClient : uses
' Service → CircuitBreaker
AIRecommendationService --> CircuitBreakerManager : uses
AIRecommendationService --> AIServiceFallback : uses
TrendAnalysisService --> CircuitBreakerManager : uses
TrendAnalysisService --> AIServiceFallback : uses
' Kafka → Service
AIJobConsumer --> AIRecommendationService : uses
AIJobConsumer --> AIJobMessage : uses
' Service → Domain
AIRecommendationService --> AIRecommendationResult : creates
AIRecommendationService --> EventRecommendation : creates
TrendAnalysisService --> TrendAnalysis : creates
JobStatusService --> JobStatusResponse : creates
' Domain Relationships
AIRecommendationResult *-- TrendAnalysis
AIRecommendationResult *-- EventRecommendation
AIRecommendationResult --> AIProvider
TrendAnalysis *-- TrendKeyword
EventRecommendation *-- Duration
EventRecommendation *-- Mechanics
EventRecommendation *-- EstimatedCost
EventRecommendation *-- ExpectedMetrics
Mechanics --> EventMechanicsType
ExpectedMetrics *-- Range
JobStatusResponse --> JobStatus
HealthCheckResponse --> ServiceStatus
' Client Relationships
ClaudeApiClient --> ClaudeRequest : uses
ClaudeApiClient --> ClaudeResponse : returns
ClaudeRequest *-- Message
ClaudeResponse *-- Content
ClaudeResponse *-- Usage
' Exception Relationships
GlobalExceptionHandler ..> ErrorResponse : creates
GlobalExceptionHandler ..> AIServiceException : handles
GlobalExceptionHandler ..> JobNotFoundException : handles
GlobalExceptionHandler ..> RecommendationNotFoundException : handles
GlobalExceptionHandler ..> CircuitBreakerOpenException : handles
AIServiceException <|-- JobNotFoundException
AIServiceException <|-- RecommendationNotFoundException
AIServiceException <|-- CircuitBreakerOpenException
note top of AiServiceApplication
Spring Boot Application Entry Point
- @SpringBootApplication
- @EnableFeignClients
- @EnableKafka
end note
note top of AIRecommendationService
**Use Case (Application Layer)**
- AI 추천 생성 비즈니스 로직
- 트렌드 분석 → 추천안 생성
- Circuit Breaker 적용
- Redis 캐싱 전략
end note
note top of TrendAnalysisService
**Use Case (Application Layer)**
- Claude AI를 통한 트렌드 분석
- 업종/지역/계절 트렌드
- Circuit Breaker Fallback
end note
note top of ClaudeApiClient
**Infrastructure (External Interface)**
- Feign Client
- Claude API 연동
- HTTP 통신 처리
end note
note top of CircuitBreakerManager
**Infrastructure (Resilience)**
- Resilience4j Circuit Breaker
- 외부 API 장애 격리
- Fallback 메커니즘
end note
note top of CacheService
**Infrastructure (Data Access)**
- Redis 캐싱 인프라
- TTL 관리
- 추천/트렌드/상태 캐싱
end note
note right of AIRecommendationResult
**Domain Entity**
- AI 추천 결과
- 불변 객체 (Immutable)
- Builder 패턴
end note
note right of TrendAnalysis
**Domain Entity**
- 트렌드 분석 결과
- 업종/지역/계절별 구분
end note
@enduml