Skip to content

Conversation

@hyoinYang
Copy link
Contributor

@hyoinYang hyoinYang commented Aug 21, 2025

✔️ 연관 이슈

📝 작업 내용

진행상황을 main으로 머지합니다.

스크린샷 (선택)

Summary by CodeRabbit

  • New Features

    • 대중교통 경로 결과에 버스·지하철 노선/정류장 메타데이터 활용 범위 확대로 안내 품질 향상
    • 정류장 검색 시 고유 식별자(ARS ID) 반영으로 동일명 정류장 구분 정확도 개선
    • 노선 ID로 휠체어 위치 정보를 직접 조회하여 접근성 정보 제공 강화
  • Refactor

    • 경로 계산 내부 구조 개선으로 메모리 효율 및 안정성 향상
    • 필요한 필드만 조회하는 방식으로 대용량 지도 구간(노드·엣지) 로딩 성능 개선
    • 외부 데이터 연동 클라이언트 구성 정비로 오류 대응 및 응답 처리 신뢰성 강화

@hyoinYang hyoinYang self-assigned this Aug 21, 2025
@hyoinYang hyoinYang added the release 프로덕션 서버에 배포 label Aug 21, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 21, 2025

Walkthrough

내부 라우팅을 InternalStep/NodeRef로 분리하고, 노드/엣지 조회를 프로젝션으로 최적화했다. OpenData 전용 WebClient를 추가했고, BusInfoService는 WebClient 기반 호출·선택 로직으로 개편되며 더미 경로를 우선 반환한다. Facility/Route/Wheelchair 리포지토리를 보완하고, Edge/Route에 생성 팩토리를 추가했다.

Changes

Cohort / File(s) Change Summary
OpenData WebClient 및 설정/DTO
src/main/java/com/wayble/server/common/config/WebClientConfig.java, .../external/opendata/OpenDataProperties.java, .../external/opendata/dto/OpenDataResponse.java, .../external/opendata/dto/StationSearchResponse.java
OpenData 전용 WebClient 빈 추가(기본 URL/코덱 설정). OpenDataProperties에서 key/userAgent 제거. OpenData 응답 DTO에 rtNm/stNm/arsId 필드 추가. 정류장 검색 응답에 arsId 추가.
내부 DTO 도입
.../direction/dto/InternalStep.java, .../direction/dto/NodeRef.java
내부 라우팅용 불변 레코드 추가(InternalStep, NodeRef).
엔티티 팩토리 보강
.../entity/transportation/Edge.java, .../entity/transportation/Route.java
Route 포함 엣지 생성 팩토리 추가(createEdgeWithRoute). Route 생성 팩토리 추가(createRoute) 및 wheelchairs 초기화.
프로젝션 추가 및 리포지토리 최적화
.../repository/NodeBoundingBoxProjection.java, .../repository/EdgeBoundingBoxProjection.java, .../repository/NodeRepository.java, .../repository/EdgeRepository.java
노드/엣지 바운딩박스 조회를 프로젝션으로 전환 및 정렬/파라미터 보강.
리포지토리 보강
.../repository/RouteRepository.java, .../repository/WheelchairInfoRepository.java, .../repository/FacilityRepository.java
노선명 단건 조회 메서드 추가. 휠체어 위치 문자열 리스트 조회 추가. Facility 커스텀 @query 제거(메서드명 유도).
서비스 리팩터링
.../service/TransportationService.java, .../service/BusInfoService.java, .../service/FacilityService.java
라우팅 내부 표현을 InternalStep으로 전환하고 프로젝션 기반 그래프 구성/다익스트라/머지 로직 재작성. BusInfoService를 WebClient 기반으로 변경(더미 우선 반환, 정류장 선택/도착정보 필터링 추가). FacilityService는 휠체어 위치 문자열 사용 및 Optional 흐름 정리.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant API as TransportationController
  participant TS as TransportationService
  participant Repo as Node/Edge Repos (Projections)
  participant Build as Graph Builder
  participant D as Dijkstra
  participant Enrich as Enrichment (Bus/Facility/Subway)

  API->>TS: findRoutes(request)
  TS->>Repo: findNodesInBoundingBox(min/max)
  TS->>Repo: findEdgesInBoundingBox(min/max)
  TS->>Build: build graph (NodeRef/Edge w/Route)
  Build-->>TS: graph
  TS->>D: runDijkstra / multi-route
  D-->>TS: List<List<InternalStep>>
  TS->>Enrich: enrichRoutesWithServiceInfo(InternalStep)
  Enrich-->>TS: List<Route(Step)>
  TS-->>API: TransportationResponseDto
  note over TS,Enrich: 내부 표현(InternalStep) → 외부 DTO 변환
Loading
sequenceDiagram
  autonumber
  participant Svc as BusInfoService
  participant ODW as OpenData WebClient
  participant RR as RouteRepository
  participant OM as ObjectMapper

  Svc->>Svc: getBusInfo(...)
  alt dummy enabled
    Svc-->>Svc: return createDummyBusInfo()
  else real path
    Svc->>ODW: fetchStationByName(name) (GET)
    ODW-->>Svc: raw JSON
    Svc->>OM: parse StationSearchResponse
    Svc->>Svc: pick closest/top-3 stations
    Svc->>RR: findRouteNameById(busId)
    RR-->>Svc: Optional<routeName>
    loop stations
      Svc->>ODW: fetchArrivals(arsId) (GET)
      ODW-->>Svc: raw JSON
      Svc->>OM: parse OpenDataResponse
      Svc->>Svc: filter by routeName (if present)
    end
    Svc-->>Svc: build BusInfo
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Assessment against linked issues

