Skip to content

Conversation

@Ji-soo708
Copy link
Member

@Ji-soo708 Ji-soo708 commented Jul 28, 2025

💡 작업 내용

✅ 셀프 체크리스트

  • PR 제목을 형식에 맞게 작성했나요?
  • 브랜치 전략에 맞는 브랜치에 PR을 올리고 있나요?
  • 테스트는 잘 통과했나요?
  • 빌드에 성공했나요?
  • 본인을 assign 해주세요.
  • 해당 PR에 맞는 label을 붙여주세요.

🙋🏻‍ 확인해주세요

  • 관련된 Discussion 등이 있다면 첨부해주세요

🔗 Jira 티켓


https://yappsocks.atlassian.net/browse/YS-509

Summary by CodeRabbit

  • 신규 기능

    • 실험 모집 공고문에서 키워드를 추출하는 API가 추가되었습니다. (연구자 권한 필요)
    • 키워드 추출 결과는 모집 대상, 신청 방법, 보상, 모집 인원, 소요 시간 등 구조화된 정보로 제공합니다.
  • 버그 수정

    • 해당 없음
  • 문서화

    • 키워드 추출 관련 API와 데이터 구조에 대한 Swagger 문서가 추가되었습니다.
  • 테스트

    • 키워드 추출 기능에 대한 단위 테스트가 추가되었습니다.

@coderabbitai
Copy link

coderabbitai bot commented Jul 28, 2025

Walkthrough

실험 게시글의 텍스트에서 키워드를 추출하는 새로운 기능이 도입되었습니다. 이를 위해 도메인, 인프라, 서비스, 프레젠테이션 계층에 걸쳐 유스케이스, 게이트웨이, 매퍼, DTO, 예외, API 엔드포인트, 테스트 등이 추가되었습니다. OpenAI API 연동 및 프롬프트 템플릿 로딩도 포함됩니다.

Changes

Cohort / File(s) Change Summary
유스케이스 및 서비스 계층
application/src/main/kotlin/com/dobby/service/ExperimentPostService.kt, application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt, application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt
실험 게시글 키워드 추출 유스케이스 및 서비스 계층 메서드 추가, 관련 테스트 클래스 신설
도메인 모델 및 게이트웨이
domain/src/main/kotlin/com/dobby/exception/DobbyException.kt, domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentKeywordExtractionGateway.kt, domain/src/main/kotlin/com/dobby/model/experiment/keyword/ApplyMethodKeyword.kt, domain/src/main/kotlin/com/dobby/model/experiment/keyword/ExperimentPostKeyword.kt, domain/src/main/kotlin/com/dobby/model/experiment/keyword/TargetGroupKeyword.kt
키워드 추출 도메인 모델, 게이트웨이 인터페이스, OpenAI 예외 추가
인프라: OpenAI 연동 및 매핑
infrastructure/src/main/kotlin/com/dobby/config/OpenAiFeignConfig.kt, infrastructure/src/main/kotlin/com/dobby/config/properties/OpenAiProperties.kt, infrastructure/src/main/kotlin/com/dobby/external/feign/openAi/OpenAiFeignClient.kt, infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentKeywordExtractionGatewayImpl.kt, infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt, infrastructure/src/main/kotlin/com/dobby/external/prompt/PromptTemplate.kt, infrastructure/src/main/kotlin/com/dobby/external/prompt/PromptTemplateLoader.kt, infrastructure/src/main/kotlin/com/dobby/external/prompt/dto/ApplyMethodDto.kt, infrastructure/src/main/kotlin/com/dobby/external/prompt/dto/ExperimentPostKeywordDto.kt, infrastructure/src/main/kotlin/com/dobby/external/prompt/dto/TargetGroupDto.kt
OpenAI API 연동용 Feign 클라이언트, 요청/응답 DTO, 프롬프트 템플릿 로더 및 매퍼, 설정 클래스, 게이트웨이 구현체 추가
프롬프트 리소스
infrastructure/src/main/resources/prompts/keyword_extraction_prompt.json
키워드 추출 프롬프트 JSON 리소스 파일 추가
프레젠테이션 계층: API/DTO/매퍼
presentation/src/main/kotlin/com/dobby/api/controller/ExperimentPostController.kt, presentation/src/main/kotlin/com/dobby/api/dto/request/OpenAiRequest.kt, presentation/src/main/kotlin/com/dobby/api/dto/request/experiment/ExtractKeywordRequest.kt, presentation/src/main/kotlin/com/dobby/api/dto/response/OpenAiResponse.kt, presentation/src/main/kotlin/com/dobby/api/dto/response/experiment/ExtractKeywordResponse.kt, presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt
키워드 추출 API 엔드포인트, 요청/응답 DTO, 매퍼 함수 추가

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant UseCase
    participant Gateway
    participant OpenAI

    Client->>Controller: 키워드 추출 요청(ExtractKeywordRequest)
    Controller->>Service: extractExperimentPostKeywords(input)
    Service->>UseCase: execute(input)
    UseCase->>Gateway: extractKeywords(text)
    Gateway->>OpenAI: chatCompletion(OpenAiRequest)
    OpenAI-->>Gateway: OpenAiResponse(추출 결과)
    Gateway-->>UseCase: ExperimentPostKeyword
    UseCase-->>Service: Output(ExperimentPostKeyword)
    Service-->>Controller: Output(ExperimentPostKeyword)
    Controller-->>Client: ExtractKeywordResponse
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

