feat: 품목으로 발주처 조회 및 발주처로 품목 리스트 조회 기능 구현#173
Conversation
Walkthrough품목과 발주처 간의 양방향 조회 기능을 구현합니다. 특정 품목의 발주처를 조회하고, 특정 발주처의 품목 목록을 조회할 수 있는 두 가지 새로운 REST API 엔드포인트를 추가하며, 이에 필요한 서비스 로직, 저장소 메서드, 성공 메시지 상수를 함께 구현합니다. Changes
Sequence DiagramsequenceDiagram
participant Client
participant ProductCtrl as ProductController
participant VendorCtrl as VendorController
participant ProdService as ProductService
participant ProdRepo as ProductRepository
participant DB as Database
rect rgb(200, 220, 240)
Note over Client,DB: 품목으로 발주처 조회 흐름
Client->>ProductCtrl: GET /api/v1/product/{productId}/vendor
ProductCtrl->>ProdService: getVendorByProduct(productId, userId)
ProdService->>ProdRepo: findById(productId)
ProdRepo->>DB: SELECT * FROM product WHERE id = ?
DB-->>ProdRepo: Product 객체
ProdRepo-->>ProdService: Product
ProdService->>ProdService: validateStoreAccess(product.store, userId)
ProdService->>ProdService: getVendor from Product
ProdService-->>ProductCtrl: VendorResponse
ProductCtrl-->>Client: 200 OK + ApiResponse<VendorResponse>
end
rect rgb(230, 240, 200)
Note over Client,DB: 발주처로 품목 리스트 조회 흐름
Client->>VendorCtrl: GET /api/v1/vendor/{vendorId}/products
VendorCtrl->>ProdService: getProductsByVendor(vendorId, userId)
ProdService->>ProdService: validateVendorAccess(vendorId, store)
ProdService->>ProdRepo: findByStoreIdAndVendorId(storeId, vendorId)
ProdRepo->>DB: SELECT * FROM product WHERE store_id = ? AND vendor_id = ?
DB-->>ProdRepo: List<Product>
ProdRepo-->>ProdService: 제품 목록
ProdService->>ProdService: Map to ProductResponse 리스트
ProdService-->>VendorCtrl: List<ProductResponse>
VendorCtrl-->>Client: 200 OK + ApiResponse<List<ProductResponse>>
end
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~20–25 minutes 특별히 검토할 영역:
Possibly Related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/main/java/com/almang/inventory/product/repository/ProductRepository.java (1)
31-32: 파생 쿼리 메서드가 올바르게 작성되었습니다.
storeId와vendorId조합으로 조회하는 것은 멀티테넌트 격리를 위한 좋은 설계입니다.성능 고려사항: 이 쿼리가 자주 호출된다면
(store_id, vendor_id)복합 인덱스 추가를 고려해보세요. 데이터 볼륨이 증가할수록 조회 성능에 영향을 줄 수 있습니다.CREATE INDEX idx_product_store_vendor ON product(store_id, vendor_id);src/test/java/com/almang/inventory/vendor/controller/VendorControllerTest.java (1)
731-749: 기본적인 엔드포인트 테스트가 구현되어 있습니다.현재 빈 리스트(
List.of())로 테스트하고 있는데, 실제 데이터가 포함된 케이스도 추가하면 응답 구조 검증이 더 완벽해집니다.- List<ProductResponse> response = List.of(); + List<ProductResponse> response = List.of( + new ProductResponse(1L, "테스트 품목", "P-001", ProductUnit.G, true, 1000, 1500, 1200, 1L, vendorId) + ); when(productService.getProductsByVendor(anyLong(), anyLong())) .thenReturn(response); // when & then mockMvc.perform(get("/api/v1/vendor/{vendorId}/products", vendorId) .with(authentication(auth())) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.status").value(200)) .andExpect(jsonPath("$.message").value(SuccessMessage.GET_VENDOR_PRODUCT_LIST_SUCCESS.getMessage())) - .andExpect(jsonPath("$.data").isArray()); + .andExpect(jsonPath("$.data").isArray()) + .andExpect(jsonPath("$.data.length()").value(1)) + .andExpect(jsonPath("$.data[0].vendorId").value(vendorId));이렇게 하면 응답 데이터 구조까지 검증할 수 있습니다. 단, 현재 구현도 기능적으로 문제없습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
src/main/java/com/almang/inventory/global/api/SuccessMessage.java(1 hunks)src/main/java/com/almang/inventory/product/controller/ProductController.java(2 hunks)src/main/java/com/almang/inventory/product/repository/ProductRepository.java(2 hunks)src/main/java/com/almang/inventory/product/service/ProductService.java(3 hunks)src/main/java/com/almang/inventory/vendor/controller/VendorController.java(3 hunks)src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java(2 hunks)src/test/java/com/almang/inventory/product/service/ProductServiceTest.java(2 hunks)src/test/java/com/almang/inventory/vendor/controller/VendorControllerTest.java(3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-20T10:43:47.502Z
Learnt from: JoonKyoLee
Repo: almang2/inventory-server PR: 33
File: src/main/java/com/almang/inventory/product/domain/Product.java:62-66
Timestamp: 2025-11-20T10:43:47.502Z
Learning: In the almang2/inventory-server repository, Product entity update methods (e.g., updateVendor in src/main/java/com/almang/inventory/product/domain/Product.java) do not require null checks on vendor parameters because ProductService validates vendor existence via findVendorByIdAndValidateAccess before calling update methods, and the vendor field has nullable=false constraint ensuring this.vendor is never null for persisted entities.
Applied to files:
src/test/java/com/almang/inventory/product/service/ProductServiceTest.javasrc/test/java/com/almang/inventory/vendor/controller/VendorControllerTest.javasrc/main/java/com/almang/inventory/product/service/ProductService.javasrc/test/java/com/almang/inventory/product/controller/ProductControllerTest.javasrc/main/java/com/almang/inventory/product/controller/ProductController.javasrc/main/java/com/almang/inventory/vendor/controller/VendorController.javasrc/main/java/com/almang/inventory/product/repository/ProductRepository.java
📚 Learning: 2025-11-22T18:12:13.172Z
Learnt from: JoonKyoLee
Repo: almang2/inventory-server PR: 66
File: src/main/java/com/almang/inventory/order/domain/OrderItem.java:41-53
Timestamp: 2025-11-22T18:12:13.172Z
Learning: In the almang2/inventory-server repository, OrderItem entity update methods (updateQuantity, updatePrice in src/main/java/com/almang/inventory/order/domain/OrderItem.java) do not require null checks because OrderService will validate parameters before calling these update methods, following the same pattern as Product entity updates.
<!--
Applied to files:
src/test/java/com/almang/inventory/product/service/ProductServiceTest.javasrc/test/java/com/almang/inventory/vendor/controller/VendorControllerTest.javasrc/main/java/com/almang/inventory/product/service/ProductService.javasrc/test/java/com/almang/inventory/product/controller/ProductControllerTest.javasrc/main/java/com/almang/inventory/vendor/controller/VendorController.java
🔇 Additional comments (14)
src/test/java/com/almang/inventory/product/service/ProductServiceTest.java (4)
1252-1325: 테스트 커버리지가 잘 구성되어 있습니다! 👍발주처별 품목 목록 조회 테스트가 다음을 잘 검증하고 있습니다:
- 두 개의 발주처(vendor1, vendor2)를 생성하여 격리 테스트
vendorId와name필드 검증을 통한 정확한 데이터 매핑 확인
1327-1353: 예외 케이스 테스트가 깔끔합니다.존재하지 않는 발주처와 다른 상점의 발주처 접근에 대한 예외 처리 테스트가 적절히 구현되어 있습니다.
VENDOR_NOT_FOUND와VENDOR_ACCESS_DENIED에러 코드를 검증하는 방식이 일관성 있습니다.
1355-1390: 품목 → 발주처 조회 테스트 LGTM!VendorResponse의 주요 필드들(
vendorId,name,channel,storeId,activated)을 모두 검증하고 있어 데이터 무결성 확인이 충분합니다.
1392-1446: 접근 제어 테스트가 완벽합니다.존재하지 않는 품목(
PRODUCT_NOT_FOUND)과 다른 상점 품목 접근(STORE_ACCESS_DENIED)에 대한 예외 처리가 잘 검증되어 있습니다. 멀티테넌트 환경에서 중요한 보안 테스트입니다.src/test/java/com/almang/inventory/product/controller/ProductControllerTest.java (2)
616-650: 컨트롤러 테스트가 체계적으로 구성되어 있습니다.성공 케이스에서 HTTP 상태, 메시지, 응답 데이터 필드(
vendorId,name,channel,phoneNumber,storeId,activated)를 모두 검증하고 있어 API 스펙 확인이 충분합니다.
652-686: 에러 핸들링 테스트가 일관성 있게 작성되었습니다.
PRODUCT_NOT_FOUND(404)와STORE_ACCESS_DENIED(403) 케이스가 기존 패턴과 동일하게 테스트되어 있습니다.$.data가 존재하지 않음을 검증하는 것도 좋은 관행입니다.src/main/java/com/almang/inventory/global/api/SuccessMessage.java (1)
39-40: 성공 메시지 상수가 일관성 있게 추가되었습니다.기존 네이밍 컨벤션(
GET_*_SUCCESS)을 잘 따르고 있으며, VENDOR 섹션에 적절히 배치되었습니다.src/test/java/com/almang/inventory/vendor/controller/VendorControllerTest.java (1)
751-785: 에러 시나리오 테스트가 완벽합니다.
VENDOR_NOT_FOUND와VENDOR_ACCESS_DENIED예외 처리가 기존 패턴과 일관성 있게 테스트되어 있습니다.src/main/java/com/almang/inventory/product/controller/ProductController.java (1)
119-133: 새 엔드포인트가 기존 패턴과 일관성 있게 구현되었습니다! 🎉
- 로깅에
userId와productId컨텍스트 포함- Swagger
@Operation어노테이션 적용ApiResponse<VendorResponse>반환 타입이 명확RESTful 리소스 설계 관점에서
/product/{productId}/vendor경로는 "품목의 발주처"라는 관계를 잘 표현하고 있습니다.src/main/java/com/almang/inventory/vendor/controller/VendorController.java (2)
8-9: 의존성 주입이 깔끔합니다! 👍
VendorController가ProductService를 의존하는 크로스 도메인 의존성이지만, RESTful 관점에서 vendor의 하위 리소스(products)를 조회하는 엔드포인트이므로 적절한 설계 선택입니다. 생성자 주입을 통해 불변성도 보장되고 있네요.Also applies to: 42-42
157-170: 엔드포인트 구현이 일관성 있게 잘 되어 있습니다!기존 패턴을 정확히 따르고 있으며, 인증/로깅/응답 래핑이 모두 적절합니다. RESTful 설계 원칙에 따라
/vendor/{vendorId}/products로 하위 리소스를 표현한 점도 좋습니다.참고: REST API 설계에서 리소스 간 관계를 표현할 때는 이렇게 경로를 중첩시키는 것이 표준 관례입니다. (RFC 3986 참고)
src/main/java/com/almang/inventory/product/service/ProductService.java (3)
122-135: 보안 검증 플로우가 훌륭합니다!발주처 접근 권한을 먼저 검증한 후 데이터를 조회하는 순서가 정확합니다. 이렇게 하면 권한이 없는 사용자가 쿼리 실행 전에 차단되어 불필요한 DB 부하를 방지할 수 있습니다.
추가 포인트:
@Transactional(readOnly = true)는 읽기 전용 힌트를 제공하여 성능 최적화에 도움을 줍니다- Stream API를 활용한 매핑이 깔끔하고 가독성이 좋네요
137-149: 깔끔한 구현입니다!품목 검증 → Store 접근 권한 검증 → 발주처 반환의 흐름이 논리적이고 명확합니다.
product.getVendor()에서 null 체크가 없는 것은 DB 스키마의nullable=false제약 조건 덕분에 안전하며, 기존 학습 내용과도 일치합니다.
194-201: 코드 중복을 제거한 좋은 리팩토링입니다!기존의
findVendorByIdAndValidateAccess(Long, User)메서드와 유사한 로직을Store를 직접 받는 형태로 추출하여getProductsByVendor에서 재사용할 수 있게 만들었네요. 단일 책임 원칙(SRP)을 잘 따르고 있습니다.참고: 이런 식으로 검증 로직을 분리하면 테스트 작성도 더 쉬워지고, 향후 검증 규칙 변경 시 한 곳만 수정하면 됩니다.
✨ 작업 내용
📝 적용 범위
/vendor/product📌 참고 사항
Summary by CodeRabbit
새로운 기능
테스트
✏️ Tip: You can customize this high-level summary in your review settings.