Objective Addressed Explanation
쿼리 최적화 (#167)
API 호출을 비동기 처리 (#167)
API 호출 최소화 (#167) 정류장 후보 축소/노선명 필터링은 보이지만, 캐싱/배치 처리 등 체계적 감축 근거는 확인 불가.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation

Possibly related PRs

Suggested reviewers

  • KiSeungMin
  • zyovn
  • wonjun-lee-fcwj245
  • seung-in-Yoo

Poem

버니는 귀를 세우고 그래프를 짠다
노선은 프로젝션, 발걸음은 InternalStep
바람처럼 WebClient 휘익—비동기 응답을 줍는다
가장 가까운 정류장, 토끼 코가 먼저 맡고
길찾기 속도는 퐁퐁—봄 풀처럼 싹 튼다 🥕✨

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.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

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.

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

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

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/main/java/com/wayble/server/direction/service/BusInfoService.java (1)

120-126: 더미 데이터는 결정적(seed) 무작위로 생성해 재현성 확보

현재 Math.random()은 호출 시점마다 값이 달라 테스트/디버깅이 어렵습니다. 입력에 기반한 seed를 사용해 결정적으로 생성하세요.

-        List<Boolean> isLowFloor = new ArrayList<>();
-        isLowFloor.add(Math.random() < 0.7);
-        isLowFloor.add(Math.random() < 0.5);
-        
-        Integer dispatchInterval = (int) (Math.random() * 15) + 1;
+        var seed = java.util.Objects.hash(stationName, busId);
+        var rnd  = new java.util.Random(seed);
+        List<Boolean> isLowFloor = new ArrayList<>();
+        isLowFloor.add(rnd.nextDouble() < 0.7);
+        isLowFloor.add(rnd.nextDouble() < 0.5);
+        Integer dispatchInterval = rnd.nextInt(15) + 1;
src/main/java/com/wayble/server/direction/service/TransportationService.java (3)

328-353: 실제 사용 엣지 추출 시 '정류장명 비교'는 오탐 위험 — NodeRef ID 기반으로 단순/정확화

정류장명은 중복 가능해 오탐이 발생합니다. 이미 InternalStepstartNode/endNode(NodeRef)가 있으므로 ID를 사용하면 O(S)로 정확히 추출할 수 있습니다.

-    private Set<Pair<Long, Long>> extractActualEdgesFromRoute(List<InternalStep> route, Map<Long, List<Edge>> graph) {
-        Set<Pair<Long, Long>> usedEdges = new HashSet<>();
-        
-        for (InternalStep step : route) {
-            String fromName = step.from();
-            String toName = step.to();
-            
-            for (Map.Entry<Long, List<Edge>> entry : graph.entrySet()) {
-                Long nodeId = entry.getKey();
-                List<Edge> edges = entry.getValue();
-                
-                for (Edge edge : edges) {
-                    Node fromNode = edge.getStartNode();
-                    Node toNode = edge.getEndNode();
-                    
-                    if ((fromNode.getStationName().equals(fromName) && toNode.getStationName().equals(toName)) ||
-                        (fromNode.getStationName().equals(toName) && toNode.getStationName().equals(fromName))) {
-                        usedEdges.add(Pair.of(fromNode.getId(), toNode.getId()));
-                        usedEdges.add(Pair.of(toNode.getId(), fromNode.getId()));
-                    }
-                }
-            }
-        }
-        
-        return usedEdges;
-    }
+    private Set<Pair<Long, Long>> extractActualEdgesFromRoute(List<InternalStep> route, Map<Long, List<Edge>> graph) {
+        Set<Pair<Long, Long>> usedEdges = new HashSet<>();
+        for (InternalStep step : route) {
+            if (step.startNode() != null && step.endNode() != null) {
+                Long fromId = step.startNode().id();
+                Long toId   = step.endNode().id();
+                if (fromId != null && toId != null) {
+                    usedEdges.add(Pair.of(fromId, toId));
+                    usedEdges.add(Pair.of(toId, fromId));
+                }
+            }
+        }
+        return usedEdges;
+    }

383-391: 경로 동일성 비교 시 routeName 대신 routeId 우선 사용

노선명은 표기 흔들림(공백/접두어/개편)으로 오탐 가능성이 큽니다. routeId가 있으면 그것을 기준으로 비교하세요.

-            if (step1.mode() != step2.mode() || 
-                !Objects.equals(step1.from(), step2.from()) || 
-                !Objects.equals(step1.to(), step2.to()) ||
-                !Objects.equals(step1.routeName(), step2.routeName())) {
+            if (step1.mode() != step2.mode()
+                || !Objects.equals(step1.from(), step2.from())
+                || !Objects.equals(step1.to(), step2.to())
+                || !Objects.equals(step1.routeId(), step2.routeId())) {
                 return false;
             }

858-890: 환승 걷기거리 계산은 문자열 매칭 대신 NodeRef 좌표로 직접 계산

현재 calculateTransferWalkDistancefrom/to 문자열로 pathEdges를 뒤져 노드를 찾습니다(중복명시 엣지 케이스에 취약). 이미 각 step에 endNode/startNode가 있으니 이를 사용해 직접 거리 계산하세요.

-                    // 이전 step의 도착지와 다음 step의 출발지 사이의 직선거리 계산
-                    int walkDistance = calculateTransferWalkDistance(transferFrom, transferTo, pathEdges);
+                    // 이전 step의 도착지와 다음 step의 출발지 좌표를 활용해 직선거리 계산
+                    int walkDistance = calculateTransferWalkDistance(
+                        currentStep.endNode(),
+                        nextStep.startNode()
+                    );
...
-                    InternalStep walkStep = new InternalStep(
+                    InternalStep walkStep = new InternalStep(
                         DirectionType.WALK,
                         null,
                         null,
                         walkDistance,
                         null,
                         null,
                         transferFrom,
                         transferTo,
                         null,
-                        null,
-                        null
+                        currentStep.endNode(),
+                        nextStep.startNode()
                     );

그리고 계산 함수를 교체합니다:

-    private int calculateTransferWalkDistance(String fromStation, String toStation, List<Edge> pathEdges) {
-        ...
-    }
+    private int calculateTransferWalkDistance(NodeRef fromNode, NodeRef toNode) {
+        if (fromNode == null || toNode == null) return 0;
+        double km = haversine(fromNode.latitude(), fromNode.longitude(), toNode.latitude(), toNode.longitude());
+        return (int) (km * 1000);
+    }
🧹 Nitpick comments (26)
src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java (1)

16-17: 중복 값/불안정 정렬 대비: DISTINCT + ORDER BY를 권장합니다.

호출 측(FacilityService)에서 휠체어 위치는 정렬이나 중복 제거를 하지 않습니다. 결과의 결정성을 보장하고 중복 레코드 가능성(데이터 품질 이슈)에 대비하려면 JPQL에 DISTINCT와 ORDER BY를 추가하는 편이 안전합니다.

적용 예시:

-    @Query("SELECT w.wheelchairLocation FROM Wheelchair w WHERE w.route.routeId = :routeId")
+    @Query("SELECT DISTINCT w.wheelchairLocation FROM Wheelchair w WHERE w.route.routeId = :routeId ORDER BY w.wheelchairLocation ASC")
     List<String> findWheelchairLocationsByRouteId(@Param("routeId") Long routeId);
src/main/java/com/wayble/server/direction/service/FacilityService.java (5)

19-19: 불필요한 import 제거 제안: lombok.Builder 미사용

해당 파일 내에서 Builder를 사용하지 않습니다. 린한 코드 유지를 위해 제거를 권장합니다.

-import lombok.Builder;

50-57: trim 중복 호출 제거 및 결과 정렬로 결정성 확보

현재 trim을 두 번 호출합니다. 한 번만 수행하고 비어 있지 않은 값만 수집하도록 단순화할 수 있습니다. 또한 리포지토리 레벨에서 ORDER BY를 추가하지 않는다면, 서비스 레벨에서라도 정렬해 응답의 결정성을 보장하는 것이 좋습니다.

-        if (routeId != null) {
-            List<String> wheelchairLocations = wheelchairInfoRepository.findWheelchairLocationsByRouteId(routeId);
-            for (String location : wheelchairLocations) {
-                if (location != null && !location.trim().isEmpty()) {
-                    wheelchair.add(location.trim());
-                }
-            }
-        }
+        if (routeId != null) {
+            List<String> wheelchairLocations = wheelchairInfoRepository.findWheelchairLocationsByRouteId(routeId);
+            for (String location : wheelchairLocations) {
+                if (location == null) continue;
+                String trimmed = location.trim();
+                if (!trimmed.isEmpty()) {
+                    wheelchair.add(trimmed);
+                }
+            }
+            // 리포지토리에서 ORDER BY를 쓰지 않는 경우 서비스 레벨에서 정렬
+            wheelchair.sort(String::compareTo);
+        }

59-69: KRIC 호출의 지연/불안정성에 대비한 타임아웃/캐싱 권장

  • 현재 WebClient 호출은 block()으로 동기화되며 타임아웃이 없어 외부 API 지연 시 스레드가 장시간 점유될 수 있습니다.
  • 동일(stinCd/railOpr/lnCd) 키 조합에 대한 반복 호출이 예상됩니다. 짧은 TTL 캐싱(Caffeine/Redis)을 적용하면 성능 목표에 부합합니다.

최소 변경으로 타임아웃만 추가하는 예시(메서드 내부 연쇄에 timeout 추가):

             KricToiletRawResponse response = kricWebClient
                     .get()
                     .uri(uri)
                     .retrieve()
                     .bodyToMono(KricToiletRawResponse.class)
-                    .block();
+                    .timeout(java.time.Duration.ofSeconds(2))
+                    .block();

캐싱은 프록시 한계를 피하기 위해 별도 @service 컴포넌트로 분리(public 메서드에 @Cacheable)하는 접근을 권장합니다. 키는 (railOprLsttCd, lnCd, stinCd) 조합을 사용하세요. 원하시면 제가 캐시 어댑터 초안을 드리겠습니다.


59-77: 누락 필드 경고 로깅은 좋습니다. 추가로 빈 문자열도 방어하세요.

null만 체크하고 있어 공백 문자열이 들어오면 외부 API 호출 실패 가능성이 있습니다. isBlank()까지 포함하는 방어를 권장합니다.

-            if (stinCd != null && railOprLsttCd != null && lnCd != null) {
+            if (stinCd != null && !stinCd.isBlank()
+                    && railOprLsttCd != null && !railOprLsttCd.isBlank()
+                    && lnCd != null && !lnCd.isBlank()) {

70-71: getElevatorInfo 메서드 시그니처 간소화 제안

getElevatorInfo(Facility, Long)routeId 파라미터가 메서드 본문에서 전혀 사용되지 않으므로, 혼동 방지 및 API 표면 최소화를 위해 아래와 같이 시그니처와 호출부에서 제거를 권장합니다.

• 수정 위치

  • src/main/java/com/wayble/server/direction/service/FacilityService.java:
    • 호출부 (70행)
    • 메서드 정의부 (138행)

• 예시 diff

--- a/src/main/java/com/wayble/server/direction/service/FacilityService.java
+++ b/src/main/java/com/wayble/server/direction/service/FacilityService.java
@@ -67,7 +67,7 @@
                 accessibleRestroom = toiletInfo.getOrDefault(stinCd, false);
             }
 
-            elevator = getElevatorInfo(facility, routeId);
+            elevator = getElevatorInfo(facility);
         } else {
             log.warn("Facility 정보 누락 - nodeId: {}, stinCd: {}, railOprLsttCd: {}, lnCd: {}", 
                 nodeId, stinCd, railOprLsttCd, lnCd);
@@ -135,7 +135,7 @@
     @Transactional(readOnly = true)
-    private List<String> getElevatorInfo(Facility facility, Long routeId) {
+    private List<String> getElevatorInfo(Facility facility) {
         List<String> elevatorLocations = new ArrayList<>();
         
         try {

이 변화로 컴파일 에러가 발생하지 않음을 확인했으며, getElevatorInfofacility만으로도 모든 로직을 수행합니다.

src/main/java/com/wayble/server/direction/dto/NodeRef.java (2)

3-8: 좌표 필드의 박싱 타입(Double) → 원시 타입(double) 전환 고려

내부 라우팅 DTO라면 latitude/longitude가 null일 가능성이 거의 없을 것입니다. Double 대신 double을 쓰면 불필요한 오토박싱/GC를 줄이고 NPE 리스크도 낮출 수 있습니다.

적용 예시:

 public record NodeRef(
     Long id, 
     String stationName, 
-    Double latitude, 
-    Double longitude
+    double latitude, 
+    double longitude
 ) {}

3-8: 명명 일관성 통일 제안(lat/lon vs. latitude/longitude)

동일 PR 내 com.wayble.server.direction.entity.Node(record)의 필드명이 lat/lon인 반면 본 DTO는 latitude/longitude를 사용합니다. 팀 컨벤션에 맞춰 통일하면 매핑 로직과 가독성이 좋아집니다. 변경 영향이 크면 유지하되, 매퍼/변환 유틸에서의 명시적 매핑을 확인해 주세요.

src/main/java/com/wayble/server/direction/external/opendata/dto/StationSearchResponse.java (2)

16-21: arsId 추가 LGTM — 필드 매핑 명시화(@JsonProperty)로 회귀 리스크 축소

레코드 컴포넌트 이름 기반 매핑은 Jackson 설정(파라미터 이름 유지 등)에 의존합니다. 외부 스키마 변화 시 회귀를 줄이려면 @JsonProperty로 명시 매핑을 권장합니다.

적용 예시:

     @JsonIgnoreProperties(ignoreUnknown = true)
     public record StationItem(
-            String arsId,
-            String stId,
-            String stNm,
-            String tmX,
-            String tmY
+            @com.fasterxml.jackson.annotation.JsonProperty("arsId") String arsId,
+            @com.fasterxml.jackson.annotation.JsonProperty("stId") String stId,
+            @com.fasterxml.jackson.annotation.JsonProperty("stNm") String stNm,
+            @com.fasterxml.jackson.annotation.JsonProperty("tmX") String tmX,
+            @com.fasterxml.jackson.annotation.JsonProperty("tmY") String tmY
     ) {}

16-21: itemList/필드 null 가능성에 대한 방어적 처리

Open Data 응답에서 itemList 또는 개별 필드가 누락될 수 있습니다. 서비스 레이어에서 NPE 없이 동작하도록 Collections.emptyList() 기본값 부여 또는 널 처리(옵셔널/필터링) 경로를 점검해 주세요. 좌표(tmX, tmY)가 문자열이므로 파싱 실패/좌표계 변환 실패에 대한 예외 처리도 필요합니다.

src/main/java/com/wayble/server/direction/entity/transportation/Route.java (1)

44-53: 정적 팩토리 추가 LGTM — wheelchairs의 일관 초기화 보장

현재 createRoute에서만 wheelchairs(new ArrayList<>())가 보장됩니다. 향후 다른 빌더 진입 경로가 추가될 경우 NPE 리스크가 있습니다. @Builder.Default로 필드에 기본값을 두고, 팩토리에서는 중복 초기화를 제거하는 패턴을 권장합니다.

권장 수정:

-    private List<Wheelchair> wheelchairs;
+    @Builder.Default
+    private List<Wheelchair> wheelchairs = new ArrayList<>();

그리고 팩토리에서는 중복 설정을 제거:

     public static Route createRoute(Long routeId, String routeName, DirectionType routeType, Node startNode, Node endNode) {
         return Route.builder()
                 .routeId(routeId)
                 .routeName(routeName)
                 .routeType(routeType)
                 .startNode(startNode)
                 .endNode(endNode)
-                .wheelchairs(new ArrayList<>())
                 .build();
     }

추가로 양방향 연관관계 편의 메서드(addWheelchair)를 제공하면 연관관계 일관성 관리가 쉬워집니다.

src/main/java/com/wayble/server/common/config/WebClientConfig.java (1)

45-53: OpenData WebClient 개선 제안: baseUrl·기본 헤더·타임아웃·재시도로 안정성·응답 속도 강화

PR 목표인 외부 API 호출 응답 속도 향상 및 회복력 강화를 위해, openDataWebClient()에 아래 선택적 리팩터링을 추천드립니다.

  • baseUrl 명시로 URI 구성 가독성↑
  • 기본 Accept 헤더 지정으로 컨텐츠 협상 일관성 보장
  • maxInMemorySize 확대(1 MB)로 큰 응답 처리 여유 확보
  • timeout·retry 설정으로 지연/실패 시 빠른 오류 복구

수정 예시 (파일: src/main/java/com/wayble/server/common/config/WebClientConfig.java Lines 45–53):

     @Bean
     public WebClient openDataWebClient() {
-        DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(openDataProperties.baseUrl());
-        factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
-        return WebClient.builder()
-                .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(512 * 1024))
-                .uriBuilderFactory(factory)
-                .build();
+        DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(openDataProperties.baseUrl());
+        factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
+        return WebClient.builder()
+                .uriBuilderFactory(factory)
+                .defaultHeader(org.springframework.http.HttpHeaders.ACCEPT, openDataProperties.accept())
+                .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1 * 1024 * 1024)) // 필요 시 값 조정
+                .filter((request, next) -> next.exchange(request)
+                        .timeout(java.time.Duration.ofSeconds(openDataProperties.timeout()))
+                        .retryWhen(reactor.util.retry.Retry.backoff(3, java.time.Duration.ofSeconds(1))
+                                .filter(t -> t instanceof org.springframework.web.reactive.function.client.WebClientRequestException)))
+                .build();
     }

환경설정 확인 스크립트:

#!/bin/bash
# @ConfigurationProperties(prefix="opendata.api") 바인딩 확인
rg -n '@ConfigurationProperties\s*\(\s*prefix\s*=\s*"opendata\.api"\s*\)' -g 'src/**/*.java'
# application.properties에 opendata.api.* 설정 확인
rg -n 'opendata\.api\.' -g 'src/main/resources/application.properties' -C2
src/main/java/com/wayble/server/direction/repository/NodeRepository.java (2)

13-22: 대용량 범위 조회 대비: 공간 인덱스/복합 인덱스 고려

현재 정렬이 ORDER BY latitude, longitude로 들어가 있어 파일 소트가 발생할 수 있습니다. Node 테이블에 (latitude, longitude) 복합 인덱스(또는 DB가 허용한다면 공간 인덱스)를 추가하면 본 쿼리의 필터링과 정렬 비용을 크게 줄일 수 있습니다. 마이그레이션에 인덱스 추가를 권장드립니다.


5-7: (선택) Pageable 도입으로 결과 개수 제어 및 정렬 일관성 강화

지금은 bounding box 내 모든 노드를 반환합니다. 화면/페이지 단에서 일부만 필요할 가능성이 높아 Pageable 인자를 추가해 네트워크/메모리 사용을 줄이는 것을 권장합니다. JPQL에 명시된 ORDER BY는 그대로 유지되며, Pageable의 page/size만 활용 가능합니다.

적용 예시:

 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.Query;
 import org.springframework.data.repository.query.Param;
@@
-    List<NodeBoundingBoxProjection> findNodesInBoundingBox(
-            @Param("minLat") double minLat,
-            @Param("maxLat") double maxLat,
-            @Param("minLon") double minLon,
-            @Param("maxLon") double maxLon
-    );
+    List<NodeBoundingBoxProjection> findNodesInBoundingBox(
+            @Param("minLat") double minLat,
+            @Param("maxLat") double maxLat,
+            @Param("minLon") double minLon,
+            @Param("maxLon") double maxLon,
+            Pageable pageable
+    );

Also applies to: 23-28

src/main/java/com/wayble/server/direction/repository/RouteRepository.java (1)

12-13: 메서드 명칭이 혼동 소지 — routeId 사용을 이름에 드러내기 권장

PK가 아닐 수 있는 routeId로 조회하므로 findRouteNameById 보다는 findRouteNameByRouteId처럼 더 구체적인 이름이 유지보수에 유리합니다. 릴리스 브랜치 특성상 즉시 변경이 어렵다면 이후 리팩터링 백로그로 남기는 것을 제안드립니다.

변경 예시:

-    @Query("SELECT r.routeName FROM Route r WHERE r.routeId = :routeId")
-    Optional<String> findRouteNameById(@Param("routeId") Long routeId);
+    @Query("SELECT r.routeName FROM Route r WHERE r.routeId = :routeId")
+    Optional<String> findRouteNameByRouteId(@Param("routeId") Long routeId);
src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java (2)

32-41: 역직렬화/매핑 단위 테스트 추가 권장

스펙 필드명이 API와 1:1 대응하는지, null/결측 시 안전한지 빠르게 보증하기 위해 JSON 스냅샷 기반 테스트를 추가하는 것을 권장합니다. 특히 busType1/busType2/term이 문자열 숫자로 오는 케이스와 누락 케이스를 함께 검증해 주세요.

원하시면 예시 JSON과 ObjectMapper를 이용한 간단한 역직렬화 테스트 코드를 드리겠습니다.


32-41: (선택) 정수형 파생 변환은 서비스/매퍼 계층에서 수행

busType1/busType2/term을 Integer로 변환해 사용하는 경우가 많다면 DTO/서비스 매퍼에서 안전 변환(빈 문자열/비정상 값 default 처리)을 일괄 수행해 도메인 로직 단에서 타입 캐스팅을 제거하는 것을 제안드립니다. 레코드 자체의 필드 타입은 외부 스펙에 맞춰 String을 유지하는 편이 안전합니다.

src/main/java/com/wayble/server/direction/repository/EdgeBoundingBoxProjection.java (1)

5-18: (선택) 시작/종료 노드 서브 프로젝션으로 응집도 향상

필드 수가 많아 가독성이 떨어질 수 있습니다. StartNodeProjection(id, stationName, lat, lon) / EndNodeProjection(...) 같은 서브 프로젝션을 도입하면 쿼리/매핑 양쪽 모두의 응집도가 올라갑니다. 다만 JPQL alias 재정렬이 필요하므로 차기 리팩터링 대상 정도로 제안드립니다.

src/main/java/com/wayble/server/direction/dto/InternalStep.java (1)

13-14: moveNumber 단위/의미 주석 보완

moveNumber가 WALK일 때 미터 단위, 그 외는 "이동 횟수"로 사용됩니다. 필드 주석에 단위를 명시하면 해석 차이를 줄일 수 있습니다.

src/main/java/com/wayble/server/direction/repository/EdgeRepository.java (1)

33-45: 중복 반환 가능성 최소화를 위한 DISTINCT 고려

현재 JOIN 구조상 Edge:Route가 1:N이 아니면 중복은 거의 없겠지만, 스키마/매핑 변경에 취약합니다. 안전 차원에서 DISTINCT를 권장합니다. 또한, 공간 조건에 인덱스가 없다면 성능 병목이 될 수 있습니다.

-    @Query("SELECT " +
+    @Query("SELECT DISTINCT " +
            "e.id         as edgeId, " +
            ...
            "ORDER BY e.id")

추가로 DB에 다음 인덱스를 고려해 주세요(실제 컬럼/DB에 맞춰 조정 필요):

  • start_node(latitude, longitude), end_node(latitude, longitude) 복합 인덱스
src/main/java/com/wayble/server/direction/service/BusInfoService.java (3)

131-161: 불필요한 URI 문자열과 ObjectMapper 생성 반복 제거

fullUri는 사용되지 않습니다. 또한 매번 new ObjectMapper()를 생성하지 말고 빈 주입으로 재사용하세요.

-            String fullUri = openDataProperties.baseUrl() + 
-                    openDataProperties.endpoints().arrivals() +
-                    "?serviceKey=" + serviceKey +
-                    "&arsId=" + stationId +
-                    "&resultType=json";
...
-                    ObjectMapper objectMapper = new ObjectMapper();
-                    originalResponse = objectMapper.readValue(rawResponse, OpenDataResponse.class);
+                    originalResponse = objectMapper.readValue(rawResponse, OpenDataResponse.class);

동일 방식으로 fetchStationByName에서도 fullUri 제거 및 ObjectMapper 재사용을 적용하세요.


177-188: 노선명 비교 방식 보완 제안

projectRouteName.contains(item.rtNm()) || item.rtNm().contains(projectRouteName)는 오탐/미탐이 생길 수 있습니다(예: "6628" vs "B6628", 공백/접미어). 정규화(숫자만 비교, 공백/특수문자 제거) 후 비교를 고려하세요.


55-66: 다중 후보 조회 시 API 호출 최소화

상위 3개 정류소에 대해 각각 fetchArrivals를 호출한 뒤, 최종 선택된 정류소에 대해 다시 fetchArrivals를 호출하고 있습니다. 최초 조회 결과를 캐싱하여 재사용하면 공공데이터 API 호출 수를 절반 이하로 줄일 수 있습니다.

src/main/java/com/wayble/server/direction/service/TransportationService.java (3)

679-694: 미사용 requestId 제거 및 시그니처 단순화

requestId는 생성만 되고 사용되지 않습니다. 불필요한 인자를 제거해 가독성을 높이세요.

-        long requestId = System.currentTimeMillis();
-
-        return mergeConsecutiveRoutes(pathEdges, requestId);
+        return mergeConsecutiveRoutes(pathEdges);

그리고 아래 정의도 함께 수정:

-    private List<InternalStep> mergeConsecutiveRoutes(List<Edge> pathEdges, long requestId) {
+    private List<InternalStep> mergeConsecutiveRoutes(List<Edge> pathEdges) {

200-214: finally에서 명시적 GC 호출은 지양 권장

System.gc()는 서버 전체에 영향이 커 예측 불가능한 STW를 유발할 수 있습니다. 진짜 OOM 안전장치가 필요하다면 메모리 상한/노드 제한/그래프 타일링 등 상위 레벨 제약으로 제어하는 편이 안전합니다.


986-1010: 환승 횟수 계산 로직 단순화 제안

병합 이후 동일 모드/동일 노선의 연속 Step은 거의 나오지 않습니다. 현재 분기들은 일부 중복적이며 과대 계상 가능성이 있습니다. "연속한 두 Step이 (WALK/Waypoint 제외) 모드가 다르거나 routeId가 다르면 환승 1회"로 단순화하면 오판을 줄일 수 있습니다.

📜 Review details

Configuration used: CodeRabbit UI
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 6c33eee and 1305b1c.

📒 Files selected for processing (18)
  • src/main/java/com/wayble/server/common/config/WebClientConfig.java (2 hunks)
  • src/main/java/com/wayble/server/direction/dto/InternalStep.java (1 hunks)
  • src/main/java/com/wayble/server/direction/dto/NodeRef.java (1 hunks)
  • src/main/java/com/wayble/server/direction/entity/transportation/Edge.java (1 hunks)
  • src/main/java/com/wayble/server/direction/entity/transportation/Route.java (2 hunks)
  • src/main/java/com/wayble/server/direction/external/opendata/OpenDataProperties.java (0 hunks)
  • src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java (1 hunks)
  • src/main/java/com/wayble/server/direction/external/opendata/dto/StationSearchResponse.java (1 hunks)
  • src/main/java/com/wayble/server/direction/repository/EdgeBoundingBoxProjection.java (1 hunks)
  • src/main/java/com/wayble/server/direction/repository/EdgeRepository.java (2 hunks)
  • src/main/java/com/wayble/server/direction/repository/FacilityRepository.java (0 hunks)
  • src/main/java/com/wayble/server/direction/repository/NodeBoundingBoxProjection.java (1 hunks)
  • src/main/java/com/wayble/server/direction/repository/NodeRepository.java (2 hunks)
  • src/main/java/com/wayble/server/direction/repository/RouteRepository.java (1 hunks)
  • src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java (1 hunks)
  • src/main/java/com/wayble/server/direction/service/BusInfoService.java (5 hunks)
  • src/main/java/com/wayble/server/direction/service/FacilityService.java (2 hunks)
  • src/main/java/com/wayble/server/direction/service/TransportationService.java (25 hunks)
💤 Files with no reviewable changes (2)
  • src/main/java/com/wayble/server/direction/external/opendata/OpenDataProperties.java
  • src/main/java/com/wayble/server/direction/repository/FacilityRepository.java
🧰 Additional context used
🧬 Code graph analysis (13)
src/main/java/com/wayble/server/direction/dto/NodeRef.java (2)
src/main/java/com/wayble/server/direction/entity/Node.java (1)
  • Node (3-8)
src/main/java/com/wayble/server/direction/entity/transportation/Node.java (3)
  • Entity (13-79)
  • Node (55-61)
  • createNode (63-65)
src/main/java/com/wayble/server/direction/repository/NodeBoundingBoxProjection.java (1)
src/main/java/com/wayble/server/direction/entity/transportation/Node.java (2)
  • Entity (13-79)
  • Node (55-61)
src/main/java/com/wayble/server/direction/entity/transportation/Edge.java (1)
src/main/java/com/wayble/server/direction/entity/transportation/Node.java (2)
  • Entity (13-79)
  • createNode (63-65)
src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java (1)
src/main/java/com/wayble/server/direction/entity/transportation/Wheelchair.java (1)
  • Entity (6-24)
src/main/java/com/wayble/server/common/config/WebClientConfig.java (1)
src/main/java/com/wayble/server/direction/external/opendata/OpenDataProperties.java (1)
  • ConfigurationProperties (5-16)
src/main/java/com/wayble/server/direction/dto/InternalStep.java (1)
src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (5)
  • Step (21-30)
  • Route (16-19)
  • Schema (11-64)
  • MoveInfo (37-39)
  • SubwayInfo (47-51)
src/main/java/com/wayble/server/direction/repository/RouteRepository.java (2)
src/main/java/com/wayble/server/direction/repository/FacilityRepository.java (2)
  • FacilityRepository (12-17)
  • Query (13-16)
src/main/java/com/wayble/server/direction/repository/ElevatorRepository.java (1)
  • ElevatorRepository (11-14)
src/main/java/com/wayble/server/direction/entity/transportation/Route.java (2)
src/main/java/com/wayble/server/direction/entity/transportation/Wheelchair.java (1)
  • Entity (6-24)
src/main/java/com/wayble/server/direction/entity/transportation/Node.java (1)
  • Entity (13-79)
src/main/java/com/wayble/server/direction/service/TransportationService.java (1)
src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (2)
  • Route (16-19)
  • Schema (11-64)
src/main/java/com/wayble/server/direction/repository/NodeRepository.java (1)
src/main/java/com/wayble/server/direction/entity/transportation/Node.java (1)
  • Entity (13-79)
src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java (2)
src/main/java/com/wayble/server/direction/external/opendata/dto/Arrival.java (1)
  • Arrival (3-7)
src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (1)
  • Schema (11-64)
src/main/java/com/wayble/server/direction/service/BusInfoService.java (2)
src/main/java/com/wayble/server/direction/service/FacilityService.java (1)
  • Service (33-162)
src/main/java/com/wayble/server/direction/service/TransportationService.java (1)
  • Slf4j (27-1013)
src/main/java/com/wayble/server/direction/service/FacilityService.java (3)
src/main/java/com/wayble/server/direction/dto/response/TransportationResponseDto.java (1)
  • NodeInfo (59-63)
src/main/java/com/wayble/server/direction/repository/FacilityRepository.java (1)
  • FacilityRepository (12-17)
src/main/java/com/wayble/server/direction/entity/transportation/Facility.java (1)
  • Entity (11-45)
🔇 Additional comments (11)
src/main/java/com/wayble/server/direction/repository/WheelchairInfoRepository.java (2)

16-17: 프로젝션 기반 조회 추가, 방향성 좋습니다.

엔티티 전체 대신 단일 필드(String) 프로젝션으로 조회해 불필요한 로딩을 피합니다. 읽기 경로에서의 메모리/성능 측면에서 이득이 큽니다.


16-17: wheelchair 테이블의 route_id 인덱스 존재 여부 수동 확인 필요

  • 해당 메서드
    @Query("SELECT w.wheelchairLocation FROM Wheelchair w WHERE w.route.routeId = :routeId")
    List<String> findWheelchairLocationsByRouteId(@Param("routeId") Long routeId);
    routeId 단일 컬럼 필터를 사용합니다.
  • 조회 빈도가 높다면, DB 레벨에서 wheelchair(route_id) 인덱스가 존재하는지 반드시 확인하고, 없다면 추가를 검토해 주세요.
  • 자동 검사 스크립트 실행 환경에 MySQL 클라이언트가 없어 결과를 확인하지 못했습니다.
    운영/로컬 DB에서 다음과 같은 명령어로 수동 확인 바랍니다:
    SHOW INDEX FROM wheelchair;
src/main/java/com/wayble/server/direction/entity/transportation/Edge.java (1)

48-56: 리뷰 코멘트 정정: createEdgeWithRoute는 읽기 전용 맥락에서만 사용되어 수동 ID 설정 위험이 없습니다

해당 팩토리 메서드는 DB에서 조회된 edgeId를 엔티티에 매핑하는 용도로만 쓰이며, 이후에 savepersist 호출이 전혀 없습니다. 따라서 @GeneratedValue(strategy = IDENTITY)와 충돌하거나 불필요한 업데이트·제약 위반을 발생시킬 우려가 없으므로, 오버로드 추가나 사용처 분리 작업은 불필요합니다.

Likely an incorrect or invalid review comment.

src/main/java/com/wayble/server/direction/repository/NodeRepository.java (2)

13-22: 프로젝션 기반 JPQL로 I/O 최적화 잘 하셨습니다

필요한 컬럼만 alias로 선택하여 NodeBoundingBoxProjection에 매핑하는 방식이 적절합니다. alias(id, stationName, nodeType, latitude, longitude)와 프로젝션 getter명이 1:1로 일치해 런타임 바인딩에도 문제가 없어 보입니다. 정렬 조건(위도, 경도)도 범위 조회 결과의 안정성을 높이는 데 도움 됩니다.


23-28: 호출부 검증 완료 – 반환 타입 변경 안전함

  • 실행한 스크립트 결과, findNodesInBoundingBox 메서드는 src/main/java/com/wayble/server/direction/service/TransportationService.java 112–114행에서만 호출되며,
    모든 호출부가 List<NodeBoundingBoxProjection>로 안전하게 받고 있습니다.
  • List<Node>로 받는 사용처는 전혀 존재하지 않습니다.

따라서 반환 타입을 List<Node>에서 List<NodeBoundingBoxProjection>로 변경한 사항은 문제 없이 적용되었으며, 추가 조치는 필요하지 않습니다.

src/main/java/com/wayble/server/direction/repository/NodeBoundingBoxProjection.java (1)

5-11: 프로젝션 정의 적합

엔티티 필드 타입과 일치하는 getter 시그니처로 닫힌(projection) 인터페이스가 잘 정의되어 있습니다. 상위 레이어에서 엔티티 의존성을 줄이고 I/O를 절약하는 목적에 부합합니다.

src/main/java/com/wayble/server/direction/repository/RouteRepository.java (1)

12-13: Route 엔티티의 routeId는 Primary Key로 유니크·인덱스가 이미 보장됩니다

  • src/main/java/com/wayble/server/direction/entity/transportation/Route.javarouteId 필드에 @Id 어노테이션이 적용되어 있어, JPA 매핑 시 DB 테이블의 기본 키(primary key)로 지정됩니다.
  • 관계형 DB에서는 기본 키 컬럼에 대해 자동으로 유니크 제약(unique constraint)과 인덱스가 생성되므로, 별도의 보조 인덱스 추가나 유니크 제약 선언은 불필요합니다.

따라서 해당 리포지토리 메서드(findRouteNameById)에서 routeId를 기준으로 조회할 때, 성능 및 유니크 보장은 이미 확보된 상태입니다.

src/main/java/com/wayble/server/direction/external/opendata/dto/OpenDataResponse.java (1)

37-41: 필드 확장(rtNm, stNm, arsId)으로 도메인 요구 충족 — 역직렬화 호환성 OK

@JsonIgnoreProperties(ignoreUnknown = true) 하에 문자열 타입으로 안전하게 수용되어 호환성 문제가 없어 보입니다. Open API의 노선명/정류장명/ARS ID를 서비스 레이어에서 바로 활용할 수 있어 후속 로직 단순화에 도움이 됩니다.

src/main/java/com/wayble/server/direction/repository/EdgeBoundingBoxProjection.java (1)

5-18: 검증 완료: JPQL alias와 프로젝션 getter 일치
JPQL 쿼리에서 지정한 alias가 EdgeBoundingBoxProjection 인터페이스의 모든 getter 메서드 이름과 정확히 일치함을 확인했습니다. 별도 수정이 필요 없습니다.

• e.id as edgeId → getEdgeId()
• s.id as startNodeId → getStartNodeId()
• en.id as endNodeId → getEndNodeId()
• e.edgeType as edgeType → getEdgeType()
• s.stationName as startStationName → getStartStationName()
• s.latitude as startLatitude → getStartLatitude()
• s.longitude as startLongitude → getStartLongitude()
• en.stationName as endStationName → getEndStationName()
• en.latitude as endLatitude → getEndLatitude()
• en.longitude as endLongitude → getEndLongitude()
• r.routeId as routeId → getRouteId()
• r.routeName as routeName → getRouteName()

src/main/java/com/wayble/server/direction/repository/EdgeRepository.java (1)

20-33: 프로젝션 쿼리 자체는 적절함 (불필요 로딩 제거)
선택 컬럼만 가져오는 프로젝션으로 전환되어 I/O를 줄였습니다. ORDER BY e.id로 결과 안정성도 확보되어 있습니다.

src/main/java/com/wayble/server/direction/service/BusInfoService.java (1)

255-295: 불필요한 검토: findClosestStation가 실제로 호출되지 않습니다

현재 getBusInfo 메소드 최상단에 있는

return createDummyBusInfo(stationName, busId, x, y);

구문 때문에 이후에 위치한 실사용 로직(주석 처리된 부분 포함)과 findClosestStation 호출부는 실행 경로에 포함되지 않습니다.
따라서 좌표계 혼용 문제는 현 코드베이스에서는 발생할 수 없으므로 이 리뷰 코멘트는 무시하셔도 됩니다.

Likely an incorrect or invalid review comment.

Comment on lines +11 to +21
List<TransportationResponseDto.MoveInfo> moveInfo,
String routeName,
Integer moveNumber,
TransportationResponseDto.BusInfo busInfo,
TransportationResponseDto.SubwayInfo subwayInfo,
String from,
String to,
Long routeId,
NodeRef startNode,
NodeRef endNode
) {} No newline at end of file
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

null 허용 필드에 @nullable 명시 필요 (NPE 방지, 가독성 향상)

WALK/환승 등에서 moveInfo, busInfo, subwayInfo, routeId, startNode, endNode가 null로 사용됩니다. 내부 DTO라도 null 가능성을 타입 시그니처로 드러내면 후속 사용처에서 방어 코드가 단순해지고 리팩터 시 안전합니다.

아래와 같이 명시를 제안합니다.

+import org.springframework.lang.Nullable;

 public record InternalStep(
     DirectionType mode,
-    List<TransportationResponseDto.MoveInfo> moveInfo,
-    String routeName,
+    @Nullable List<TransportationResponseDto.MoveInfo> moveInfo,
+    @Nullable String routeName,
     Integer moveNumber,
-    TransportationResponseDto.BusInfo busInfo,
-    TransportationResponseDto.SubwayInfo subwayInfo,
+    @Nullable TransportationResponseDto.BusInfo busInfo,
+    @Nullable TransportationResponseDto.SubwayInfo subwayInfo,
     String from,
     String to,
-    Long routeId,
-    NodeRef startNode,
-    NodeRef endNode
+    @Nullable Long routeId,
+    @Nullable NodeRef startNode,
+    @Nullable NodeRef endNode
 ) {}
📝 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
List<TransportationResponseDto.MoveInfo> moveInfo,
String routeName,
Integer moveNumber,
TransportationResponseDto.BusInfo busInfo,
TransportationResponseDto.SubwayInfo subwayInfo,
String from,
String to,
Long routeId,
NodeRef startNode,
NodeRef endNode
) {}
import org.springframework.lang.Nullable;
public record InternalStep(
DirectionType mode,
- List<TransportationResponseDto.MoveInfo> moveInfo,
@Nullable List<TransportationResponseDto.MoveInfo> moveInfo,
@Nullable String routeName,
Integer moveNumber,
- TransportationResponseDto.BusInfo busInfo,
@Nullable TransportationResponseDto.BusInfo busInfo,
@Nullable TransportationResponseDto.SubwayInfo subwayInfo,
String from,
String to,
- Long routeId,
- NodeRef startNode,
@Nullable Long routeId,
@Nullable NodeRef startNode,
@Nullable NodeRef endNode
) {}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/dto/InternalStep.java around lines
11 to 21, annotate nullable fields to make nullability explicit: add a suitable
@Nullable import (e.g., javax.annotation.Nullable or
org.jetbrains.annotations.Nullable) and apply @Nullable to moveInfo, busInfo,
subwayInfo, routeId, startNode, and endNode in the constructor/record signature;
ensure the chosen @Nullable is imported and the rest of the code compiles.

Comment on lines +34 to +36
// 나중에 서비스키 문제 해결되면 이 함수 호출 제거
return createDummyBusInfo(stationName, busId, x, y);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

메인 병합에 더미 반환이 하드코딩됨 — 즉시 플래그로 가드 필요

getBusInfo가 무조건 더미를 반환하여 이후 실제 API 로직이 모두 도달 불가(unreachable)입니다. 릴리스 브랜치에서 예측불가한 랜덤 응답은 위험합니다. 환경 플래그로 제어하고, 더미 값은 결정적(seed 기반)으로 바꿔 주세요.

+import org.springframework.beans.factory.annotation.Value;
...
 @RequiredArgsConstructor
 public class BusInfoService {
 
     private final WebClient openDataWebClient;
     private final OpenDataProperties openDataProperties;
     private final RouteRepository routeRepository;
+    private final ObjectMapper objectMapper;
+
+    @Value("${feature.dummy-bus-info:false}")
+    private boolean dummyBusInfo;
 
     public TransportationResponseDto.BusInfo getBusInfo(String stationName, Long busId, Double x, Double y) {
-        // 나중에 서비스키 문제 해결되면 이 함수 호출 제거
-        return createDummyBusInfo(stationName, busId, x, y);
+        if (dummyBusInfo) {
+            return createDummyBusInfo(stationName, busId, x, y);
+        }

또한 ObjectMapper는 빈으로 주입해 재사용하도록 함께 반영했습니다(아래 다른 코멘트 참고).

📝 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 createDummyBusInfo(stationName, busId, x, y);
// Add this at the top with your other imports
import org.springframework.beans.factory.annotation.Value;
@RequiredArgsConstructor
public class BusInfoService {
private final WebClient openDataWebClient;
private final OpenDataProperties openDataProperties;
private final RouteRepository routeRepository;
private final ObjectMapper objectMapper;
// Feature flag to toggle dummy data
@Value("${feature.dummy-bus-info:false}")
private boolean dummyBusInfo;
public TransportationResponseDto.BusInfo getBusInfo(String stationName, Long busId, Double x, Double y) {
if (dummyBusInfo) {
return createDummyBusInfo(stationName, busId, x, y);
}
// …existing real API logic follows here…
}
// …other methods…
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/service/BusInfoService.java around
lines 34-36, the method currently always returns a hardcoded dummy via
createDummyBusInfo making the real API path unreachable; change this by gating
the dummy return behind a configuration flag (e.g., @Value or a feature property
with default false) so production runs use the real logic, make the dummy data
deterministic by seeding any random generator (or deriving values from
stationName/busId) instead of non-deterministic randomness, and refactor
ObjectMapper usage to inject a shared singleton bean (autowire the ObjectMapper)
rather than creating/using a new instance locally. Ensure the flag is
documented, defaulted to false, and the code falls through to the real API call
when the flag is off.

Comment on lines +50 to +52
private NodeRef toNodeRef(Node node) {
return new NodeRef(node.getId(), node.getStationName(), node.getLatitude(), node.getLongitude());
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

toNodeRef NPE 방지 (null-safe 변환)

일부 WALK/환승 단계에서 startNode/endNode가 null일 수 있습니다. 변환 함수에서 null을 처리해 두면 사용처가 단순해집니다.

-    private NodeRef toNodeRef(Node node) {
-        return new NodeRef(node.getId(), node.getStationName(), node.getLatitude(), node.getLongitude());
-    }
+    private NodeRef toNodeRef(Node node) {
+        return (node == null) ? null
+                : new NodeRef(node.getId(), node.getStationName(), node.getLatitude(), node.getLongitude());
+    }
📝 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 NodeRef toNodeRef(Node node) {
return new NodeRef(node.getId(), node.getStationName(), node.getLatitude(), node.getLongitude());
}
private NodeRef toNodeRef(Node node) {
return (node == null) ? null
: new NodeRef(node.getId(), node.getStationName(), node.getLatitude(), node.getLongitude());
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/direction/service/TransportationService.java
around lines 50 to 52, the toNodeRef method must be null-safe because some
WALK/transfer steps may have startNode/endNode == null; change the method to
handle a null input (accept nullable Node) and return null when node is null,
otherwise construct and return the NodeRef from node.getId(),
node.getStationName(), node.getLatitude(), node.getLongitude(); update any call
sites if they assume a non-null return to handle the possible null.

@hyoinYang hyoinYang merged commit 0d381f0 into main Aug 21, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release 프로덕션 서버에 배포

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[refactor] 대중교통 길찾기 api의 속도 개선

2 participants