✅ TEST, ✨ FEATURE

Poem

🐰
실험 글 속 숨은 뜻,
토끼가 키워드 쏙쏙 뽑았네.
OpenAI와 대화하며
프롬프트도 척척 불러오고,
API로 뚝딱 결과 전송!
연구자님, 이제 걱정 마세요—
토끼가 다 알아서 해줄게요!

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/YS-509

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@Ji-soo708 Ji-soo708 changed the title [YS-509] feat: 공고 키워드 자동완성 기능 구현 [YS-509] feat: 공고 키워드 자동완성 기능 구현 (재배포) Jul 28, 2025
@Ji-soo708 Ji-soo708 self-assigned this Jul 28, 2025
@Ji-soo708 Ji-soo708 added 🐛 BUG 버그 ⚙️ CHORE config, workflow.yaml ✨ FEATURE 기능 추가 🔥 HOTFIX 핫픽스 labels Jul 28, 2025
@Ji-soo708 Ji-soo708 merged commit eb9e822 into dev Jul 28, 2025
3 of 4 checks passed
@Ji-soo708 Ji-soo708 deleted the feat/YS-509 branch July 28, 2025 09:50
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (5)
infrastructure/src/main/kotlin/com/dobby/config/properties/OpenAiProperties.kt (1)

6-14: API Key 미설정 검증 로직 추가 제안

운영 환경에서 openai.api.key 값이 비어 있으면 즉시 장애로 이어집니다. @Validated@field:NotBlank를 활용해 부트 초기화 시점에 검증하도록 권장합니다.

 import org.springframework.boot.context.properties.ConfigurationProperties
 import org.springframework.stereotype.Component
+import org.springframework.validation.annotation.Validated
+import jakarta.validation.constraints.NotBlank
 
 @Component
-@ConfigurationProperties(prefix = "openai")
+@ConfigurationProperties(prefix = "openai")
+@Validated
 data class OpenAiProperties(
     var api: Api = Api()
 ) {
     data class Api(
-        var key: String = ""
+        @field:NotBlank
+        var key: String = ""
     )
 }
infrastructure/src/main/kotlin/com/dobby/external/prompt/PromptTemplate.kt (1)

3-9: 타입 안정성 및 네이밍 컨벤션 고려 필요

  1. output_formatMap<String, Any>로 두면 후속 로직에서 값 타입 캐스팅이 잦아집니다. 자료구조를 별도 DTO로 명시하면 타입 안정성이 향상됩니다.
  2. 속성명이 snake_case라서 Kotlin 관례와 다릅니다. JSON 매핑 목적이라면 @JsonProperty("output_format") 등으로 매핑하고 프로퍼티 자체는 camelCase로 두는 방식을 고려해 보세요.

큰 변경이 필요 없다면 그대로 유지해도 무방하나, 장기적인 유지보수를 위해 검토를 권장합니다.

domain/src/main/kotlin/com/dobby/model/experiment/keyword/ApplyMethodKeyword.kt (1)

3-9: 🔧 Boolean 기본값을 지정하여 생성 편의성 향상
isFormUrl / isPhoneNum 가 필수(true/false) 값이지만 기본값이 없어서 생성 시 매번 명시해야 합니다. 기본값을 false 로 주면 DTO ↔︎ 도메인 매핑 코드가 간결해집니다.

