Skip to content

feat: 상품 검색 API 구현#29

Merged
23tae merged 4 commits intodevelopfrom
feature/DND1302002-61
Aug 26, 2025
Merged

feat: 상품 검색 API 구현#29
23tae merged 4 commits intodevelopfrom
feature/DND1302002-61

Conversation

@23tae
Copy link
Copy Markdown
Member

@23tae 23tae commented Aug 26, 2025

📝 작업 내용

사용자가 입력한 키워드를 기반으로 상품 목록을 조회하는 검색 API를 구현했습니다.

⚡ 주요 변경사항

  • API Endpoint 추가: GET /api/v1/products/search
    • keyword 쿼리 파라미터를 통해 상품명(product.name)에 키워드가 포함된 상품 목록을 반환합니다.
  • 비즈니스 로직 구현
    • ProductService에 검색 로직을 추가했습니다.
    • 검색어가 비어있거나 공백으로만 이루어진 경우, DB 조회 없이 빈 목록([])을 반환하도록 예외 처리했습니다.
  • 테스트 코드 작성
  • API 문서화

📌 리뷰 포인트

📋 체크리스트

  • ✍ PR 제목 규칙을 맞췄나요? (예: feat: 기능1 추가)
  • 📝 관련 이슈를 등록했나요?
  • ✅ 모든 테스트가 통과했나요?
  • 🏗 빌드가 정상적으로 완료되었나요?

Summary by CodeRabbit

  • 신기능
    • 제품 검색 API 추가: GET /api/v1/products/search?keyword=...로 제품을 키워드로 검색하고 productId, name, itemName 목록을 반환합니다.
    • keyword 미제공 시 400 응답, 공백 키워드는 빈 목록을 반환합니다.
  • 문서
    • Swagger/OpenAPI에 제품 검색 엔드포인트 설명과 예시 응답/오류 케이스를 추가했습니다.
  • 테스트
    • 테스트 편의를 위한 Mockito-Kotlin 의존성을 추가했습니다.

@23tae 23tae self-assigned this Aug 26, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 26, 2025

Walkthrough

검색용 DTO와 서비스/저장소 메서드를 추가하고, GET /api/v1/products/search 컨트롤러 엔드포인트를 도입했으며 빌드 스크립트에 Mockito Kotlin 테스트 의존성(org.mockito.kotlin:mockito-kotlin:5.4.0)을 추가했다.

Changes

Cohort / File(s) Summary
Build config
build.gradle.kts
테스트 의존성 추가: testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
Product search API
src/main/kotlin/com/eodigo/domain/product/controller/ProductController.kt, src/main/kotlin/com/eodigo/domain/product/service/ProductService.kt, src/main/kotlin/com/eodigo/domain/product/repository/ProductRepository.kt, src/main/kotlin/com/eodigo/domain/product/dto/ProductSearchResponse.kt
- Controller: GET /api/v1/products/search 추가, keyword 쿼리 파라미터 수신, List<ProductSearchResponse> 반환, OpenAPI 어노테이션 추가
- Service: searchProducts(keyword: String) 추가; 빈 키워드면 빈 리스트 반환, 아니면 Repository 호출 및 DTO 매핑
- Repository: findByNameContaining(keyword: String): List<Product> 추가
- DTO: ProductSearchResponse 데이터 클래스 및 from(product: Product) 팩토리 메서드 추가

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Controller as ProductController
  participant Service as ProductService
  participant Repo as ProductRepository
  note over Controller,Service: GET /api/v1/products/search?keyword=...

  Client->>Controller: 요청 (keyword)
  alt keyword가 공백/blank
    Controller->>Service: searchProducts(keyword)
    Service-->>Controller: []
    Controller-->>Client: 200 OK, []
  else keyword가 유효
    Controller->>Service: searchProducts(keyword)
    Service->>Repo: findByNameContaining(keyword)
    Repo-->>Service: List<Product>
    Service-->>Controller: List<ProductSearchResponse>
    Controller-->>Client: 200 OK, List<ProductSearchResponse>
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • feat: Product API 초기 구현 #18 — 동일한 ProductController/ProductRepository 영역에 제품 검색 확장(검색 엔드포인트 및 repository 메서드 추가)과 직접적으로 관련됨.

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1d1d6a5 and bbcdaf8.

📒 Files selected for processing (1)
  • src/main/kotlin/com/eodigo/domain/product/dto/ProductSearchResponse.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/kotlin/com/eodigo/domain/product/dto/ProductSearchResponse.kt
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/DND1302002-61

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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

Copy link
Copy Markdown

@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: 0

🧹 Nitpick comments (5)
src/main/kotlin/com/eodigo/domain/product/repository/ProductRepository.kt (1)

14-14: 검색 일관성(대소문자 무시) 및 확장성(페이징) 고려 제안

현재 메서드는 DB/컬레이션에 따라 대소문자 민감도가 달라질 수 있습니다. 서비스 전역에서 일관된 사용자 경험을 위해 대소문자 무시를 명시하고, 결과 폭주를 방지하기 위해 페이징 지원을 고려해 주세요.

옵션 A: 대소문자 무시

-    fun findByNameContaining(keyword: String): List<Product>
+    fun findByNameContainingIgnoreCase(keyword: String): List<Product>

옵션 B: 대소문자 무시 + 페이징

+    // Page 요청을 허용하여 대량 결과를 제어
+    fun findByNameContainingIgnoreCase(keyword: String, pageable: org.springframework.data.domain.Pageable): org.springframework.data.domain.Page<Product>

성능 메모

  • name 컬럼에 인덱스(또는 MySQL이라면 Fulltext Index) 추가를 검토해 주세요. 대규모 데이터에 %keyword% LIKE는 풀스캔이 될 수 있습니다. 마이그레이션 예시(Flyway 기준):
CREATE INDEX idx_product_name ON product (name);
-- 또는
CREATE FULLTEXT INDEX ftx_product_name ON product (name);
src/main/kotlin/com/eodigo/domain/product/service/ProductService.kt (2)

132-140: 와일드카드('%','_') 포함 검색어의 의도치 않은 매칭 방지(선택)

사용자가 % 또는 _를 입력하면 LIKE 패턴으로 해석되어 범위가 과도하게 넓어질 수 있습니다. 필요 시 서비스 계층에서 이 두 문자를 이스케이프하고, Repository에서 ESCAPE 절을 쓰는 커스텀 쿼리로 전환하는 방법을 고려해 주세요.

다음 헬퍼를 추가하고(다른 파일에 배치 가능), term에 적용한 뒤 쿼리를 커스텀 JPQL/Native로 정의합니다.

private fun escapeLikeWildcards(input: String): String =
    input.replace("!", "!!").replace("%", "!%").replace("_", "!_")

Repository 예시:

@Query("select p from Product p where lower(p.name) like lower(concat('%', :term, '%')) escape '!'")
fun searchByNameLikeEscaped(@Param("term") term: String): List<Product>

132-140: 대량 결과 대비 페이징 도입 검토(선택)

검색 결과가 수천 건 이상이 될 수 있다면, 서비스·컨트롤러 서명에 Pageable을 추가하고 Repository도 Page<Product>로 바꾸는 것을 권장합니다. 이는 응답 시간과 메모리 사용량을 안정화합니다.

예시(참고용, 해당 파일 외 변경 필요):

fun searchProducts(keyword: String, pageable: Pageable): Page<ProductSearchResponse> = ...
src/main/kotlin/com/eodigo/domain/product/controller/ProductController.kt (1)

6-6: 엔드포인트 추가 전반은 깔끔합니다. 파라미터 문서화와 길이 제한을 소폭 보강하면 좋겠습니다

  • DTO를 응답으로 사용하는 점은 컨벤션에 적합합니다.
  • OpenAPI에 파라미터 설명/예시를 추가하면 클라이언트가 사용하기 쉬워집니다.
  • 과도한 입력 방지를 위해 길이 제한(예: 최대 50자)을 검토해 주세요. 빈 문자열은 서비스에서 빈 배열로 처리하므로 유지 가능합니다.
-    fun searchProducts(
-        @RequestParam("keyword") keyword: String
-    ): ResponseEntity<List<ProductSearchResponse>> {
+    fun searchProducts(
+        @io.swagger.v3.oas.annotations.Parameter(description = "검색 키워드", example = "대파")
+        @RequestParam("keyword") keyword: String
+    ): ResponseEntity<List<ProductSearchResponse>> {
         val searchResult = productService.searchProducts(keyword)
         return ResponseEntity.ok(searchResult)
     }

추가(파일 외) 코드 스니펫

  • 길이 제한과 검증 활성화를 적용하려면 아래를 참고하세요. 필요 시 선택적으로 도입하세요.
// import 제안
import jakarta.validation.constraints.Size
import org.springframework.validation.annotation.Validated

// 클래스 어노테이션
@Validated
class ProductController(...) {

    fun searchProducts(
        @Parameter(description = "검색 키워드", example = "대파")
        @RequestParam("keyword") @Size(max = 50) keyword: String
    ): ResponseEntity<List<ProductSearchResponse>> { ... }
}

문서화(선택)

  • 200 응답에 Content/Schema를 명시하면 스키마가 Swagger UI에 드러납니다.
@ApiResponse(
  responseCode = "200",
  description = "상품 검색 성공...",
  content = [Content(
      mediaType = "application/json",
      schema = Schema(implementation = ProductSearchResponse::class)
  )]
)

Also applies to: 20-20, 112-147

src/main/kotlin/com/eodigo/domain/product/dto/ProductSearchResponse.kt (1)

6-10: 클래스 레벨 스키마와 도메인 결합 완화(선택)

  • Swagger 문서 가독성을 위해 클래스 레벨 @Schema(name/description) 추가를 제안합니다.
  • DTO에 도메인 엔티티를 import하여 팩토리에서 직접 참조하는 방식은 간단하지만 결합도를 높입니다. 매퍼(예: MapStruct)나 Product.toSearchResponse() 확장 함수로 분리하면 테스트와 재사용성이 좋아집니다.

예시:

@Schema(name = "ProductSearchResponse", description = "상품 검색 결과 DTO")
data class ProductSearchResponse(...)

// 또는 확장 함수
fun Product.toSearchResponse() = ProductSearchResponse(
    productId = requireNotNull(id) { "ProductSearchResponse: product.id가 null입니다." },
    name = name,
    itemName = itemName
)

원하시면 간단한 매퍼/확장 함수와 테스트 템플릿까지 함께 PR에 추가해 드릴게요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2b68a68 and 1d1d6a5.

⛔ Files ignored due to path filters (3)
  • src/test/kotlin/com/eodigo/domain/product/controller/ProductControllerTest.kt is excluded by !src/test/**
  • src/test/kotlin/com/eodigo/domain/product/repository/ProductRepositoryTest.kt is excluded by !src/test/**
  • src/test/kotlin/com/eodigo/domain/product/service/ProductServiceTest.kt is excluded by !src/test/**
📒 Files selected for processing (5)
  • build.gradle.kts (1 hunks)
  • src/main/kotlin/com/eodigo/domain/product/controller/ProductController.kt (3 hunks)
  • src/main/kotlin/com/eodigo/domain/product/dto/ProductSearchResponse.kt (1 hunks)
  • src/main/kotlin/com/eodigo/domain/product/repository/ProductRepository.kt (1 hunks)
  • src/main/kotlin/com/eodigo/domain/product/service/ProductService.kt (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.kt

⚙️ CodeRabbit configuration file

src/**/*.kt: ## 1. Kotlin 코딩 컨벤션

  • Kotlin 공식 코딩 컨벤션을 준수해야 합니다.
  • 클래스는 PascalCase, 함수/변수는 camelCase, 상수는 UPPER_SNAKE_CASE로 작성하세요.
  • 변경 불가능한 데이터는 val을, 변경이 필요한 경우에만 var를 사용하세요.
  • Null Pointer Exception을 유발하는 !! 연산자 사용을 금지합니다.
  • 의미를 알 수 없는 숫자나 문자열(매직 넘버) 대신 명명된 상수를 사용하세요.

2. Spring Boot

  • Controller-Service-Repository 계층형 아키텍처의 책임을 분리하세요.
  • Controller의 요청/응답에는 반드시 DTO를 사용하고, 엔티티를 직접 노출하지 마세요.
  • 민감한 설정 정보(DB 계정, API 키 등)는 코드에 하드코딩하지 말고 외부에서 주입하세요.
  • @Autowired 필드 주입 대신 생성자 주입을 사용하세요.

3. JPA 및 데이터베이스

  • @entity 클래스에 Setter 사용을 지양하고, 비즈니스 메서드를 통해 상태를 변경하세요.
  • 데이터를 변경하지 않는 조회용 Service 메서드에는 @transactional(readOnly = true)를 적용하세요.
  • N+1 문제가 발생하는 쿼리가 있는지 확인하고, Fetch Join으로 최적화하세요.
  • 엔티티의 양방향 연관관계에서 toString()을 사용할 경우 순환 참조가 발생하지 않도록 주의하세요.

4. 예외 처리

  • @RestControllerAdvice를 사용하여 예외를 전역에서 일관되게 처리하세요.
  • RuntimeException 대신, 의미가 명확한 커스텀 예외(예: MemberNotFoundException)를 정의하여 사용하세요.

Files:

  • src/main/kotlin/com/eodigo/domain/product/service/ProductService.kt
  • src/main/kotlin/com/eodigo/domain/product/controller/ProductController.kt
  • src/main/kotlin/com/eodigo/domain/product/dto/ProductSearchResponse.kt
  • src/main/kotlin/com/eodigo/domain/product/repository/ProductRepository.kt
⏰ 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-and-test
🔇 Additional comments (3)
build.gradle.kts (1)

64-66: 테스트 코드 및 Mockito 사용 여부 확인 요청
프로젝트 내에서 테스트 디렉토리(src/test/kotlin 등)와 테스트 클래스가 확인되지 않아, Mockito 사용 패턴 및 최종(final) 클래스/데이터 클래스 mocking 필요 여부를 자동으로 검증할 수 없었습니다.

  • 테스트 코드가 존재하는 경로(예: src/test/kotlin, src/test/java)를 확인해 주세요.
  • Mockito Kotlin 확장(org.mockito.kotlin:mockito-kotlin:5.4.0)을 실제로 사용 중인지, 그리고 최종 클래스/데이터 클래스 mocking이 필요한지 직접 검토 부탁드립니다.
  • 또한, Spring Boot BOM이 관리하지 않는 아티팩트이므로 mockito-kotlin:5.4.0 버전이 팀 표준 버전 정책에 부합하는지도 함께 확인해 주세요.
src/main/kotlin/com/eodigo/domain/product/service/ProductService.kt (1)

132-140: 테스트 검증 필요: searchProducts의 trim(), 대소문자 무시, 정렬 로직

현재 코드베이스에서

  • searchProducts 호출부 및 컨트롤러는 확인되나,
  • 입력값 trim() 처리 검증 로직,
  • 대소문자 무시(IgnoreCase) 동작 검증,
  • 결과 정렬 검증을 위한 단위/통합 테스트가 모두 부재합니다.

위 기능이 의도한 대로 H2와 MySQL 환경에서 일관되게 동작하는지 확인할 수 있도록, 관련 테스트를 추가하거나 기존 테스트에서 검증 여부를 직접 확인해 주세요.

src/main/kotlin/com/eodigo/domain/product/dto/ProductSearchResponse.kt (1)

12-19: from() 팩토리의 null 방어가 적절합니다

영속 엔티티에서 id null 불가 조건을 강제하여 하류 레이어 오류를 조기에 드러냅니다. 현재 구현은 명확하고 안전합니다.

@23tae 23tae merged commit 138bad2 into develop Aug 26, 2025
2 checks passed
@23tae 23tae deleted the feature/DND1302002-61 branch August 26, 2025 17:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant