Skip to content

[feat] Elastic Search를 사용한 지도 검색 기능 구현#28

Merged
KiSeungMin merged 35 commits into
developfrom
feature/seungmin
Jul 11, 2025
Merged

[feat] Elastic Search를 사용한 지도 검색 기능 구현#28
KiSeungMin merged 35 commits into
developfrom
feature/seungmin

Conversation

@KiSeungMin

@KiSeungMin KiSeungMin commented Jul 10, 2025

Copy link
Copy Markdown
Member

✔️ 연관 이슈

📝 작업 내용

  • Elastic Search를 사용한 지도 검색 기능을 구현했습니다.
    • queryparameter로 위도, 경도, 검색 반경거리(km), 가게 이름, 가게 종류(카페, 음식점 등) 을 받을 수 있습니다.
    • 전달받은 위도, 경도를 바탕으로 결과를 거리 순으로 반환합니다.
    • Slice를 사용해 무한 스크롤이 가능하도록 구현했습니다.
  • 기능 관련 테스트 코드를 추가했습니다.

참고 사항

  • elastic search가 설치되어 있지 않아 서버 실행이 안 될 겁니다. 다음 내용을 참고해주세요!!
  • application.yml 파일에 다음 내용 추가 (노션 설정 정보 페이지 참고)
spring:
  elasticsearch:
    uris: localhost:9200
  • 프로젝트 루트 폴더에서 다음 두 개 명령어 차례대로 실행
    • 혹시 Docker가 설치되어 있지 않다면 설치 부탁드립니다 ㅠㅠ
    • (docker-els.yml 파일에 주석으로 써놨으니 그거 복붙해서 하셔도 돼요)
docker compose -f docker-els.yml build
docker compose -f docker-els.yml up -d 

스크린샷 (선택)

  • 응답 결과
image

Summary by CodeRabbit

  • 신규 기능

    • Elasticsearch 기반의 Wayble Zone 검색 API가 추가되었습니다. 위치, 반경, 이름, 타입 등 다양한 조건으로 검색이 가능합니다.
    • Wayble Zone 문서를 등록하는 API가 추가되었습니다.
    • 검색 결과는 페이지네이션 및 거리순 정렬을 지원합니다.
  • 개선 및 변경

    • Wayble Zone 관련 DTO, Elasticsearch 문서 및 매핑/설정 파일이 추가되어 한글 검색 및 위치 기반 검색이 최적화되었습니다.
    • Address 엔티티에 빌더, 생성자, toString 등 Lombok 어노테이션이 추가되어 사용성이 향상되었습니다.
  • 버그 수정

    • 검색 시 존재하지 않는 문서에 대한 에러 메시지가 추가되었습니다.
  • 문서화

    • Elasticsearch 한글 분석기 및 매핑 설정 JSON 파일이 추가되었습니다.
  • 테스트

    • Wayble Zone 검색 API의 통합 테스트가 추가되어 다양한 검색 시나리오에 대한 검증이 이루어집니다.
  • 환경설정

    • Elasticsearch 및 관련 플러그인 설치를 위한 Dockerfile, Docker Compose 파일이 추가되었습니다.
    • 빌드 및 의존성 설정이 업데이트되었습니다.

@KiSeungMin KiSeungMin self-assigned this Jul 10, 2025
@KiSeungMin KiSeungMin added the 💡 feature 기능 구현 및 개발 label Jul 10, 2025
@coderabbitai

coderabbitai Bot commented Jul 10, 2025

Copy link
Copy Markdown

Walkthrough

Elasticsearch 기반 웨이블존 검색 기능이 새롭게 도입되었습니다. 검색 API, DTO, Elasticsearch 문서 및 매핑, 설정 파일, 테스트, 도커 환경, 의존성 추가 등 전체적인 검색 인프라와 API가 구현되었습니다. 거리, 이름, 카테고리별 검색과 페이징, 통합 테스트가 포함되어 있습니다.

Changes

파일/경로 요약 변경 내용 요약
.gitignore src/main/generated 디렉토리 Git 추적 허용 패턴 추가
Dockerfile.elasticsearch, docker-els.yml Elasticsearch 9.0.2 및 analysis-nori 플러그인 설치, 도커 컴포즈 환경 및 네트워크/볼륨 설정 추가
build.gradle Elasticsearch, Validation, Lombok 등 의존성 추가 및 테스트 의존성 보강
src/main/java/com/wayble/server/common/config/ElasticsearchConfig.java Elasticsearch 연결 설정 및 저장소 활성화 구성 클래스 추가
src/main/java/com/wayble/server/common/entity/Address.java Lombok 생성자, 빌더, toString 등 추가
src/main/java/com/wayble/server/search/controller/SearchController.java 검색 및 문서 등록 API 엔드포인트 구현, 기존 예제 엔드포인트 제거
src/main/java/com/wayble/server/search/dto/* 검색 조건, 응답, 등록 DTO 및 SearchSliceDto 레코드 추가
src/main/java/com/wayble/server/search/entity/EsAddress.java, WaybleZoneDocument.java Elasticsearch용 주소 및 웨이블존 문서 엔티티, 팩토리 메서드 구현
src/main/java/com/wayble/server/search/exception/SearchErrorCase.java NO_SUCH_DOCUMENT 에러 케이스 추가
src/main/java/com/wayble/server/search/repository/* WaybleZoneSearchRepository, Custom, Impl 등 저장소 및 커스텀 쿼리 구현
src/main/java/com/wayble/server/search/service/SearchService.java 검색, 저장, 조회 등 서비스 계층 기능 추가
src/main/resources/elasticsearch/settings/wayble_zone_mappings.json, wayble_zone_settings.json 한글 edge-ngram 분석기, geo_point 등 인덱스 매핑/설정 파일 추가
src/test/java/com/wayble/server/search/WaybleZoneSearchApiIntegrationTest.java 웨이블존 검색 API 통합 테스트(거리, 이름, 카테고리, 조합 등) 및 테스트 데이터 생성

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant SearchController
    participant SearchService
    participant WaybleZoneSearchRepository
    participant Elasticsearch

    Client->>SearchController: GET /search (조건, page, size)
    SearchController->>SearchService: searchWaybleZonesByCondition(조건, 페이징)
    SearchService->>WaybleZoneSearchRepository: searchWaybleZonesByCondition(조건, 페이징)
    WaybleZoneSearchRepository->>Elasticsearch: 쿼리 실행(geo, zoneName, zoneType 등)
    Elasticsearch-->>WaybleZoneSearchRepository: 검색 결과 반환
    WaybleZoneSearchRepository-->>SearchService: Slice<WaybleZoneSearchResponseDto>
    SearchService-->>SearchController: Slice<WaybleZoneSearchResponseDto>
    SearchController-->>Client: CommonResponse<SearchSliceDto<WaybleZoneSearchResponseDto>>
Loading
sequenceDiagram
    participant Client
    participant SearchController
    participant SearchService
    participant WaybleZoneSearchRepository

    Client->>SearchController: POST /search (등록 DTO)
    SearchController->>SearchService: saveDocumentFromDto(DTO)
    SearchService->>WaybleZoneSearchRepository: save(WaybleZoneDocument)
    WaybleZoneSearchRepository-->>SearchService: 저장 완료
    SearchService-->>SearchController: 성공 메시지
    SearchController-->>Client: CommonResponse<String>
Loading

Assessment against linked issues

Objective Addressed Explanation
거리 순 조회 (#23)
특정 단어가 포함된 웨이블존 조회 가능 (#23)
특정 카테고리(카페, 편의점 등)의 웨이블존 조회 가능 (#23)

Suggested reviewers

  • hyoinYang
  • zyovn
  • wonjun-lee-fcwj245

Poem

🐇
웨이블존을 찾아라, 거리를 재며
한글도 척척, nori로 풀며
카테고리, 이름도 한 번에 쏙!
토끼는 신나서 귀를 쫑긋
Elasticsearch와 함께 춤을 추네
검색의 숲에서 오늘도 점프!
🥕✨

✨ Finishing Touches
  • 📝 Generate Docstrings

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 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.

@KiSeungMin KiSeungMin added the 🤔 test 테스트 관련 및 테스트 코드 작성 label Jul 10, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (18)
.gitignore (1)

7-7: src/test/generated/ 예외도 함께 추가하는 것을 권장합니다
QueryDSL 등의 코드 생성이 테스트 소스 세트에도 적용된다면, src/test/generated/ 디렉터리는 여전히 무시 대상입니다. 운영-코드와 동일한 규칙을 유지하려면 아래 예외를 추가해 주세요.

+!**/src/test/generated/
src/main/java/com/wayble/server/search/repository/WaybleZoneSearchRepositoryCustom.java (1)

8-10: 인터페이스에 대한 JavaDoc 및 예외 명세 추가 필요

공개 API 성격의 메서드이므로 파라미터/반환값 설명, 잠재적 예외 등을 JavaDoc으로 명확히 남겨두면 유지보수성이 높아집니다.
특히 Slice 기반 페이지네이션에서 pageable.isUnpaged() 가 전달될 경우 처리 전략(예: IAE 발생 여부)을 명시해 두는 편이 좋습니다.

src/main/java/com/wayble/server/search/repository/WaybleZoneSearchRepository.java (1)

5-6: 불필요한 import 삭제

QuerydslPredicateExecutor 를 import 했지만 extends 목록에 포함되지 않아 컴파일러 경고가 발생합니다.

-import org.springframework.data.querydsl.QuerydslPredicateExecutor;
src/main/java/com/wayble/server/common/config/ElasticsearchConfig.java (1)

13-14: 여러 호스트 지원 및 환경 분리 고려

spring.elasticsearch.uris 에 쉼표로 구분된 다중 노드를 넣을 경우 현재 구현도 동작하지만,
프로퍼티명을 spring.elasticsearch.urisspring.elasticsearch.uris[ ] 로 변경하거나 List<String> 으로 바꾸면 명확성이 올라갑니다.

src/main/java/com/wayble/server/common/entity/Address.java (2)

7-12: equals/hashCode 누락

엔티티 값 객체로 사용될 가능성이 높은 Address 가 equals/hashCode 를 구현하지 않으면
컬렉션 key 로 사용하거나 변경 감지 시 문제를 유발할 수 있습니다. Lombok @EqualsAndHashCode 추가를 권장합니다.

-@ToString
+@ToString
+@EqualsAndHashCode

34-40: 위도·경도 자료형 검토

Double 은 부동소수 표현 오차가 있어 정밀 좌표 계산 시 문제가 될 수 있습니다.
BigDecimal 또는 Elasticsearch geo_point 와 1:1 매핑되는 GeoPoint 클래스를 고려해 보세요.

src/main/resources/elasticsearch/settings/wayble_zone_settings.json (1)

10-13: Nori tokenizer 설정 검토가 필요합니다

decompound_mode"mixed"로 설정되어 있습니다. 이는 복합어를 분해하여 검색 성능을 향상시키지만, 검색 결과의 정확성에 영향을 줄 수 있습니다. 실제 사용 사례에 따라 "discard" 또는 "none" 모드도 고려해보세요.

src/main/java/com/wayble/server/search/service/SearchService.java (1)

33-39: 중복 코드 제거를 고려하세요

saveDocumentFromEntitysaveDocumentFromDto 메서드가 유사한 패턴을 가지고 있습니다. 공통 로직을 추출하여 중복을 줄일 수 있습니다.

+ private void saveWaybleZoneDocument(WaybleZoneDocument document) {
+     waybleZoneSearchRepository.save(document);
+ }
+
  public void saveDocumentFromEntity(WaybleZone waybleZone) {
-     waybleZoneSearchRepository.save(WaybleZoneDocument.fromEntity(waybleZone));
+     saveWaybleZoneDocument(WaybleZoneDocument.fromEntity(waybleZone));
  }

  public void saveDocumentFromDto(WaybleZoneDocumentRegisterDto dto) {
-     waybleZoneSearchRepository.save(WaybleZoneDocument.fromDto(dto));
+     saveWaybleZoneDocument(WaybleZoneDocument.fromDto(dto));
  }
src/main/java/com/wayble/server/search/entity/WaybleZoneDocument.java (1)

44-44: TODO 주석을 해결하세요

하드코딩된 "thumbnail image url" 문자열이 있습니다. 실제 이미지 경로 로직을 구현하거나 기본값 처리 방안을 마련해야 합니다.

실제 썸네일 이미지 URL 생성 로직을 구현할 수 있도록 도와드릴까요? 또는 이 작업을 추적하기 위한 이슈를 생성하시겠습니까?

docker-els.yml (1)

12-12: 힙 메모리 크기가 작을 수 있습니다

ES_JAVA_OPTS=-Xms256m -Xmx256m 설정은 개발 환경에는 적절하지만, 실제 데이터가 많아지면 메모리 부족 문제가 발생할 수 있습니다. 운영 환경에서는 더 큰 메모리 할당을 고려하세요.

 environment:
   - discovery.type=single-node
   - xpack.security.enabled=false
-  - ES_JAVA_OPTS=-Xms256m -Xmx256m
+  - ES_JAVA_OPTS=-Xms512m -Xmx512m
src/main/java/com/wayble/server/search/dto/WaybleZoneSearchConditionDto.java (2)

22-23: 반경 검증 조건을 개선해주세요.

현재 @DecimalMin(value = "0.1", message = "검색 반경은 100미터 이상이어야 합니다.") 검증은 최소값만 설정하고 있습니다. 최대값 제한도 고려해보세요.

다음과 같이 최대값 제한을 추가하는 것을 고려해보세요:

 @DecimalMin(value = "0.1", message = "검색 반경은 100미터 이상이어야 합니다.")
+@DecimalMax(value = "1000.0", message = "검색 반경은 1000km 이하여야 합니다.")
 Double radiusKm,

25-26: zoneName 필드가 선택적이지만 null 체크가 없습니다.

현재 @Size(min = 2)는 null 값에 대해서는 검증을 수행하지 않지만, 빈 문자열에 대해서는 검증을 수행합니다. 명확한 의도를 위해 추가 검증을 고려해보세요.

 @Size(min = 2, message = "zoneName은 최소 2글자 이상이어야 합니다.")
+@Pattern(regexp = "^$|^.{2,}$", message = "zoneName은 비어있거나 최소 2글자 이상이어야 합니다.")
 String zoneName,

또는 빈 문자열 처리를 위해:

 @Size(min = 2, message = "zoneName은 최소 2글자 이상이어야 합니다.")
+@NotBlank(message = "zoneName이 제공되면 공백이 아니어야 합니다.")
 String zoneName,
src/main/java/com/wayble/server/search/repository/WaybleZoneSearchRepositoryImpl.java (2)

36-37: 기본 반경 값이 매직 넘버로 설정되어 있습니다.

기본 반경 값 100.0이 하드코딩되어 있어 의미가 불분명합니다.

+private static final double DEFAULT_RADIUS_KM = 100.0;
+
-double radius = cond.radiusKm() != null ? cond.radiusKm() : 100.0;
+double radius = cond.radiusKm() != null ? cond.radiusKm() : DEFAULT_RADIUS_KM;

46-47: Elasticsearch 필드 이름이 하드코딩되어 있습니다.

필드 이름들이 하드코딩되어 있어 엔티티 변경 시 동기화 문제가 발생할 수 있습니다.

상수로 분리하는 것을 고려해보세요:

+private static final String ZONE_TYPE_FIELD = "zoneType.keyword";
+private static final String ZONE_NAME_FIELD = "zoneName";
+private static final String LOCATION_FIELD = "address.location";
+
-.field("zoneType.keyword")
+.field(ZONE_TYPE_FIELD)
-.field("zoneName")
+.field(ZONE_NAME_FIELD)
-.field("address.location")
+.field(LOCATION_FIELD)
src/main/java/com/wayble/server/search/controller/SearchController.java (1)

39-43: Spring Security 의존성 여부 수동 확인 필요
프로젝트 최상위에 있는 빌드 파일(pom.xml 또는 build.gradle 등)에서 spring-boot-starter-security 의존성이 실제로 포함되어 있는지 검토해주세요.

src/test/java/com/wayble/server/search/WaybleZoneSearchApiIntegrationTest.java (3)

47-58: 테스트 데이터에 하드코딩된 값들이 있습니다.

테스트에서 사용하는 상점 이름들이 하드코딩되어 있어 유지보수성이 떨어집니다.

-List<String> nameList = new ArrayList<>(Arrays.asList(
-        "던킨도너츠",
-        "베스킨라빈스",
-        // ... 나머지 이름들
-));
+private static final List<String> STORE_NAMES = List.of(
+        "던킨도너츠",
+        "베스킨라빈스",
+        "투썸플레이스",
+        "스타벅스",
+        "메가엠지씨커피",
+        "공차",
+        "롯데리아",
+        "맥도날드",
+        "KFC",
+        "노브랜드버거"
+);

138-142: 거리 계산 정확도 검증에서 허용 오차가 고정되어 있습니다.

0.05km의 고정된 허용 오차가 모든 상황에 적절하지 않을 수 있습니다.

거리에 비례한 허용 오차를 사용하는 것을 고려해보세요:

-// 허용 오차: 0.05 km (≈50m)
-assertThat(dto.distance())
-        .withFailMessage("zoneId=%d: expected=%.5f, actual=%.5f",
-                dto.zoneId(), expected, dto.distance())
-        .isCloseTo(expected, offset(0.05));
+// 거리에 비례한 허용 오차 (최소 0.01km, 최대 0.1km)
+double tolerance = Math.max(0.01, Math.min(0.1, expected * 0.01));
+assertThat(dto.distance())
+        .withFailMessage("zoneId=%d: expected=%.5f, actual=%.5f, tolerance=%.5f",
+                dto.zoneId(), expected, dto.distance(), tolerance)
+        .isCloseTo(expected, offset(tolerance));

97-98: 테스트에서 System.out.println을 사용하고 있습니다.

테스트 코드에서 직접 콘솔 출력을 사용하는 것은 좋지 않은 관례입니다.

로거를 사용하거나 테스트 전용 출력 방식을 사용해보세요:

+private static final Logger log = LoggerFactory.getLogger(WaybleZoneSearchApiIntegrationTest.class);
+
-System.out.println("=== 저장된 데이터 확인 ===");
-System.out.println("Total documents: " + all.size());
+log.info("=== 저장된 데이터 확인 ===");
+log.info("Total documents: {}", all.size());

또는 테스트에서는 출력을 완전히 제거하는 것을 고려해보세요.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5a464cf and cbba2cf.

📒 Files selected for processing (21)
  • .gitignore (1 hunks)
  • Dockerfile.elasticsearch (1 hunks)
  • build.gradle (2 hunks)
  • docker-els.yml (1 hunks)
  • src/main/java/com/wayble/server/common/config/ElasticsearchConfig.java (1 hunks)
  • src/main/java/com/wayble/server/common/entity/Address.java (1 hunks)
  • src/main/java/com/wayble/server/search/controller/SearchController.java (1 hunks)
  • src/main/java/com/wayble/server/search/dto/SearchSliceDto.java (1 hunks)
  • src/main/java/com/wayble/server/search/dto/WaybleZoneDocumentRegisterDto.java (1 hunks)
  • src/main/java/com/wayble/server/search/dto/WaybleZoneSearchConditionDto.java (1 hunks)
  • src/main/java/com/wayble/server/search/dto/WaybleZoneSearchResponseDto.java (1 hunks)
  • src/main/java/com/wayble/server/search/entity/EsAddress.java (1 hunks)
  • src/main/java/com/wayble/server/search/entity/WaybleZoneDocument.java (1 hunks)
  • src/main/java/com/wayble/server/search/exception/SearchErrorCase.java (1 hunks)
  • src/main/java/com/wayble/server/search/repository/WaybleZoneSearchRepository.java (1 hunks)
  • src/main/java/com/wayble/server/search/repository/WaybleZoneSearchRepositoryCustom.java (1 hunks)
  • src/main/java/com/wayble/server/search/repository/WaybleZoneSearchRepositoryImpl.java (1 hunks)
  • src/main/java/com/wayble/server/search/service/SearchService.java (1 hunks)
  • src/main/resources/elasticsearch/settings/wayble_zone_mappings.json (1 hunks)
  • src/main/resources/elasticsearch/settings/wayble_zone_settings.json (1 hunks)
  • src/test/java/com/wayble/server/search/WaybleZoneSearchApiIntegrationTest.java (1 hunks)
🔇 Additional comments (13)
Dockerfile.elasticsearch (1)

1-4: Elasticsearch 9.0.2 최신 버전 및 보안 상태 확인 완료

확인 결과, 2025년 6월 3일 출시된 9.0.2가 현재 최신 버전이며 별도의 보안 취약점 보고는 없습니다. nori 플러그인 설치 설정도 적절하므로 해당 Dockerfile 변경사항을 승인합니다.

build.gradle (2)

32-33: Elasticsearch 및 Validation 의존성 추가 승인

새로운 검색 기능 구현을 위한 적절한 의존성들이 추가되었습니다. Spring Boot 3.2.5와 호환되는 의존성들입니다.


44-44: 테스트 코드 Lombok 지원 승인

테스트 코드에서 Lombok 사용을 위한 의존성이 적절히 추가되었습니다.

src/main/java/com/wayble/server/search/exception/SearchErrorCase.java (1)

11-12: 에러 케이스 추가 승인

새로운 NO_SUCH_DOCUMENT 에러 케이스가 적절히 추가되었습니다. 에러 코드 순서가 논리적이고 한국어 메시지가 명확합니다.

src/main/java/com/wayble/server/search/dto/SearchSliceDto.java (1)

1-9: 페이지네이션 DTO 구현 승인

제네릭 레코드 클래스를 사용한 깔끔한 페이지네이션 DTO 구현입니다. 재사용성과 불변성을 보장하는 좋은 설계입니다.

src/main/resources/elasticsearch/settings/wayble_zone_mappings.json (1)

1-12: Elasticsearch 매핑 및 분석기 정의 검증 완료

  • wayble_zone_mappings.jsonzoneName(text + 한국어 분석기) 및 address.location(geo_point) 설정이 적절합니다.
  • src/main/resources/elasticsearch/settings/wayble_zone_settings.json에서 korean_edge_ngram_analyzerkorean_search_analyzer가 정상적으로 정의된 것을 확인했습니다.

따라서 추가 조치 없이 매핑을 승인합니다.

src/main/java/com/wayble/server/search/dto/WaybleZoneDocumentRegisterDto.java (1)

7-16: 레코드 + @builder 조합 시 컴파일 환경 확인

Lombok 1.18.22 이상에서만 record builder 가 정식 지원됩니다. 하위 버전, IDE 플러그인 충돌 시 생성 코드가 누락될 수 있으니 CI 빌드 로그를 확인해 주세요.
또한 averageRating, reviewCount 가 null 가능성이 보이므로 API 계약서에 필수/옵션 여부를 명시해 두면 좋습니다.

src/main/resources/elasticsearch/settings/wayble_zone_settings.json (1)

4-8: Edge n-gram 설정이 적절합니다

2-20 글자 범위의 edge n-gram 설정이 한국어 검색에 적합하며, token_charsletterdigit를 포함한 것이 좋습니다.

src/main/java/com/wayble/server/search/service/SearchService.java (1)

28-31: ID 기반 조회 메서드가 적절합니다

NO_SUCH_DOCUMENT 예외 처리와 함께 명확한 에러 메시지를 제공하는 좋은 구현입니다.

src/main/java/com/wayble/server/search/entity/WaybleZoneDocument.java (2)

51-61: DTO 변환 시 null 안전성이 잘 구현되었습니다

averageRatingreviewCount에 대한 null 체크와 기본값 설정이 적절합니다. 방어적 프로그래밍의 좋은 예시입니다.


23-26: 분석기 정의 확인 완료

src/main/resources/elasticsearch/settings/wayble_zone_settings.json 파일 내 23행의 korean_edge_ngram_analyzer와 28행의 korean_search_analyzer가 모두 정의되어 있어 인덱싱 오류가 발생하지 않습니다.

docker-els.yml (1)

29-30: 실행 명령어가 유용합니다

Docker Compose 실행 명령어를 주석으로 제공한 것이 좋습니다. 개발자들이 쉽게 환경을 설정할 수 있도록 도움이 됩니다.

src/main/java/com/wayble/server/search/dto/WaybleZoneSearchResponseDto.java (1)

30-42: NPE 위험 없음 – 추가 null 검사가 필요하지 않습니다
WaybleZoneDocument.fromEntity/fromDto에서 항상 EsAddress와 그 안의 GeoPoint(location)를 생성하고 있으며, 통합 테스트(WaybleZoneSearchApiIntegrationTest)에서 doc.getAddress().getLocation()이 null 아님을 검증하고 있습니다. 따라서 from 메서드에 별도의 null 체크를 추가할 필요는 없습니다.

Comment on lines +10 to +13
public interface WaybleZoneSearchRepository extends ElasticsearchRepository<WaybleZoneDocument, Long>, WaybleZoneSearchRepositoryCustom{
Optional<WaybleZoneDocument> findById(Long waybleZoneId);
List<WaybleZoneDocument> findAll();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

중복 메서드 정의 및 인터페이스 설계 재검토

  1. ElasticsearchRepository 가 이미 findById, findAll 을 제공하므로 동일 시그니처의 메서드를 다시 선언할 필요가 없습니다.
  2. 만약 커스텀 동작(예: soft-delete 필터 적용)을 의도했다면 메서드 이름을 변경하거나 Default method 로 구현해 주세요.
-    Optional<WaybleZoneDocument> findById(Long waybleZoneId);
-    List<WaybleZoneDocument> findAll();
📝 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
public interface WaybleZoneSearchRepository extends ElasticsearchRepository<WaybleZoneDocument, Long>, WaybleZoneSearchRepositoryCustom{
Optional<WaybleZoneDocument> findById(Long waybleZoneId);
List<WaybleZoneDocument> findAll();
}
public interface WaybleZoneSearchRepository
extends ElasticsearchRepository<WaybleZoneDocument, Long>,
WaybleZoneSearchRepositoryCustom {
}
🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/search/repository/WaybleZoneSearchRepository.java
lines 10 to 13, remove the redundant declarations of findById and findAll
methods since ElasticsearchRepository already provides them. If custom behavior
like soft-delete filtering is needed, implement those as default methods with
distinct names or provide custom implementations in the
WaybleZoneSearchRepositoryCustom interface instead of redeclaring these methods
with the same signatures.

Comment on lines +17 to +21
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder()
.connectedTo(host)
.build();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

타임아웃·헤더 등 클라이언트 세부 옵션 누락

연결/소켓 타임아웃, 압축 여부, 기본 인증 등을 지정하지 않아 운영 환경에서 연결 지연 시 애플리케이션이 장시간 블로킹될 수 있습니다.

 return ClientConfiguration.builder()
         .connectedTo(host)
+        .withConnectTimeout(Duration.ofSeconds(3))
+        .withSocketTimeout(Duration.ofSeconds(5))
         .build();
📝 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
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder()
.connectedTo(host)
.build();
}
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder()
.connectedTo(host)
.withConnectTimeout(Duration.ofSeconds(3))
.withSocketTimeout(Duration.ofSeconds(5))
.build();
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/common/config/ElasticsearchConfig.java around
lines 17 to 21, the clientConfiguration method lacks detailed client options
such as connection and socket timeouts, compression settings, and basic
authentication. To fix this, enhance the ClientConfiguration builder by
specifying appropriate connection and socket timeout values, enabling
compression if needed, and configuring basic authentication credentials to
prevent long blocking during connection delays in production.