-data class ApplyMethodKeyword(
-    val content: String?,
-    val isFormUrl: Boolean,
-    val formUrl: String?,
-    val isPhoneNum: Boolean,
-    val phoneNum: String?
+data class ApplyMethodKeyword(
+    val content: String? = null,
+    val isFormUrl: Boolean = false,
+    val formUrl: String? = null,
+    val isPhoneNum: Boolean = false,
+    val phoneNum: String? = null
 )
infrastructure/src/main/kotlin/com/dobby/external/prompt/dto/TargetGroupDto.kt (1)

4-7: 🤔 DTO 레이어에서도 Enum 타입을 사용하는 편이 직렬화-역직렬화 오류를 줄입니다
genderTypeString 으로 두면 오타나 예상치 못한 값이 들어왔을 때 컴파일 타임 검증이 불가능합니다. DTO 에도 GenderType? 을 사용하고, 필요 시 @JsonProperty@JsonValue 로 직렬화 형식을 맞추는 방안을 고려해 주세요.

infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentKeywordExtractionGatewayImpl.kt (1)

37-41: 선택적 개선 사항: 모델 및 온도 파라미터 설정 가능하게 만들기

현재 gpt-4o 모델과 0.2 온도가 하드코딩되어 있습니다. 향후 유연성을 위해 설정 파일로 외부화하는 것을 고려해보세요.

Properties 클래스에 다음과 같이 추가할 수 있습니다:

// OpenAiProperties에 추가
val model: String = "gpt-4o"
val temperature: Double = 0.2
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4051af and 7ccfe47.

📒 Files selected for processing (25)
  • application/src/main/kotlin/com/dobby/service/ExperimentPostService.kt (3 hunks)
  • application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt (1 hunks)
  • application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt (1 hunks)
  • domain/src/main/kotlin/com/dobby/exception/DobbyException.kt (1 hunks)
  • domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentKeywordExtractionGateway.kt (1 hunks)
  • domain/src/main/kotlin/com/dobby/model/experiment/keyword/ApplyMethodKeyword.kt (1 hunks)
  • domain/src/main/kotlin/com/dobby/model/experiment/keyword/ExperimentPostKeyword.kt (1 hunks)
  • domain/src/main/kotlin/com/dobby/model/experiment/keyword/TargetGroupKeyword.kt (1 hunks)
  • infrastructure/src/main/kotlin/com/dobby/config/OpenAiFeignConfig.kt (1 hunks)
  • infrastructure/src/main/kotlin/com/dobby/config/properties/OpenAiProperties.kt (1 hunks)
  • infrastructure/src/main/kotlin/com/dobby/external/feign/openAi/OpenAiFeignClient.kt (1 hunks)
  • infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentKeywordExtractionGatewayImpl.kt (1 hunks)
  • infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt (1 hunks)
  • infrastructure/src/main/kotlin/com/dobby/external/prompt/PromptTemplate.kt (1 hunks)
  • infrastructure/src/main/kotlin/com/dobby/external/prompt/PromptTemplateLoader.kt (1 hunks)
  • infrastructure/src/main/kotlin/com/dobby/external/prompt/dto/ApplyMethodDto.kt (1 hunks)
  • infrastructure/src/main/kotlin/com/dobby/external/prompt/dto/ExperimentPostKeywordDto.kt (1 hunks)
  • infrastructure/src/main/kotlin/com/dobby/external/prompt/dto/TargetGroupDto.kt (1 hunks)
  • infrastructure/src/main/resources/prompts/keyword_extraction_prompt.json (1 hunks)
  • presentation/src/main/kotlin/com/dobby/api/controller/ExperimentPostController.kt (3 hunks)
  • presentation/src/main/kotlin/com/dobby/api/dto/request/OpenAiRequest.kt (1 hunks)
  • presentation/src/main/kotlin/com/dobby/api/dto/request/experiment/ExtractKeywordRequest.kt (1 hunks)
  • presentation/src/main/kotlin/com/dobby/api/dto/response/OpenAiResponse.kt (1 hunks)
  • presentation/src/main/kotlin/com/dobby/api/dto/response/experiment/ExtractKeywordResponse.kt (1 hunks)
  • presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt (4 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#150
File: infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt:52-58
Timestamp: 2025-07-17T08:06:16.021Z
Learning: AI가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(IllegalArgumentException)는 정상적인 상황에 가깝다. 따라서 ExperimentPostKeywordMapper에서 TimeSlot, MatchType 등의 enum 변환 실패 시 로깅을 추가하지 않는 것이 적절하다.
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#140
File: domain/src/main/kotlin/com/dobby/gateway/auth/GoogleAuthGateway.kt:7-7
Timestamp: 2025-06-04T09:02:40.657Z
Learning: Ji-soo708의 팀은 로컬 ktlint 기준과 GitHub ktlint 체크 기준이 다를 때, 기존 코드베이스와의 일관성을 위해 로컬 기준을 우선시한다.
Learnt from: Ji-soo708
PR: YAPP-Github/Gradmeet-BE#154
File: infrastructure/src/main/kotlin/com/dobby/mapper/ExperimentPostKeywordsLogMapper.kt:15-22
Timestamp: 2025-07-27T12:37:15.372Z
Learning: Ji-soo708의 팀에서는 JSON 역직렬화 시 예외를 catch하여 래핑하기보다는, 예외를 자연스럽게 상위로 전파시켜 원본 컨텍스트를 보존하고 후속 조치를 용이하게 하는 것을 선호한다.
presentation/src/main/kotlin/com/dobby/api/dto/response/experiment/ExtractKeywordResponse.kt (1)

Learnt from: Ji-soo708
PR: #150
File: infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt:52-58
Timestamp: 2025-07-17T08:06:16.021Z
Learning: AI가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(IllegalArgumentException)는 정상적인 상황에 가깝다. 따라서 ExperimentPostKeywordMapper에서 TimeSlot, MatchType 등의 enum 변환 실패 시 로깅을 추가하지 않는 것이 적절하다.

infrastructure/src/main/kotlin/com/dobby/config/OpenAiFeignConfig.kt (1)

Learnt from: Ji-soo708
PR: #150
File: infrastructure/src/main/kotlin/com/dobby/external/feign/openAi/OpenAiFeignClient.kt:10-14
Timestamp: 2025-07-17T08:06:13.596Z
Learning: Ji-soo708의 팀은 현재 OpenAI 공식 API만 사용할 예정이므로, FeignClient URL을 외부화하지 않고 하드코딩하는 것을 선호한다. 팀의 요구사항에 따라 단순함을 우선시하는 접근 방식을 취한다.

infrastructure/src/main/kotlin/com/dobby/external/feign/openAi/OpenAiFeignClient.kt (1)

Learnt from: Ji-soo708
PR: #150
File: infrastructure/src/main/kotlin/com/dobby/external/feign/openAi/OpenAiFeignClient.kt:10-14
Timestamp: 2025-07-17T08:06:13.596Z
Learning: Ji-soo708의 팀은 현재 OpenAI 공식 API만 사용할 예정이므로, FeignClient URL을 외부화하지 않고 하드코딩하는 것을 선호한다. 팀의 요구사항에 따라 단순함을 우선시하는 접근 방식을 취한다.

domain/src/main/kotlin/com/dobby/model/experiment/keyword/ExperimentPostKeyword.kt (1)

Learnt from: Ji-soo708
PR: #150
File: infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt:52-58
Timestamp: 2025-07-17T08:06:16.021Z
Learning: AI가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(IllegalArgumentException)는 정상적인 상황에 가깝다. 따라서 ExperimentPostKeywordMapper에서 TimeSlot, MatchType 등의 enum 변환 실패 시 로깅을 추가하지 않는 것이 적절하다.

presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt (1)

Learnt from: Ji-soo708
PR: #150
File: infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt:52-58
Timestamp: 2025-07-17T08:06:16.021Z
Learning: AI가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(IllegalArgumentException)는 정상적인 상황에 가깝다. 따라서 ExperimentPostKeywordMapper에서 TimeSlot, MatchType 등의 enum 변환 실패 시 로깅을 추가하지 않는 것이 적절하다.

infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt (1)

Learnt from: Ji-soo708
PR: #150
File: infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt:52-58
Timestamp: 2025-07-17T08:06:16.021Z
Learning: AI가 텍스트에서 키워드를 추출하는 과정에서 enum 변환 실패(IllegalArgumentException)는 정상적인 상황에 가깝다. 따라서 ExperimentPostKeywordMapper에서 TimeSlot, MatchType 등의 enum 변환 실패 시 로깅을 추가하지 않는 것이 적절하다.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (21)
domain/src/main/kotlin/com/dobby/gateway/experiment/ExperimentKeywordExtractionGateway.kt (1)

5-7: 도메인 계층 인터페이스 설계 적합 확인
현재 인터페이스는 단순 계약만 정의하며 외부 의존성이 없고, 반환 타입도 명확합니다. 특이사항 없습니다.

infrastructure/src/main/resources/prompts/keyword_extraction_prompt.json (1)

16-35: 💡 도메인 Enum 이름과 JSON Enum 값의 일치 여부를 반드시 확인하세요
matchType, genderType, timeRequired 등에 사용된 문자열 리터럴이 실제 코드상의 Enum 상수와 정확히 일치하지 않으면 매핑 시 IllegalArgumentException 이 발생합니다.
특히 "timeRequired" 값의 LESS_30M / ABOUT_30M … 목록과, 도메인 계층에 정의된 시간 구간 Enum 이름이 1:1 대응하는지 검증이 필요합니다.

presentation/src/main/kotlin/com/dobby/api/dto/request/experiment/ExtractKeywordRequest.kt (1)

6-10: LGTM! 잘 구조화된 DTO입니다.

텍스트 필드에 대한 적절한 유효성 검증(@notblank)과 Swagger 문서화(@Schema)가 포함되어 있습니다. 예시 텍스트도 도메인에 적합합니다.

application/src/main/kotlin/com/dobby/service/ExperimentPostService.kt (3)

11-11: 적절한 의존성 추가입니다.

새로운 키워드 추출 기능을 위한 UseCase 의존성이 올바르게 추가되었습니다.


45-45: 생성자 주입이 올바르게 구현되었습니다.

새로운 UseCase가 private 필드로 적절히 주입되었습니다.


170-172: 서비스 메서드가 적절히 구현되었습니다.

UseCase로의 단순한 위임 패턴을 따르고 있으며, 외부 API 호출이므로 @transactional을 사용하지 않은 것이 적절합니다.

domain/src/main/kotlin/com/dobby/model/experiment/keyword/ExperimentPostKeyword.kt (1)

6-13: 도메인 모델이 잘 설계되었습니다.

AI 키워드 추출 특성상 모든 필드가 nullable로 설정된 것이 적절하며, 도메인 enum과 관련 키워드 객체를 활용한 구조가 좋습니다.

infrastructure/src/main/kotlin/com/dobby/external/feign/openAi/OpenAiFeignClient.kt (1)

10-18: Feign 클라이언트가 올바르게 구성되었습니다.

OpenAI API URL 하드코딩은 팀의 선호에 따른 단순함을 우선시하는 접근 방식으로 적절합니다. 커스텀 설정 클래스를 통한 인증 처리도 좋은 구조입니다.

infrastructure/src/main/kotlin/com/dobby/external/prompt/dto/ApplyMethodDto.kt (1)

1-10: 깔끔한 DTO 구조입니다.

프로퍼티들이 적절한 기본값과 함께 잘 정의되어 있고, 키워드 추출 결과를 담기에 적합한 구조입니다.

infrastructure/src/main/kotlin/com/dobby/external/prompt/PromptTemplateLoader.kt (1)

6-19: 리소스 로딩 로직이 잘 구현되었습니다.

Jackson을 사용한 JSON 역직렬화와 적절한 에러 처리, 리소스 관리가 잘 되어 있습니다. use 함수를 통한 자동 리소스 해제도 적절합니다.

presentation/src/main/kotlin/com/dobby/api/controller/ExperimentPostController.kt (1)

222-235: 일관된 패턴으로 잘 구현된 엔드포인트입니다.

기존 컨트롤러의 패턴을 일관되게 따르고 있으며, 적절한 인증 설정과 문서화가 되어 있습니다.

presentation/src/main/kotlin/com/dobby/api/dto/request/OpenAiRequest.kt (1)

1-13: OpenAI API 요청 구조가 적절히 모델링되었습니다.

OpenAI의 채팅 완성 API 스펙에 맞는 구조로 잘 정의되어 있습니다. 중첩된 Message 클래스도 적절합니다.

application/src/test/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCaseTest.kt (1)

11-34: 유스케이스 테스트가 적절히 작성되었습니다.

BDD 스타일로 명확하게 구조화되어 있고, 게이트웨이 모킹과 검증이 올바르게 구현되어 있습니다. 팀의 테스트 패턴을 잘 따르고 있습니다.

presentation/src/main/kotlin/com/dobby/api/mapper/ExperimentPostMapper.kt (1)

524-534: LGTM! 키워드 추출 매핑 함수가 올바르게 구현되었습니다.

매핑 함수들이 기존 패턴과 일관성 있게 구현되어 있고, presentation 계층과 application 계층 간의 명확한 분리를 제공합니다.

infrastructure/src/main/kotlin/com/dobby/external/prompt/dto/ExperimentPostKeywordDto.kt (1)

5-14: LGTM! AI 응답 파싱에 적합한 DTO 설계입니다.

Jackson 어노테이션을 통한 JSON 매핑이 올바르게 설정되어 있고, 모든 필드가 nullable로 설계되어 AI 키워드 추출 결과의 불확실성을 적절히 반영하고 있습니다.

application/src/main/kotlin/com/dobby/usecase/experiment/ExtractExperimentPostKeywordsUseCase.kt (1)

7-17: LGTM! UseCase 패턴이 올바르게 구현되었습니다.

Gateway로의 단순한 위임을 통해 관심사의 분리가 잘 이루어져 있고, Input/Output 클래스 구조가 명확합니다.

infrastructure/src/main/kotlin/com/dobby/external/prompt/ExperimentPostKeywordMapper.kt (2)

17-60: LGTM! AI 키워드 추출의 특성을 잘 반영한 매핑 로직입니다.

Enum 변환 실패 시 기본값으로 처리하는 로직이 적절합니다. 이전 학습 내용에 따르면, AI 텍스트 추출 과정에서 enum 변환 실패는 정상적인 상황이므로 현재의 예외 처리 방식이 올바릅니다.


62-87: LGTM! Domain에서 DTO로의 역변환이 올바르게 구현되었습니다.

Null 처리와 enum의 name 속성 사용이 적절하며, 양방향 매핑이 일관성 있게 구현되어 있습니다.

infrastructure/src/main/kotlin/com/dobby/external/gateway/experiment/ExperimentKeywordExtractionGatewayImpl.kt (3)

30-60: LGTM! OpenAI API 통합이 견고하게 구현되었습니다.

예외 처리가 포괄적이며, FeignException과 일반 예외를 적절히 구분하여 처리하고 있습니다. JSON 응답 정리 로직도 AI 응답의 특성(마크다운 코드 블록)을 잘 고려했습니다.


62-68: LGTM! JSON 문자열 이스케이프 로직이 적절합니다.

주요 JSON 특수 문자들을 올바르게 처리하고 있습니다.


70-78: LGTM! AI 응답 정리 로직이 효과적입니다.

마크다운 코드 블록과 백틱 제거 로직이 AI 응답의 특성을 잘 반영하고 있습니다.

Comment on lines +81 to +84
/**
* OpenAI API call specific exceptions
*/
data class CustomOpenAiCallException(override val message: String, override val cause: Throwable? = null) : ServerException("AI0001", message, cause)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

CustomOpenAiCallException에서 컴파일 오류 발생 가능성

Throwable.messageString? 타입이므로, 이를 override val message: String(nonnull)로 재정의하면 null-ability가 좁아져 컴파일이 실패합니다. 또한 예외를 data class로 선언할 필요성이 낮아 equals/hashCode 생성 등 부가 오버헤드만 증가합니다.

-data class CustomOpenAiCallException(override val message: String, override val cause: Throwable? = null) : ServerException("AI0001", message, cause)
+class CustomOpenAiCallException(
+    message: String,
+    cause: Throwable? = null
+) : ServerException("AI0001", message, cause)
🤖 Prompt for AI Agents
In domain/src/main/kotlin/com/dobby/exception/DobbyException.kt around lines 81
to 84, the CustomOpenAiCallException class overrides the nullable
Throwable.message property with a non-null String, causing a compile error due
to nullability mismatch. Also, declaring it as a data class is unnecessary and
adds overhead. To fix this, change CustomOpenAiCallException to a regular class
(not data class) and override message with a nullable String type matching
Throwable.message.

Comment on lines +5 to +10
data class TargetGroupKeyword(
var startAge: Int?,
var endAge: Int?,
var genderType: GenderType?,
var otherCondition: String?
)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ 도메인 객체의 불변성 확보를 위해 var 대신 val 사용 고려
도메인 모델은 생성 이후 변하지 않는 값이 대부분이므로 가변 프로퍼티(var)를 사용하면 예기치 않은 상태 변경이 가능해집니다. 불변 객체로 유지해도 기능 요구사항에는 영향이 없으니 다음과 같이 수정하는 것을 권장합니다.

-data class TargetGroupKeyword(
-    var startAge: Int?,
-    var endAge: Int?,
-    var genderType: GenderType?,
-    var otherCondition: String?
+data class TargetGroupKeyword(
+    val startAge: Int? = null,
+    val endAge: Int? = null,
+    val genderType: GenderType? = null,
+    val otherCondition: String? = null
 )
🤖 Prompt for AI Agents
In
domain/src/main/kotlin/com/dobby/model/experiment/keyword/TargetGroupKeyword.kt
around lines 5 to 10, the properties are declared with 'var', making them
mutable. To ensure domain object immutability and prevent unintended state
changes, change all 'var' declarations to 'val' since the values do not need to
change after creation.

Comment on lines +7 to +9
class OpenAiFeignConfig(
private val openAiProperties: OpenAiProperties
) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

@configuration 어노테이션이 누락되었습니다.

Spring 설정 클래스로 인식되도록 @configuration 어노테이션을 추가해야 합니다.

다음과 같이 수정하세요:

+@Configuration
 class OpenAiFeignConfig(
     private val openAiProperties: OpenAiProperties
 ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class OpenAiFeignConfig(
private val openAiProperties: OpenAiProperties
) {
@Configuration
class OpenAiFeignConfig(
private val openAiProperties: OpenAiProperties
) {
// existing bean definitions, e.g. RequestInterceptor bean
}
🤖 Prompt for AI Agents
In infrastructure/src/main/kotlin/com/dobby/config/OpenAiFeignConfig.kt around
lines 7 to 9, the class OpenAiFeignConfig is missing the @Configuration
annotation. Add the @Configuration annotation above the class declaration to
ensure Spring recognizes it as a configuration class.

Comment on lines +12 to +15
return RequestInterceptor { template ->
template.header("Authorization", "Bearer ${openAiProperties.api.key}")
template.header("Content-Type", "application/json")
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

API 키에 대한 null 안전성 검증을 추가하세요.

API 키가 null이거나 비어있을 경우 잘못된 Authorization 헤더가 생성될 수 있습니다.

다음과 같이 개선하는 것을 권장합니다:

 return RequestInterceptor { template ->
-    template.header("Authorization", "Bearer ${openAiProperties.api.key}")
+    val apiKey = openAiProperties.api.key
+    require(!apiKey.isNullOrBlank()) { "OpenAI API key must not be null or blank" }
+    template.header("Authorization", "Bearer $apiKey")
     template.header("Content-Type", "application/json")
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return RequestInterceptor { template ->
template.header("Authorization", "Bearer ${openAiProperties.api.key}")
template.header("Content-Type", "application/json")
}
return RequestInterceptor { template ->
val apiKey = openAiProperties.api.key
require(!apiKey.isNullOrBlank()) { "OpenAI API key must not be null or blank" }
template.header("Authorization", "Bearer $apiKey")
template.header("Content-Type", "application/json")
}
🤖 Prompt for AI Agents
In infrastructure/src/main/kotlin/com/dobby/config/OpenAiFeignConfig.kt around
lines 12 to 15, the code sets the Authorization header using the API key without
checking if the key is null or empty, which can lead to invalid headers. Add a
null and empty check for openAiProperties.api.key before setting the
Authorization header, and only set the header if the key is valid to ensure null
safety and prevent malformed requests.

Comment on lines +6 to +8
data class ExtractKeywordResponse(
@Schema(description = "추출된 키워드 정보")
val experimentPostKeyword: ExperimentPostKeyword
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

🚥 API 응답에서 도메인 객체 직접 노출은 계층 간 의존도를 높입니다
ExperimentPostKeyword 가 변경될 때마다 API 스펙이 깨질 위험이 있습니다. 별도 Response DTO 로 변환해 노출 계층을 분리하는 것을 권장합니다.

data class ExtractKeywordResponse(
    @Schema(description = "추출된 키워드 정보")
    val keyword: ExtractKeywordDto   // ← 프레젠테이션 전용 DTO
)

이렇게 하면 도메인 변경이 외부 계약(API)에 영향을 주지 않습니다.

🤖 Prompt for AI Agents
In
presentation/src/main/kotlin/com/dobby/api/dto/response/experiment/ExtractKeywordResponse.kt
around lines 6 to 8, the API response directly exposes the domain object
ExperimentPostKeyword, which increases coupling between layers and risks
breaking the API contract if the domain changes. To fix this, create a separate
presentation-layer DTO (e.g., ExtractKeywordDto) that maps the necessary fields
from ExperimentPostKeyword, and update ExtractKeywordResponse to use this new
DTO instead of the domain object, thereby decoupling the API response from
domain changes.

Comment on lines +3 to +14
data class OpenAiResponse(
val choices: List<Choice>
) {
data class Choice(
val message: Message
)

data class Message(
val role: String,
val content: String
)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

미정의 필드가 포함된 OpenAI 응답 파싱 실패 우려

OpenAI Chat Completion API 응답에는 id, usage, 등 다수의 필드가 존재합니다. 현재 클래스에 정의되지 않은 속성이 전달되면 Jackson 기본 설정에서 FAIL_ON_UNKNOWN_PROPERTIES=true 인 경우 역직렬화가 실패할 수 있습니다. 안전하게 무시하도록 어노테이션을 추가해 주세요.

+import com.fasterxml.jackson.annotation.JsonIgnoreProperties
+
-data class OpenAiResponse(
+@JsonIgnoreProperties(ignoreUnknown = true)
+data class OpenAiResponse(
     val choices: List<Choice>
 ) {
+    @JsonIgnoreProperties(ignoreUnknown = true)
     data class Choice(
         val message: Message
     )
 
+    @JsonIgnoreProperties(ignoreUnknown = true)
     data class Message(
         val role: String,
         val content: String
     )
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data class OpenAiResponse(
val choices: List<Choice>
) {
data class Choice(
val message: Message
)
data class Message(
val role: String,
val content: String
)
}
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true)
data class OpenAiResponse(
val choices: List<Choice>
) {
@JsonIgnoreProperties(ignoreUnknown = true)
data class Choice(
val message: Message
)
@JsonIgnoreProperties(ignoreUnknown = true)
data class Message(
val role: String,
val content: String
)
}
🤖 Prompt for AI Agents
In presentation/src/main/kotlin/com/dobby/api/dto/response/OpenAiResponse.kt
around lines 3 to 14, the OpenAiResponse data class lacks handling for unknown
JSON properties, which can cause deserialization failures if the API response
includes fields like id or usage not defined in the class. Add the Jackson
annotation @JsonIgnoreProperties(ignoreUnknown = true) to the OpenAiResponse
class to safely ignore any undefined fields during deserialization.

chock-cho pushed a commit that referenced this pull request Oct 21, 2025
* feat: add ExperimentKeyword for prompt

* feat: add ExperimentKeywordExtractionGateway with OpenAI integration

* feat: configure OpenAI API client with Feign

* feat: add prompt json file for keyword extraction

* fix: fix typo

* feat: add extractExperimentPostKeywords api

* fix: add default values to Dto fields for null safety

* style: apply ktlint formatting

* test: add ExtractExperimentPostKeywordsUseCase test code

* refactor: reuse ObjectMapper instance to improve efficiency

* feat: add CustomOpenAiCallException for OpenAI API call errors

* refactor: move ExperimentPostKeyword DTO to `/prompt/dto` for better clarity

* style: add ktlint formatting

* refactor: replace `@Service` with `@Component`

* feat: add field length limits to extraction prompt

* fix: restrict OpenAI Feign interceptor to avoid affecting global OAuth clients
chock-cho pushed a commit that referenced this pull request Oct 21, 2025
* feat: add ExperimentKeyword for prompt

* feat: add ExperimentKeywordExtractionGateway with OpenAI integration

* feat: configure OpenAI API client with Feign

* feat: add prompt json file for keyword extraction

* fix: fix typo

* feat: add extractExperimentPostKeywords api

* fix: add default values to Dto fields for null safety

* style: apply ktlint formatting

* test: add ExtractExperimentPostKeywordsUseCase test code

* refactor: reuse ObjectMapper instance to improve efficiency

* feat: add CustomOpenAiCallException for OpenAI API call errors

* refactor: move ExperimentPostKeyword DTO to `/prompt/dto` for better clarity

* style: add ktlint formatting

* refactor: replace `@Service` with `@Component`

* feat: add field length limits to extraction prompt

* fix: restrict OpenAI Feign interceptor to avoid affecting global OAuth clients
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐛 BUG 버그 ⚙️ CHORE config, workflow.yaml ✨ FEATURE 기능 추가 🔥 HOTFIX 핫픽스

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants