이미지 생성 및 AI 추천 inner sequence 수정

주요 변경사항:
- event-이미지생성요청.puml: Kafka 제거, ContentService 내부 Job 관리로 변경
- event-이미지결과조회.puml: ContentService 패턴으로 업데이트
- event-AI추천요청.puml: Gateway 패턴 추가, 한글화

아키텍처 구분:
- AI 추천: Kafka 사용 (ai-job-topic)
- 이미지 생성: 내부 Job 관리 (Kafka 사용 안 함)

모든 파일:
- Gateway 패턴 적용
- 레이어 아키텍처 (<<API Layer>>, <<Business Layer>>)
- 한글 요청/응답
- Redis 키 패턴 표준화

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
cherry2250 2025-10-23 16:31:36 +09:00
parent d7d4b0a2da
commit e15b7b42e5
3 changed files with 292 additions and 94 deletions

View File

@ -3,54 +3,124 @@
title Event Service - AI 추천 요청 (Kafka Job 발행) (UFR-EVENT-030)
participant "EventController" as Controller <<C>>
participant "EventService" as Service <<S>>
participant "JobService" as JobSvc <<S>>
participant "EventRepository" as Repo <<R>>
actor Client
participant "API Gateway" as Gateway
participant "EventController" as Controller <<API Layer>>
participant "EventService" as Service <<Business Layer>>
participant "JobService" as JobSvc <<Business Layer>>
participant "EventRepository" as Repo <<Data Layer>>
participant "Redis Cache" as Cache <<E>>
database "Event DB" as DB <<E>>
participant "Kafka Producer" as Kafka <<E>>
note over Controller: POST /api/events/{id}/ai-recommendations
note over Controller, Kafka
**UFR-EVENT-030: AI 이벤트 추천 요청**
- Kafka 비동기 Job 발행
- AI Service가 Kafka 구독하여 처리
- 트렌드 분석 + 3가지 추천안 생성
- 처리 시간: 평균 2분 이내
end note
Client -> Gateway: POST /api/events/{eventDraftId}/ai-recommendations\n{"objective": "신규 고객 유치",\n"industry": "음식점",\n"region": "서울 강남구"}
activate Gateway
Gateway -> Controller: POST /api/events/{eventDraftId}/ai-recommendations
activate Controller
Controller -> Controller: 요청 검증\n(필수 필드, 목적 유효성)
Controller -> Service: requestAIRecommendation(eventDraftId, userId)
activate Service
== 1단계: 이벤트 초안 조회 및 검증 ==
Service -> Repo: findById(eventDraftId)
activate Repo
Repo -> DB: 이벤트 초안 조회\n(초안ID와 사용자ID로 조회)
Repo -> DB: 이벤트 초안 조회\n(초안ID로 이벤트 목적,\n매장 정보 조회)
activate DB
DB --> Repo: EventDraft
DB --> Repo: EventDraft 엔티티\n{목적, 매장명, 업종, 주소}
deactivate DB
Repo --> Service: EventDraft entity
deactivate Repo
Service -> Service: validateOwnership(userId, eventDraft)
note right: 사용자 권한 검증
Service -> Service: 소유권 검증\nvalidateOwnership(userId, eventDraft)
Service -> JobSvc: createAIJob(eventDraft)
activate JobSvc
alt 소유권 없음
Service --> Controller: throw ForbiddenException\n("권한이 없습니다")
Controller --> Gateway: 403 Forbidden\n{"code": "EVENT_003",\n"message": "권한이 없습니다"}
deactivate Service
deactivate Controller
Gateway --> Client: 403 Forbidden
deactivate Gateway
JobSvc -> JobSvc: generateJobId()
note right: UUID 생성
else 소유권 확인
JobSvc -> Cache: set("job:" + jobId,\n{status: PENDING, createdAt}, TTL=1시간)
activate Cache
Cache --> JobSvc: OK
deactivate Cache
== 2단계: Kafka Job 생성 ==
JobSvc -> Kafka: publish(ai-job,\n{jobId, eventDraftId, objective,\nindustry, region, storeInfo})
activate Kafka
note right: Kafka Job Topic:\nai-job-topic
Kafka --> JobSvc: ACK
deactivate Kafka
Service -> JobSvc: createAIJob(eventDraft)
activate JobSvc
JobSvc --> Service: JobResponse\n{jobId, status: PENDING}
deactivate JobSvc
JobSvc -> JobSvc: Job ID 생성 (UUID)
Service --> Controller: JobResponse\n{jobId, status: PENDING}
deactivate Service
Controller --> Client: 202 Accepted\n{jobId, status: PENDING}
JobSvc -> Cache: Job 상태 저장\nKey: job:{jobId}\nValue: {status: PENDING,\neventDraftId, type: AI_RECOMMEND,\ncreatedAt}\nTTL: 1시간
activate Cache
Cache --> JobSvc: 저장 완료
deactivate Cache
note over Controller, Kafka: AI Service는 백그라운드에서\nKafka ai-job 토픽을 구독하여\n비동기로 처리
== 3단계: Kafka 이벤트 발행 ==
JobSvc -> Kafka: 이벤트 발행\nTopic: ai-job-topic\nPayload: {jobId, eventDraftId,\nobjective, industry,\nregion, storeInfo}
activate Kafka
note right of Kafka
**Kafka Topic**
- Topic: ai-job-topic
- Consumer: AI Service
- Consumer Group: ai-service-group
**Payload**
{
"jobId": "UUID",
"eventDraftId": "UUID",
"objective": "신규 고객 유치",
"industry": "음식점",
"region": "서울 강남구",
"storeInfo": {...}
}
end note
Kafka --> JobSvc: ACK (발행 확인)
deactivate Kafka
JobSvc --> Service: JobResponse\n{jobId, status: PENDING}
deactivate JobSvc
== 4단계: 응답 반환 ==
Service --> Controller: JobResponse\n{jobId, status: PENDING}
deactivate Service
Controller --> Gateway: 202 Accepted\n{"jobId": "job-uuid-123",\n"status": "PENDING",\n"message": "AI가 분석 중입니다"}
deactivate Controller
Gateway --> Client: 202 Accepted\nAI 분석 시작
deactivate Gateway
note over Service, Kafka
**AI Service 비동기 처리**
- Kafka 구독: ai-job-topic
- 트렌드 분석 (업종, 지역 기반)
- 3가지 추천안 생성 (저/중/고 비용)
- 결과: Redis에 저장 (TTL: 24시간)
- 상세: ai-트렌드분석및추천.puml 참조
**처리 시간**
- 평균: 2분 이내
- P95: 4분 이내
- Timeout: 5분
**결과 조회**
- 폴링 방식: GET /api/jobs/{jobId}/status
- 간격: 2초, 최대 30초
end note
end
@enduml

View File

@ -1,45 +1,140 @@
@startuml event-이미지결과조회
!theme mono
title Event Service - 이미지 생성 결과 폴링 조회
title Content Service - 이미지 생성 결과 폴링 조회
participant "EventController" as Controller <<C>>
participant "JobService" as JobSvc <<S>>
actor Client
participant "API Gateway" as Gateway
participant "ContentController" as Controller <<API Layer>>
participant "ContentService" as Service <<Business Layer>>
participant "JobManager" as JobMgr <<Component>>
participant "Redis Cache" as Cache <<E>>
note over Controller: GET /api/jobs/{jobId}/images
Controller -> JobSvc: getImageJobStatus(jobId)
activate JobSvc
note over Controller, Cache
**폴링 방식 Job 상태 조회**
- 최대 30초 동안 폴링 (2초 간격)
- Job 상태: PENDING → PROCESSING → COMPLETED
- 이미지 URL: Redis에 저장 (TTL: 7일)
end note
JobSvc -> Cache: get("job:" + jobId)
Client -> Gateway: GET /api/content/jobs/{jobId}/status
activate Gateway
Gateway -> Controller: GET /api/content/jobs/{jobId}/status
activate Controller
Controller -> Service: getJobStatus(jobId)
activate Service
Service -> JobMgr: getJobStatus(jobId)
activate JobMgr
JobMgr -> Cache: Job 상태 조회\nKey: job:{jobId}
activate Cache
alt 캐시 히트
Cache --> JobSvc: Job data\n{status, imageUrls, createdAt}
alt Job 데이터 존재
Cache --> JobMgr: Job 데이터\n{status, eventDraftId,\ntype, createdAt}
deactivate Cache
alt Job 완료 (status: COMPLETED)
JobSvc --> Controller: JobStatusResponse\n{jobId, status: COMPLETED,\nimageUrls: {...}}
Controller --> Client: 200 OK\n{status: COMPLETED,\nimageUrls: {\n simple: "https://cdn.../simple.png",\n fancy: "https://cdn.../fancy.png",\n trendy: "https://cdn.../trendy.png"\n}}
alt status = COMPLETED
JobMgr -> Cache: 이미지 URL 조회\nKey: content:image:{eventDraftId}
activate Cache
Cache --> JobMgr: 이미지 URL\n{simple, fancy, trendy}
deactivate Cache
else Job 진행중 (status: PROCESSING)
JobSvc --> Controller: JobStatusResponse\n{jobId, status: PROCESSING}
Controller --> Client: 200 OK\n{status: PROCESSING}
note right: 클라이언트는 3초 후\n재요청
JobMgr --> Service: JobStatusResponse\n{jobId, status: COMPLETED,\nimageUrls: {...}}
deactivate JobMgr
else Job 실패 (status: FAILED)
JobSvc --> Controller: JobStatusResponse\n{jobId, status: FAILED, error}
Controller --> Client: 200 OK\n{status: FAILED, error}
Service --> Controller: JobStatusResponse\n{status: COMPLETED, imageUrls}
deactivate Service
Controller --> Gateway: 200 OK\n{"status": "COMPLETED",\n"imageUrls": {\n "simple": "https://cdn.../simple.png",\n "fancy": "https://cdn.../fancy.png",\n "trendy": "https://cdn.../trendy.png"\n}}
deactivate Controller
Gateway --> Client: 200 OK\n이미지 URL 반환
deactivate Gateway
note right of Client
**프론트엔드 처리**
- 3가지 스타일 카드 표시
- 사용자가 스타일 선택
- 이미지 편집 가능
end note
else status = PROCESSING 또는 PENDING
JobMgr --> Service: JobStatusResponse\n{jobId, status: PROCESSING}
deactivate JobMgr
Service --> Controller: JobStatusResponse\n{status: PROCESSING}
deactivate Service
Controller --> Gateway: 200 OK\n{"status": "PROCESSING",\n"message": "이미지 생성 중입니다"}
deactivate Controller
Gateway --> Client: 200 OK\n진행 중 상태
deactivate Gateway
note right of Client
**폴링 재시도**
- 2초 후 재요청
- 최대 30초 (15회)
end note
else status = FAILED
JobMgr -> Cache: 에러 정보 조회\nKey: job:{jobId}:error
activate Cache
Cache --> JobMgr: 에러 메시지
deactivate Cache
JobMgr --> Service: JobStatusResponse\n{jobId, status: FAILED, error}
deactivate JobMgr
Service --> Controller: JobStatusResponse\n{status: FAILED, error}
deactivate Service
Controller --> Gateway: 200 OK\n{"status": "FAILED",\n"error": "이미지 생성 실패",\n"message": "다시 시도해주세요"}
deactivate Controller
Gateway --> Client: 200 OK\n실패 상태
deactivate Gateway
note right of Client
**실패 처리**
- 에러 메시지 표시
- "다시 생성" 버튼 제공
end note
end
else 캐시 미스
Cache --> JobSvc: null
JobSvc --> Controller: NotFoundError
Controller --> Client: 404 Not Found\n{error: "Job not found"}
else Job 데이터 없음
Cache --> JobMgr: null (캐시 미스)
deactivate Cache
JobMgr --> Service: throw NotFoundException\n("Job을 찾을 수 없습니다")
deactivate JobMgr
Service --> Controller: NotFoundException
deactivate Service
Controller --> Gateway: 404 Not Found\n{"code": "JOB_001",\n"message": "Job을 찾을 수 없습니다"}
deactivate Controller
Gateway --> Client: 404 Not Found
deactivate Gateway
end
deactivate Cache
deactivate JobSvc
note over Controller, Cache
**폴링 전략**
- 간격: 2초
- 최대 시간: 30초 (15회)
- Timeout 시: 사용자에게 알림 + "다시 생성" 옵션
note over Controller, Cache: 최대 30초 동안 폴링\n(3초 간격, 최대 10회)\n\n이미지 URL은 Redis에서 조회\n(TTL: 7일)\n\n타임아웃 시 클라이언트는\n에러 메시지 표시 및\n"다시 생성" 옵션 제공
**Redis 캐시**
- Job 상태: TTL 1시간
- 이미지 URL: TTL 7일
**성능 목표**
- 평균 이미지 생성 시간: 20초 이내
- P95 이미지 생성 시간: 40초 이내
end note
@enduml

View File

@ -1,57 +1,90 @@
@startuml event-이미지생성요청
!theme mono
title Event Service - 이미지 생성 요청 (Kafka Job 발행) (UFR-CONT-010)
title Content Service - 이미지 생성 요청 (UFR-CONT-010)
participant "EventController" as Controller <<C>>
participant "EventService" as Service <<S>>
participant "JobService" as JobSvc <<S>>
participant "EventRepository" as Repo <<R>>
actor Client
participant "API Gateway" as Gateway
participant "ContentController" as Controller <<API Layer>>
participant "ContentService" as Service <<Business Layer>>
participant "JobManager" as JobMgr <<Component>>
participant "Redis Cache" as Cache <<E>>
database "Event DB" as DB <<E>>
participant "Kafka Producer" as Kafka <<E>>
note over Controller: POST /api/events/{id}/content-generation
Controller -> Service: requestImageGeneration(eventDraftId, userId)
note over Controller, Cache
**UFR-CONT-010: SNS 이미지 생성 요청**
- Kafka 사용 안 함 (내부 Job 관리)
- 백그라운드 워커가 비동기 처리
- Redis에서 AI 추천 데이터 읽기
- 3가지 스타일 이미지 생성 (심플, 화려한, 트렌디)
end note
Client -> Gateway: POST /api/content/images/{eventDraftId}/generate
activate Gateway
Gateway -> Controller: POST /api/content/images/{eventDraftId}/generate
activate Controller
Controller -> Controller: 요청 검증\n(eventDraftId 유효성)
Controller -> Service: generateImages(eventDraftId)
activate Service
Service -> Repo: findById(eventDraftId)
activate Repo
Repo -> DB: 이벤트 초안 조회\n(초안ID와 사용자ID로 조회)
activate DB
DB --> Repo: EventDraft
deactivate DB
Repo --> Service: EventDraft entity
deactivate Repo
== 1단계: Redis에서 AI 추천 데이터 확인 ==
Service -> Service: validateOwnership(userId, eventDraft)
Service -> Service: validateRecommendationSelected()
note right: AI 추천 선택 여부 확인
Service -> JobSvc: createImageJob(eventDraft)
activate JobSvc
JobSvc -> JobSvc: generateJobId()
note right: UUID 생성
JobSvc -> Cache: set("job:" + jobId,\n{status: PENDING, createdAt}, TTL=1시간)
Service -> Cache: AI 추천 데이터 조회\nKey: ai:event:{eventDraftId}
activate Cache
Cache --> JobSvc: OK
Cache --> Service: AI 추천 결과\n{선택된 추천안, 이벤트 정보}
deactivate Cache
JobSvc -> Kafka: publish(image-job,\n{jobId, eventDraftId, title, prize,\nbrandColor, logoUrl, storeInfo})
activate Kafka
note right: Kafka Job Topic:\nimage-job-topic
Kafka --> JobSvc: ACK
deactivate Kafka
alt AI 추천 데이터 없음
Service --> Controller: throw NotFoundException\n("AI 추천을 먼저 선택해주세요")
Controller --> Gateway: 404 Not Found\n{"code": "CONTENT_001",\n"message": "AI 추천을 먼저 선택해주세요"}
deactivate Service
deactivate Controller
Gateway --> Client: 404 Not Found
deactivate Gateway
JobSvc --> Service: JobResponse\n{jobId, status: PENDING}
deactivate JobSvc
else AI 추천 데이터 존재
Service --> Controller: JobResponse\n{jobId, status: PENDING}
deactivate Service
Controller --> Client: 202 Accepted\n{jobId, status: PENDING}
== 2단계: Job 생성 ==
note over Controller, Kafka: Content Service는 백그라운드에서\nKafka image-job 토픽을 구독하여\n3가지 스타일 이미지 생성\n(심플, 화려한, 트렌디)
Service -> JobMgr: createJob(eventDraftId, imageGeneration)
activate JobMgr
JobMgr -> JobMgr: Job ID 생성 (UUID)
JobMgr -> Cache: Job 상태 저장\nKey: job:{jobId}\nValue: {status: PENDING,\neventDraftId, type: IMAGE_GEN,\ncreatedAt}\nTTL: 1시간
activate Cache
Cache --> JobMgr: 저장 완료
deactivate Cache
JobMgr --> Service: Job 생성 완료\n{jobId, status: PENDING}
deactivate JobMgr
== 3단계: 응답 반환 ==
Service --> Controller: JobResponse\n{jobId, status: PENDING}
deactivate Service
Controller --> Gateway: 202 Accepted\n{"jobId": "job-uuid-123",\n"status": "PENDING",\n"message": "이미지 생성 중입니다"}
deactivate Controller
Gateway --> Client: 202 Accepted\n이미지 생성 시작
deactivate Gateway
note over Service, Cache
**백그라운드 워커 처리**
- Redis 폴링 또는 스케줄러가 Job 감지
- content-이미지생성.puml 참조
- 외부 이미지 생성 API 호출 (병렬)
- Redis에 이미지 URL 저장
**상세 내용**
- 3가지 스타일 병렬 생성 (심플, 화려한, 트렌디)
- Circuit Breaker 적용 (Timeout: 5분)
- 결과: Redis Key: content:image:{eventDraftId}
- TTL: 7일
end note
end
@enduml