mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 18:46:23 +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>
530 lines
17 KiB
Plaintext
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
|