Skip to content

Conversation

@johnbosco0414
Copy link
Collaborator

@johnbosco0414 johnbosco0414 commented Oct 10, 2025

제목(필수): [TYPE]: 제목 예) [FEAT]: 회원가입 기능 추가

제목 규칙 자세히 보기
  • 형식: [TYPE]: 제목
  • 제한: 50자 이내, 첫 글자 대문자, 명령문
  • TYPE: FEAT FIX REFACTOR COMMENT STYLE TEST CHORE INIT

무엇을 / 왜

  • 무엇(What):
  1. AI 이미지 생성 기능 추가
  • ImageAiClient 인터페이스 및 StableDiffusionImageClient 구현체 추가
  • ImageAiProperties 설정 클래스 추가 (API 키, URL, 타임아웃, 재시도 설정)
  • AiService.generateImage() 메서드 구현
  • Base64 이미지 데이터를 URL로 변환하는 전체 플로우 구현
  • application.yml에 이미지 AI 설정 추가 (Stable Diffusion API 키, 타임아웃, 재시도)
  1. 스토리지 서비스 통합
  • StorageService 인터페이스 추가 (Local/S3 추상화)
  • LocalStorageService 구현체 추가 (개발 환경용)
  • application.yml에 스토리지 타입 설정 추가 (storage-type: local, local-storage-path, local-base-url)
  • Base64 이미지를 파일로 저장하고 접근 가능한 URL 반환하는 로직 구현
  1. AsyncConfig 스레드풀 구현
  • AsyncConfig 클래스에 aiTaskExecutor Bean 추가
  • AWS Small 티어(1-2 vCPU, 2GB RAM) 최적화: core-size=2, max-size=4, queue-capacity=100
  • application.yml에 스레드풀 설정 추가
  • @EnableAsync 활성화
  • 여러 시나리오 동시 생성 시 성능 개선을 위한 비동기 처리 인프라 구축
  1. 예외 처리 아키텍처 개선
  • AiServiceImplAiParsingException import 및 사용
  • 입력 검증 에러는 AiServiceException 유지
  • JSON 파싱 에러는 AiParsingException으로 변경
  • exceptionally() 핸들러에서 AiParsingException을 그대로 전파하도록 수정 (cause 체크 로직 추가)
  1. AiServiceImplTest 테스트 수정
  • 입력 검증 테스트의 예외 기대값을 AiParsingExceptionAiServiceException으로 변경
  • JSON 파싱 에러 테스트는 AiParsingException expect 유지
  • Situation 생성 성공 테스트에 실제 ObjectMapper 주입 (setField() 메서드 사용)
  • 이미지 생성 테스트의 Graceful Degradation 검증 (예외 대신 placeholder 반환 확인)
  1. ScenarioTransactionService 이미지 생성 통합
  • handleImageGeneration() 메서드에서 aiService.generateImage() 호출
  • 생성된 이미지 URL을 Scenario.img 필드에 저장
  • 이미지 생성 실패 시 null 저장 (시나리오 생성은 정상 진행)
  1. WebConfig 정적 리소스 매핑 추가
  • WebMvcConfigurer 구현하여 /images/** 경로를 로컬 파일 시스템에 매핑

  • 로컬 환경에서만 활성화 (storage-type: local 조건)

  • 1시간 브라우저 캐싱 설정

  • 왜(Why):

  1. AI 이미지 생성 기능 추가
  • 사용자에게 시각적으로 풍부한 시나리오 제공
  • 텍스트만으로는 전달하기 어려운 시나리오 분위기를 이미지로 표현
  • Stable Diffusion 3.5 Large Turbo 모델을 통한 고품질 이미지 생성
  1. 스토리지 서비스 통합
  • Local(개발)과 S3(운영) 환경을 동일한 인터페이스로 처리하여 환경별 전환 용이
  • 개발 환경에서 S3 없이 로컬 파일 시스템으로 테스트 가능
  • 향후 다른 스토리지(GCS, Azure Blob 등) 추가 시 확장 용이
  1. AsyncConfig 스레드풀 구현
  • AI 호출(30초 이상 소요)을 비동기로 처리하여 DB 락 방지 및 동시성 향상
  • 여러 사용자가 동시에 시나리오 생성 요청 시 성능 최적화
  • AWS Small 티어의 제한된 리소스(1-2 vCPU, 2GB RAM)에 최적화된 설정
  1. 예외 처리 아키텍처 개선
  • 에러 원인에 따라 적절한 예외 타입 사용하여 의미론적 정확성 확보
  • 디버깅 시 에러 원인 파악 용이 (입력 검증 실패 vs JSON 파싱 실패)
  • 클라이언트에서 에러 타입별로 다른 UI/UX 처리 가능
  • exceptionally() 핸들러가 모든 예외를 AiServiceException으로 감싸는 문제 해결
  1. AiServiceImplTest 테스트 수정
  • 실제 구현체(AiServiceImpl)의 예외 타입과 테스트 기대값을 일치시켜 테스트 정확성 확보
  • Static 메서드(SituationPrompt.extractSituation())는 Mockito로 mock 불가하므로 실제 ObjectMapper 사용
  • 실제 JSON 파싱 로직 테스트로 프로덕션 코드와 동일한 동작 보장
  1. ScenarioTransactionService 이미지 생성 통합
  • 시나리오 생성 시 자동으로 이미지도 생성하여 사용자 경험 향상
  • Graceful Degradation 패턴: 이미지 생성 실패가 전체 시나리오 생성을 막지 않음
  • 이미지 API 장애 시에도 시나리오는 정상 생성되며, 이미지는 null로 저장
  1. WebConfig 정적 리소스 매핑 추가
  • 로컬 개발 환경에서 /images/** URL로 저장된 이미지 파일 접근 가능
  • 프론트엔드가 <img> 태그로 이미지 직접 로드 가능
  • S3 환경에서는 자동 비활성화 (S3 URL 직접 사용)

어떻게(요약) — 3줄 이내

  1. AI 이미지 생성 기능: Stable Diffusion 3.5 API 연동 및 Local/S3 스토리지 통합 (Graceful Degradation 패턴)
  2. 예외 처리 아키텍처 개선: 입력 검증(AiServiceException)과 파싱 에러(AiParsingException)를 명확히 구분
  3. AsyncConfig 스레드풀 설정 및 테스트 안정성 확보: AWS Small 티어 최적화 스레드풀 구현, 모든 AI 서비스 테스트 수정 완료

영향 범위

  • API 변경
  • DB 마이그레이션
  • Breaking Change
  • 보안/권한 영향
  • 문서/가이드 업데이트 필요

체크리스트

  • 타입 라벨 부착 (FEAT/FIX/REFACTOR/COMMENT/STYLE/TEST/CHORE/INIT)
  • 로컬/CI 테스트 통과
  • 영향도 점검 완료
  • 주석/문서 반영(필요 시)

ToDo (선택)

  • 할 일 1
  • 할 일 2

스크린샷/증빙(선택)

이슈 연결 (자동)

Closes #97

@johnbosco0414 johnbosco0414 self-assigned this Oct 10, 2025
@johnbosco0414 johnbosco0414 added the FEAT 새로운 기능 추가 label Oct 10, 2025
@Table(name = "scenarios",
indexes = {
@Index(name = "idx_scenario_user_status", columnList = "user_id, status, created_date"),
@Index(name = "idx_scenario_decision_line", columnList = "decision_line_id"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uniqueConstraints 생성 시에 유니크 속성으로 인해서 인덱스가 만들어지게 돼요.
이 부분 @Index(name = "idx_scenario_decision_line", columnList = "decision_line_id")은 지워도 무방할 것 같습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영했습니다! 리뷰 감사드립니다 :)

Comment on lines +61 to +66
String s3Url = String.format(
"https://%s.s3.%s.amazonaws.com/%s",
imageAiConfig.getS3BucketName(),
imageAiConfig.getS3Region(),
fileName
);
Copy link
Collaborator

@Gooraeng Gooraeng Oct 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

너무 좋습니다! 👍

url의 경우에는 영건님께서 구상하고 계신 게 거의 맞습니다.
다만, 지금 이 url은 s3를 통해 직접 접근하는 방식이고, 현재 우리 팀의 버킷 설정 상 s3로의 직접적인 퍼블릭 접근이 막혀있어요.
그래서 제가 cloudfront -> s3 를 통해 리소스 접근을 하게 한 것인데요. 왜 이렇게 했는지 알려드릴게요!

  1. 리소스 낭비 줄이기
    S3의 경우 요청(Get), 저장(Put), 삭제(Delete), 복사(Copy) 등 다양한 요청 방식으로부터 요금이 청구되는 방식이예요.
    그렇다 보니, 우리가 만약 이미지를 요청하는 시나리오 창에서 영건님의 구현 방향 (s3로 직접 Get)으로 가게 되면, 그만큼의 Get 요청이 날아게 되고, 높은 요금 청구로 이어지게 될 가능성이 생겨요. 그래서 이미지, 정적 웹사이트 등과 같은 정적 데이터의 경우에는 CloudFront와 같은 CDN 서비스를 통해 호출하는 방식을 권장하고 있어요.

  2. CloudFront의 동작 방식
    위에서 말씀드린 것처럼 CloudFront -> S3를 통해 요청을 보내는 방향으로 제가 설정해놓았는데요.
    최초로 이미지를 불러온다고 하면 CloudFront가 S3로부터 경로에 맞는 사진을 달라고 요청합니다. 이 때 처음 불러오는 거라면 S3에 Get 요청이 온 것이므로 Get 비용이 발생됩니다. 그리고 CloudFront 서버에서는 이 이미지에 대해 캐싱 작업을 하게 돼요. 여기가 CloudFront의 장점이 드러나는 부분인데요. 사용자가 새로고침 등으로 인해 이미지를 다시 요청한다고 하면, CloudFront -> S3로 가는 게 아니라, CloudFront에서 캐싱된 이미지를 바로 리턴해줍니다. 그래서 S3 쪽에 Get 요청이 가지 않게 돼요.
    다만, 제가 ttl 이라고 하는 "최초 요청 이후부터 몇 초 동안은 캐싱하게 해주세요" 라고 하는 부분이 있어서, 일정 시간이 지나고 나면 그 때 Get 요청이 S3에 다시 가게 돼요.

  3. 보안
    S3에 직접적인 퍼블릭 접근이 웬만하면 막혀있기 때문에, 권한이 없는 외부 사람들이 악의적으로 이미지를 삭제하는 등의 행위를 일체 방어하는 게 가능해지죠.

  4. CloudFront의 비용적 메리트
    S3에 비해 장점을 가져요. 프리티어를 사용하고 있지 않고, 서비스 규모가 작은 현재 상황에서는 이 차이가 더욱 커지게 되구요.

    • CloudFront
      • 매월 요청(http / https) 수 1천만 건 무료 => 이후 요금 부과 시작
      • 매월 대역폭 1TB 무료 => 이후 요금 부과 시작
      • 상시 CloudFront가 S3에서 데이터를 가져올 때 전송 비용 무료
      • 캐싱된 요청 덕에 response 사이즈가 작음
    • S3
      • 요청마다 비용 부과
      • 대역폭 무료 없음. 따라서 이미지 캐시가 되지 않으므로 매 요청마다 이미지 크기에 해당하는 요금 발생
        • jpeg로 고민해주신 거 너무 좋아요! 👍👍
  5. 그러면 어떻게 이미지 url을 줘야 할까요?
    저장해주실 때, (https:// - 선택) + AWS_CLOUD_FRONT_DOMAIN (.env.production 참고) + "/" + 파일명 조합으로 해주시는 게 좋아요. => https://AWS_CLOUD_FRONT_DOMAIN/파일명
    그러면 이제 프론트 측에서 이미지 요청을 보낼 때는 우리 cdn 링크로 요청을 보내게 될 거예요!
    한편 만약에 cdn 도메인이 바뀌면 나중에 어떻게 처리해야할까에 대한 고민이 남긴 합니다

Copy link
Collaborator

@lcs9317 lcs9317 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋습니다

Copy link
Collaborator

@simount3 simount3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다

@johnbosco0414 johnbosco0414 merged commit 7a6545d into main Oct 11, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FEAT 새로운 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT]: 시나리오 AI 이미지 생성 기능 구현

5 participants