Comment on lines +23 to +32
public static EsAddress from(Address address) {
return EsAddress.builder()
.state(address.getState())
.city(address.getCity())
.district(address.getDistrict())
.streetAddress(address.getStreetAddress())
.detailAddress(address.getDetailAddress())
.location(new GeoPoint(address.getLatitude(), address.getLongitude()))
.build();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

null 안전성 검증이 필요합니다

from 메서드에서 address.getLatitude()address.getLongitude()가 null일 경우 GeoPoint 생성 시 예외가 발생할 수 있습니다. 좌표 값에 대한 null 체크를 추가하는 것을 권장합니다.

 public static EsAddress from(Address address) {
+    if (address.getLatitude() == null || address.getLongitude() == null) {
+        throw new IllegalArgumentException("좌표 정보가 필요합니다");
+    }
     return EsAddress.builder()
             .state(address.getState())
             .city(address.getCity())
             .district(address.getDistrict())
             .streetAddress(address.getStreetAddress())
             .detailAddress(address.getDetailAddress())
             .location(new GeoPoint(address.getLatitude(), address.getLongitude()))
             .build();
 }
📝 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
public static EsAddress from(Address address) {
return EsAddress.builder()
.state(address.getState())
.city(address.getCity())
.district(address.getDistrict())
.streetAddress(address.getStreetAddress())
.detailAddress(address.getDetailAddress())
.location(new GeoPoint(address.getLatitude(), address.getLongitude()))
.build();
}
public static EsAddress from(Address address) {
if (address.getLatitude() == null || address.getLongitude() == null) {
throw new IllegalArgumentException("좌표 정보가 필요합니다");
}
return EsAddress.builder()
.state(address.getState())
.city(address.getCity())
.district(address.getDistrict())
.streetAddress(address.getStreetAddress())
.detailAddress(address.getDetailAddress())
.location(new GeoPoint(address.getLatitude(), address.getLongitude()))
.build();
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/search/entity/EsAddress.java around lines 23
to 32, the from method does not check if address.getLatitude() or
address.getLongitude() are null before creating a GeoPoint, which can cause
exceptions. Add null checks for latitude and longitude before constructing the
GeoPoint, and handle the case where either is null appropriately, such as by
setting location to null or using default values.

Comment on lines +41 to +46
public Slice<WaybleZoneSearchResponseDto> searchWaybleZonesByCondition(
WaybleZoneSearchConditionDto condition,
Pageable pageable)
{
return waybleZoneSearchRepository.searchWaybleZonesByCondition(condition, pageable);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

검색 조건 검증이 필요합니다

searchWaybleZonesByCondition 메서드에서 condition 파라미터에 대한 null 체크나 유효성 검증이 없습니다. 서비스 레이어에서 기본적인 검증을 추가하는 것을 권장합니다.

 public Slice<WaybleZoneSearchResponseDto> searchWaybleZonesByCondition(
         WaybleZoneSearchConditionDto condition,
         Pageable pageable)
 {
+    if (condition == null) {
+        throw new IllegalArgumentException("검색 조건이 필요합니다");
+    }
     return waybleZoneSearchRepository.searchWaybleZonesByCondition(condition, pageable);
 }
📝 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
public Slice<WaybleZoneSearchResponseDto> searchWaybleZonesByCondition(
WaybleZoneSearchConditionDto condition,
Pageable pageable)
{
return waybleZoneSearchRepository.searchWaybleZonesByCondition(condition, pageable);
}
public Slice<WaybleZoneSearchResponseDto> searchWaybleZonesByCondition(
WaybleZoneSearchConditionDto condition,
Pageable pageable)
{
if (condition == null) {
throw new IllegalArgumentException("검색 조건이 필요합니다");
}
return waybleZoneSearchRepository.searchWaybleZonesByCondition(condition, pageable);
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/search/service/SearchService.java around
lines 41 to 46, the method searchWaybleZonesByCondition lacks null checks and
validation for the condition parameter. Add a null check for the condition
argument at the start of the method and validate its fields as needed to ensure
it meets expected criteria before proceeding. If the condition is invalid or
null, throw an appropriate exception or handle the error gracefully to prevent
unexpected behavior.

Comment thread docker-els.yml
restart: unless-stopped
environment:
- discovery.type=single-node
- xpack.security.enabled=false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

보안 설정을 검토하세요

xpack.security.enabled=false로 설정되어 있습니다. 개발 환경에서는 적절하지만, 운영 환경에서는 보안을 활성화해야 합니다.

운영 환경용 별도의 Docker Compose 파일을 생성하여 보안 설정을 포함하는 것을 권장합니다.

🤖 Prompt for AI Agents
In docker-els.yml at line 11, the setting 'xpack.security.enabled=false'
disables security, which is acceptable for development but not for production.
To fix this, create a separate Docker Compose file for the production
environment that enables security by setting 'xpack.security.enabled=true'. This
approach keeps development and production configurations separate and ensures
security is active in production.

Comment on lines +111 to +113
Double distanceInMeters = (Double) hit.getSortValues().get(0);
Double distanceInKm = distanceInMeters / 1000.0;
return WaybleZoneSearchResponseDto.from(doc, distanceInKm);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

거리 계산에서 잠재적인 타입 안전성 문제가 있습니다.

hit.getSortValues().get(0)에서 타입 캐스팅이 안전하지 않을 수 있습니다.

-Double distanceInMeters = (Double) hit.getSortValues().get(0);
-Double distanceInKm = distanceInMeters / 1000.0;
+Object sortValue = hit.getSortValues().get(0);
+Double distanceInKm = null;
+if (sortValue instanceof Number) {
+    distanceInKm = ((Number) sortValue).doubleValue() / 1000.0;
+} else {
+    // 로깅 또는 예외 처리
+    distanceInKm = 0.0;
+}
📝 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
Double distanceInMeters = (Double) hit.getSortValues().get(0);
Double distanceInKm = distanceInMeters / 1000.0;
return WaybleZoneSearchResponseDto.from(doc, distanceInKm);
Object sortValue = hit.getSortValues().get(0);
Double distanceInKm = null;
if (sortValue instanceof Number) {
distanceInKm = ((Number) sortValue).doubleValue() / 1000.0;
} else {
// 로깅 또는 예외 처리
distanceInKm = 0.0;
}
return WaybleZoneSearchResponseDto.from(doc, distanceInKm);
🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/search/repository/WaybleZoneSearchRepositoryImpl.java
around lines 111 to 113, the cast of hit.getSortValues().get(0) to Double is
unsafe and may cause a ClassCastException. To fix this, first check the type of
the object returned by getSortValues().get(0) and safely convert or parse it to
Double, handling cases where it might be a different numeric type or null before
performing the division and passing it to the DTO.


private final ElasticsearchOperations operations;

private static final IndexCoordinates INDEX = IndexCoordinates.of("wayble_zone");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

인덱스 이름을 상수로 하드코딩하는 것을 개선해주세요.

인덱스 이름이 하드코딩되어 있어 환경별로 다른 인덱스를 사용하기 어렵습니다.

-private static final IndexCoordinates INDEX = IndexCoordinates.of("wayble_zone");
+@Value("${elasticsearch.index.wayble-zone:wayble_zone}")
+private String waybleZoneIndexName;
+
+private IndexCoordinates getIndexCoordinates() {
+    return IndexCoordinates.of(waybleZoneIndexName);
+}

그리고 사용하는 곳에서:

-operations.search(nativeQuery, WaybleZoneDocument.class, INDEX);
+operations.search(nativeQuery, WaybleZoneDocument.class, getIndexCoordinates());
📝 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
private static final IndexCoordinates INDEX = IndexCoordinates.of("wayble_zone");
// Remove the hard-coded constant:
// private static final IndexCoordinates INDEX = IndexCoordinates.of("wayble_zone");
// Add an injectable index name and helper:
@Value("${elasticsearch.index.wayble-zone:wayble_zone}")
private String waybleZoneIndexName;
private IndexCoordinates getIndexCoordinates() {
return IndexCoordinates.of(waybleZoneIndexName);
}
// …later, in your search method(s), replace INDEX with:
return operations.search(nativeQuery, WaybleZoneDocument.class, getIndexCoordinates());
🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/search/repository/WaybleZoneSearchRepositoryImpl.java
at line 29, the index name "wayble_zone" is hardcoded as a constant, which
reduces flexibility for different environments. To fix this, externalize the
index name by loading it from a configuration property or environment variable
instead of hardcoding it. Modify the code to read the index name dynamically and
assign it to the INDEX constant accordingly.

Comment on lines +28 to +29
@RequestParam(name = "page", defaultValue = "0") int page,
@RequestParam(name = "size", defaultValue = "20") int size)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

페이징 파라미터에 대한 검증이 부족합니다.

페이지 번호와 크기에 대한 범위 검증이 없어 잘못된 값이 전달될 수 있습니다.

-@RequestParam(name = "page", defaultValue = "0") int page,
-@RequestParam(name = "size", defaultValue = "20") int size)
+@RequestParam(name = "page", defaultValue = "0") @Min(0) int page,
+@RequestParam(name = "size", defaultValue = "20") @Min(1) @Max(100) int size)

필요한 import도 추가해야 합니다:

+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.Max;
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/search/controller/SearchController.java
around lines 28 to 29, add validation annotations to the paging parameters to
ensure page is zero or positive and size is positive within a reasonable limit.
Use annotations like @Min(0) for page and @Min(1) and possibly @Max for size.
Also, add the necessary imports for these validation annotations from
javax.validation.constraints.

Comment on lines +62 to +86
for (int i = 1; i <= 1000; i++) {
Map<String, Double> points = makeRandomPoint();
Address address = Address.builder()
.state("state" + i)
.city("city" + i)
.district("district" + i)
.streetAddress("street address" + i)
.detailAddress("detail address" + i)
.latitude(points.get("latitude"))
.longitude(points.get("longitude"))
.build();

WaybleZoneDocumentRegisterDto dto = WaybleZoneDocumentRegisterDto
.builder()
.zoneId((long) i)
.zoneName(nameList.get((int) (Math.random() * nameList.size())))
.address(address)
.waybleZoneType(WaybleZoneType.values()[i % WaybleZoneType.values().length])
.averageRating(Math.random() * 5)
.reviewCount((long)(Math.random() * 500))
.build();

WaybleZoneDocument waybleZoneDocument = WaybleZoneDocument.fromDto(dto);
waybleZoneSearchRepository.save(waybleZoneDocument);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

테스트 데이터 생성이 너무 많아 테스트 실행 시간이 길어질 수 있습니다.

1000개의 테스트 데이터를 매번 생성하는 것은 테스트 실행 시간을 크게 증가시킵니다.

테스트 데이터 수를 줄이거나 테스트별로 필요한 만큼만 생성하는 것을 고려해보세요:

-for (int i = 1; i <= 1000; i++) {
+private static final int TEST_DATA_SIZE = 100; // 1000 -> 100으로 감소
+
+for (int i = 1; i <= TEST_DATA_SIZE; i++) {

또는 테스트 프로파일별로 다른 크기를 사용:

+@Value("${test.data.size:100}")
+private int testDataSize;
+
-for (int i = 1; i <= 1000; i++) {
+for (int i = 1; i <= testDataSize; i++) {

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/test/java/com/wayble/server/search/WaybleZoneSearchApiIntegrationTest.java
between lines 62 and 86, the test creates 1000 data entries which significantly
increases test execution time. To fix this, reduce the number of test data
generated to a smaller, more manageable amount or generate only the necessary
data per test case. Alternatively, implement different data sizes based on test
profiles to optimize performance.

@seung-in-Yoo seung-in-Yoo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Wow 역시 최고네요 ㅎㅎ 저도 개인적으로 Elastic Search에 대해서 더 자세히 알아봐야겠어요. 구현이 어려웠을것 같은데 너무 수고 많으셨습니다~!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💡 feature 기능 구현 및 개발 🤔 test 테스트 관련 및 테스트 코드 작성

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants