-
Notifications
You must be signed in to change notification settings - Fork 0
[REFACTOR] 장바구니 조회 기능 개선 #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Caution Review failedThe pull request is closed. Walkthrough프로젝트 전반에서 식별자 타입을 String → Long으로 일괄 변환하고, JSON 출력은 Long을 문자열로 직렬화하도록 커스텀 Serializer를 도입했습니다. 장바구니 도메인은 엔티티 연관을 제거하고 FK ID(primitive)로 전환했으며, CartFacade가 UserService 및 ProductQueryService를 통해 검증/조회 후 상품 정보와 CartItem을 번들링해 반환합니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Client
participant Controller as CartController
participant Facade as CartFacade
participant UserSvc as UserService
participant CartSvc as CartService
participant ProdQuery as ProductQueryService
rect rgb(245,248,255)
note over Client,Facade: 조회: GET /cart
Client->>Controller: getCart(userId)
Controller->>Facade: getCartItems(userId)
Facade->>UserSvc: getUserById(userId)
UserSvc-->>Facade: User
Facade->>CartSvc: findByUserId(userId)
CartSvc-->>Facade: List<CartItem>
Facade->>ProdQuery: getProductsByIds(productIds)
ProdQuery-->>Facade: List<Product>
Facade-->>Controller: List<CartItemBundle>
Controller-->>Client: CartResponse
end
rect rgb(245,255,245)
note over Client,CartSvc: 추가: POST /cart/items
Client->>Controller: addItem(userId, productId, qty)
Controller->>Facade: addItem(userId, request)
Facade->>UserSvc: getUserById(userId)
UserSvc-->>Facade: User
Facade->>ProdQuery: getProduct(productId)
ProdQuery-->>Facade: Product
Facade->>CartSvc: addItem(userId, productId, qty)
alt 신규 행 삽입
CartSvc-->>Facade: CartItem
else 중복 무결성 충돌
CartSvc-->>Facade: CartItem(수량 증가)
end
Facade-->>Controller: CartItem
Controller-->>Client: 200 OK
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Possibly related PRs
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. 📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (8)
✨ Finishing Touches
🧪 Generate unit tests
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/main/kotlin/com/dh/baro/cart/presentation/swagger/CartSwagger.kt (1)
33-49: Swagger 예시 JSON의 ID 값을 문자열로 변경
CartItemResponse DTO에 @JsonSerialize(using = LongToStringSerializer::class)가 적용되어 ID가 문자열로 직렬화되므로, CartSwagger.kt의 examples 섹션(itemId/productId)에 쌍따옴표를 추가하세요.
– 응답 예시:"itemId": "101","productId": "11"
– 요청 예시(상품 추가):"productId": "11"src/main/kotlin/com/dh/baro/look/presentation/swagger/LookSwagger.kt (2)
27-31: 문서 오타 수정 필요(사용자 노출).
- "productIds는 최초애 등록된…" → "최초에"
- "reationType" → "reactionType"
다음 패치를 제안합니다:
- productIds는 최초애 등록된 순서를 유지합니다. + productIds는 최초에 등록된 순서를 유지합니다. ... - reationType = [LIKE, DISLIKE] + reactionType = [LIKE, DISLIKE]Also applies to: 69-70
180-199: 예시 응답이 실제 DTO와 불일치.LookDetailResponse는 images 대신 lookImageUrls(List), 제품 항목은 name→productName, storeName 포함, displayOrder 미노출입니다. 문서-구현 불일치를 반드시 정정하세요.
예시 교체 패치:
- { - "lookId": 1001, - "title": "여름 바캉스룩", - "description": "린넨 셔츠 + 쇼츠 + 로퍼", - "thumbnailUrl": "https://example.com/look-thumb.jpg", - "likesCount": 42, - "images": [ - { "imageUrl": "https://example.com/look-1.jpg", "displayOrder": 1 }, - { "imageUrl": "https://example.com/look-2.jpg", "displayOrder": 2 } - ], - "products": [ - { "productId": 101, "name": "린넨 셔츠", "price": 39000, "thumbnailUrl": "https://...", "displayOrder": 1 }, - { "productId": 102, "name": "베이지 쇼츠", "price": 29000, "thumbnailUrl": "https://...", "displayOrder": 2 } - ] - } + { + "lookId": 1001, + "title": "여름 바캉스룩", + "description": "린넨 셔츠 + 쇼츠 + 로퍼", + "thumbnailUrl": "https://example.com/look-thumb.jpg", + "likesCount": 42, + "lookImageUrls": [ + "https://example.com/look-1.jpg", + "https://example.com/look-2.jpg" + ], + "products": [ + { "productId": 101, "storeName": "A스토어", "productName": "린넨 셔츠", "price": 39000, "thumbnailUrl": "https://..." }, + { "productId": 102, "storeName": "B스토어", "productName": "베이지 쇼츠", "price": 29000, "thumbnailUrl": "https://..." } + ] + }src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt (1)
112-119: Swagger 예시 JSON의 ID 필드를 문자열로 변경
ProductListItem.id와PopularCursor.id에@JsonSerialize(using = LongToStringSerializer::class)이 적용돼 실제 응답 시 문자열로 직렬화되므로, Swagger 예시 블록에서 아래와 같이"id": 21→"id": "21"및"nextCursor": { "id": 21, ... }→"nextCursor": { "id": "21", ... }로 수정하세요.- { "id": 21, "storeName": "무신사", "productName": "Sneakers", "price": 99000, "thumbnailUrl": "..." } + { "id": "21", "storeName": "무신사", "productName": "Sneakers", "price": 99000, "thumbnailUrl": "..." } ... - "nextCursor": { "id": 21, "likes": 500 } + "nextCursor": { "id": "21", "likes": 500 }
🧹 Nitpick comments (33)
src/main/kotlin/com/dh/baro/core/StringListToLongListDeserializer.kt (2)
3-8: 불필요한 TypeFactory 의존 제거(또는 ctxt.typeFactory 사용)위 개선안을 적용하면
CollectionType,TypeFactoryimport가 불필요합니다. 만약 현재 방식을 유지한다면TypeFactory.defaultInstance()대신ctxt.typeFactory를 사용해 ObjectMapper 레벨 설정과 일관되게 가는 것을 권장합니다.불필요 import 제거(diff):
import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.type.CollectionType -import com.fasterxml.jackson.databind.type.TypeFactory +import com.fasterxml.jackson.core.JsonToken
9-15: 역직렬화 단위 테스트 커버리지 제안다음 케이스에 대한 단위 테스트 추가를 권장합니다: ["1","2"], [1,2,3], [1,"2",3], ["", "x"], null 포함 요소. 실패 케이스는 메시지에 필드 경로가 포함되는지 검증해 주세요.
원하시면 JUnit5+Jackson(ObjectMapper) 기반 테스트 스켈레톤을 생성해 드립니다.
src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateRequest.kt (1)
22-24: 컬렉션 전체 deserializer 대신 contentUsing으로 단순화(선택)프로젝트에 단건용
StringToLongDeserializer가 이미 있다면, 리스트 전체용 커스텀 클래스 없이 요소 단위 변환으로 일관성을 높일 수 있습니다.대안(diff):
-import com.dh.baro.core.StringListToLongListDeserializer +import com.dh.baro.core.StringToLongDeserializer @@ - @JsonDeserialize(using = StringListToLongListDeserializer::class) + @JsonDeserialize(contentUsing = StringToLongDeserializer::class)참고: 이 방식은 [ "1", 2, "3" ]처럼 숫자/문자열 혼합 입력도 단일 로직으로 수용 가능합니다(해당 deserializer가 숫자 토큰도 허용하도록 구현되어 있다는 가정).
src/main/kotlin/com/dh/baro/core/Cursor.kt (2)
6-7: 출력만이 아니라 입력 대칭성도 고려: @JsonDeserialize 추가 제안(선택)Cursor가 요청 본문/캐시 역직렬화에 사용될 여지가 있다면, 문자열 ID 입력을 Long으로 강제하는 역직렬화도 대칭으로 제공하는 것이 안전합니다.
예시(diff):
import com.fasterxml.jackson.databind.annotation.JsonSerialize +import com.fasterxml.jackson.databind.annotation.JsonDeserialize @@ class Cursor ( - @JsonSerialize(using = LongToStringSerializer::class) + @JsonSerialize(using = LongToStringSerializer::class) + @JsonDeserialize(using = StringToLongDeserializer::class) val id: Long, )
5-8: 값 객체 특성 강화: data class 전환(선택)동등성/해시코드/디스트럭처링 지원을 위해
data class로의 전환을 고려해볼 만합니다. 외부 API 시그니처에 영향이 거의 없습니다.-class Cursor ( +data class Cursor (src/main/kotlin/com/dh/baro/cart/application/CartItemBundle.kt (1)
6-9: 데이터 무결성 보강 + 패키지 일관성 제안
- cartItem.productId와 product.id의 일치 여부를 런타임에 보증하면 디버깅 비용을 줄일 수 있습니다.
- 다른 Bundle들이 application.dto 하위에 위치하는 패턴과 경로를 맞추는 것도 고려해 주세요.
적용 예시:
data class CartItemBundle( val cartItem: CartItem, val product: Product, ) +{ + init { + require(cartItem.productId == product.id) { + "CartItem.productId(${cartItem.productId}) != Product.id(${product.id})" + } + } +}src/main/kotlin/com/dh/baro/order/application/OrderFacade.kt (2)
29-34: 중복 productId 제거로 조회 비용 절감동일 상품이 여러 번 주문되는 경우가 있어도, 존재 검증과 조회는 distinct()로 중복을 제거해도 기능적으로 동일합니다(수량 병합은 도메인 서비스에서 수행).
fun placeOrder(userId: Long, request: OrderCreateRequest): Order { userService.checkUserExists(userId) - val productList = productQueryService.getProductsExists(request.orderItems.map { orderItem -> orderItem.productId }) + val productIds = request.orderItems.map { it.productId }.distinct() + val productList = productQueryService.getProductsExists(productIds) val cmd = OrderCreateCommand.toCommand(userId, productList, request) val order = orderService.createOrder(cmd) return order }@Transactional fun placeOrderV2(userId: Long, request: OrderCreateRequest): Order { userService.checkUserExists(userId) - val productList = productQueryService.getProductsExists( - request.orderItems.map { orderItem -> orderItem.productId }, - ) + val productIds = request.orderItems.map { it.productId }.distinct() + val productList = productQueryService.getProductsExists(productIds)Also applies to: 41-45
73-88: 재고 차감 이벤트는 커밋 이후 발행 권장이벤트 리스너가 DB 상태를 읽는다면, @TransactionalEventListener(phase = AFTER_COMMIT) 사용을 전제로 커밋 이후에 처리되도록 구성하는 것을 권장합니다. 현재 메서드는 @transactional 내에서 publish하고 있으니, 리스너 측 설정만으로 정합성 개선이 가능합니다.
src/main/kotlin/com/dh/baro/look/presentation/dto/LookDto.kt (1)
7-12: 필드 명명 일관성(lookId vs id) 재검토다른 DTO는 id 또는 orderId 등을 사용합니다. API 전반의 명명 규칙을 정해 일관성 확보를 고려해 주세요(대규모 변경이므로 추후 배치 권장).
src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryCreateRequest.kt (1)
9-15: ID/이름 검증 강화 제안data class CategoryCreateRequest( @JsonDeserialize(using = StringToLongDeserializer::class) - @field:NotNull - val id: Long, + @field:NotNull + @field:jakarta.validation.constraints.Positive + val id: Long, - @field:Size(min = 1, max = 50) - val name: String, + @field:jakarta.validation.constraints.NotBlank + @field:Size(min = 1, max = 50) + val name: String, )참고: Kotlin non-null 타입이라 @NotNull은 런타임 검증/스키마 용도로만 남습니다(유지 여부는 팀 규칙에 따르세요).
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductListItem.kt (1)
18-27: 네이밍 사소 제안: ofOrNull → fromOrNull다른 DTO의
from(...)컨벤션과 맞추면 검색/일관성 측면에서 이점이 있습니다. (선택)src/main/kotlin/com/dh/baro/core/LongToStringSerializer.kt (1)
7-15: 대안 제안: 표준 ToStringSerializer 사용 고려(선택)Jackson의
com.fasterxml.jackson.databind.ser.std.ToStringSerializer를 활용하면 커스텀 클래스를 줄일 수 있습니다. 대규모 교체는 아니므로 추후 리팩터 단계에서 검토해 주세요.src/main/kotlin/com/dh/baro/cart/presentation/dto/CartResponse.kt (1)
19-21: 반올림 위치 검토(선택)아이템 단계에서 소계 스케일을 확정하고 최종 합계에선 스케일 변경이 없도록 하는 것도 한 방법입니다(누적 반올림 오차를 더 명확히 관리).
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt (1)
14-17: storeId 유효성 강화(선택)ID가 양수만 허용된다면
@field:Positive를 추가해 주세요.@JsonDeserialize(using = StringToLongDeserializer::class) @field:NotNull + @field:Positive @Schema(type = "string", example = "11") val storeId: Long,src/main/kotlin/com/dh/baro/cart/presentation/CartController.kt (1)
40-41: 경로 변수 ID 유효성 검증(@positive) 추가 제안음수/0 등의 비정상 ID를 사전에 차단하려면
@Positive를 붙이는 편이 안전합니다. 메서드 파라미터 제약을 활성화하려면 컨트롤러에@Validated도 추가해 주세요.적용 예(diff):
@@ -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.* +import jakarta.validation.constraints.Positive +import org.springframework.validation.annotation.Validated @@ -@CheckAuth(UserRole.BUYER) +@CheckAuth(UserRole.BUYER) +@Validated class CartController( @@ - override fun updateQuantity( + override fun updateQuantity( @CurrentUser userId: Long, - @PathVariable itemId: Long, + @PathVariable @Positive itemId: Long, @Valid @RequestBody request: UpdateQuantityRequest, ) = cartFacade.updateQuantity(userId, itemId, request.quantity) @@ - override fun removeItem( + override fun removeItem( @CurrentUser userId: Long, - @PathVariable itemId: Long, + @PathVariable @Positive itemId: Long, ) = cartFacade.removeItem(userId, itemId)Also applies to: 48-49
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateResponse.kt (1)
8-9: Long → String 직렬화 적용 좋습니다. Kotlin use-site/스키마 힌트 소소 제안Kotlin에서는 명시적 use-site를 써 주면(직렬화의 경우 보통
@get:) 도구 호환성이 좋아집니다. 또한 Swagger 문서에서 타입을 문자열로 노출하려면@Schema(type = "string")를 추가해 두면 혼선을 줄일 수 있습니다.예시(diff):
-import com.fasterxml.jackson.databind.annotation.JsonSerialize +import com.fasterxml.jackson.databind.annotation.JsonSerialize +import io.swagger.v3.oas.annotations.media.Schema @@ - @JsonSerialize(using = LongToStringSerializer::class) + @get:JsonSerialize(using = LongToStringSerializer::class) + @Schema(type = "string") val id: Long,src/main/kotlin/com/dh/baro/order/presentation/dto/OrderCreateRequest.kt (1)
22-24: Kotlin use-site(target) 명시 및 검증 강화 제안생성자 파라미터 기반 역직렬화를 확실히 적용하려면
@param:(+호환을 위해@field:도) use-site를 권장합니다. 또한 ID에@Positive를 추가하면 음수 ID를 차단할 수 있습니다. Swagger 문서엔 문자열 ID로 보이도록@Schema(type = "string")를 권장합니다.예시(diff):
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import io.swagger.v3.oas.annotations.media.Schema @@ - @JsonDeserialize(using = StringToLongDeserializer::class) - @field:NotNull(message = "상품 ID를 입력해주세요.") - val productId: Long, + @param:JsonDeserialize(using = StringToLongDeserializer::class) + @field:JsonDeserialize(using = StringToLongDeserializer::class) + @field:NotNull(message = "상품 ID를 입력해주세요.") + @field:Positive(message = "상품 ID는 양수여야 합니다.") + @Schema(type = "string") + val productId: Long,src/main/kotlin/com/dh/baro/cart/presentation/dto/CartItemResponse.kt (3)
28-38: 게터 대신 프로퍼티 직접 접근 및 중복 호출 제거.
Product가 불변val이라면 프로퍼티 직접 접근이 선호됩니다(학습된 선호도 반영). 또한price를 변수로 담아 중복 호출을 제거하세요.fun from(bundle: CartItemBundle): CartItemResponse { val cartItem = bundle.cartItem val product = bundle.product + val price = product.price return CartItemResponse( itemId = cartItem.id, productId = product.id, - productName = product.getName(), - productThumbnailUrl = product.getThumbnailUrl(), - price = product.getPrice(), + productName = product.name, + productThumbnailUrl = product.thumbnailUrl, + price = price, quantity = cartItem.quantity, - subtotal = product.getPrice() - .multiply(BigDecimal(cartItem.quantity)) + subtotal = price.multiply(BigDecimal(cartItem.quantity)) .setScale(SCALE_NONE, RoundingMode.HALF_UP) ) }
10-13: 응답 스키마 일관성(OpenAPI): ID는 문자열 직렬화이므로 스키마도 string으로 명시.
@JsonSerialize만으로는 스키마 타입이 integer로 남습니다.@Schema(type="string")를 추가해 문서/실제 응답을 일치시켜 주세요.-import com.fasterxml.jackson.databind.annotation.JsonSerialize +import com.fasterxml.jackson.databind.annotation.JsonSerialize +import io.swagger.v3.oas.annotations.media.Schema @@ - @JsonSerialize(using = LongToStringSerializer::class) + @JsonSerialize(using = LongToStringSerializer::class) + @Schema(type = "string", example = "10001") val itemId: Long, - @JsonSerialize(using = LongToStringSerializer::class) + @JsonSerialize(using = LongToStringSerializer::class) + @Schema(type = "string", example = "11") val productId: Long,
35-37: 반올림 책임 위치 재검토(도메인 vs. 프리젠터).원화(0 스케일) 보장을 도메인/DB(precision/scale)에서 이미 하고 있다면 여기의
setScale(0, HALF_UP)은 중복일 수 있습니다. 제거하거나 도메인 계층으로 이동 검토를 권장합니다.src/main/kotlin/com/dh/baro/order/presentation/swagger/OrderSwagger.kt (2)
59-83: 예시 JSON의 ID 타입을 문자열로 수정 필요.응답 DTO는
@JsonSerialize(Long→String)이므로 예시도 문자열이어야 합니다.- { - "orderId": 1001, + { + "orderId": "1001", @@ - { - "productId": 11, + { + "productId": "11", @@ - { - "productId": 12, + { + "productId": "12",
176-195: 목록/커서 예시의 ID도 문자열로 통일.- { - "orderId": 1003, + { + "orderId": "1003", @@ - { - "orderId": 1002, + { + "orderId": "1002", @@ - "nextCursor": { "id": 1002 } + "nextCursor": { "id": "1002" }src/main/kotlin/com/dh/baro/order/presentation/dto/OrderDetailResponse.kt (2)
11-13: OpenAPI 스키마 타입을 string으로 명시.문서/응답 일치성을 위해
@Schema(type="string")추가를 권장합니다.- @JsonSerialize(using = LongToStringSerializer::class) + @JsonSerialize(using = LongToStringSerializer::class) + @io.swagger.v3.oas.annotations.media.Schema(type = "string", example = "1001") val orderId: Long,추가로 파일 상단에 다음 import를 권장합니다(프로젝트 스타일에 맞게 정리):
import io.swagger.v3.oas.annotations.media.Schema
21-23: 아이템의 productId도 스키마 string으로.- @JsonSerialize(using = LongToStringSerializer::class) + @JsonSerialize(using = LongToStringSerializer::class) + @io.swagger.v3.oas.annotations.media.Schema(type = "string", example = "11") val productId: Long,src/main/kotlin/com/dh/baro/cart/domain/CartItem.kt (3)
17-21: userId/productId는 변경 불가 컬럼으로 고정하세요.생성 후 변경될 이유가 없으므로 DB 차원에서 업데이트를 금지하면 무결성이 강화됩니다.
다음 패치를 제안합니다:
- @Column(name = "user_id", nullable = false) + @Column(name = "user_id", nullable = false, updatable = false) val userId: Long, - @Column(name = "product_id", nullable = false) + @Column(name = "product_id", nullable = false, updatable = false) val productId: Long,
29-35: 수량 변경 메서드에 방어 로직 추가 권장.다른 호출 경로에서 음수/0이 들어오면 도메인 불변식이 깨질 수 있습니다(요청 DTO에서 1 이상 검증하더라도).
- fun addQuantity(quantity: Int) { - this.quantity += quantity - } + fun addQuantity(quantity: Int) { + require(quantity > 0) { "quantity must be > 0" } + this.quantity += quantity + } - fun changeQuantity(quantity: Int) { - this.quantity = quantity - } + fun changeQuantity(quantity: Int) { + require(quantity >= 1) { "quantity must be >= 1" } + this.quantity = quantity + }
38-45: 팩토리에서 최소 수량 보장.도메인 객체 생성 시점에 하한(>=1) 보장을 추가하면 일관성이 높아집니다.
- fun newCartItem(userId: Long, productId: Long, quantity: Int): CartItem { - return CartItem( + fun newCartItem(userId: Long, productId: Long, quantity: Int): CartItem { + require(quantity >= 1) { "quantity must be >= 1" } + return CartItem( id = IdGenerator.generate(), userId = userId, productId = productId, quantity = quantity, ) }src/main/kotlin/com/dh/baro/order/presentation/OrderController.kt (1)
55-63: size 파라미터에 범위 검증 추가 제안.과도한 페이지 크기 요청 방지를 위해 최소/최대 제약을 권장합니다(예: 1~100).
- @RequestParam(required = false) cursorId: Long?, - @RequestParam(defaultValue = "10") size: Int, + @RequestParam(required = false) cursorId: Long?, + @RequestParam(defaultValue = "10") @Min(1) @Max(100) size: Int,추가 import:
import jakarta.validation.constraints.Max import jakarta.validation.constraints.Minsrc/main/kotlin/com/dh/baro/cart/presentation/dto/AddItemRequest.kt (1)
9-13: @NotNull 메시지가 실제로 노출되지 않을 수 있음.Kotlin 비널러블(Long) 필드가 누락되면 바인딩 단계에서 예외가 나 Validation 메시지까지 도달하지 않을 수 있습니다. 커스텀 메시지를 보장하려면
val productId: Long?+@NotNull로 바꾸는 방식을 고려하세요(연쇄 수정 영향 검토 필요).또한
StringToLongDeserializer가 숫자/문자열 양쪽 입력을 모두 수용하는지(공백/널 처리 포함) 확인 부탁드립니다.커스텀 역직렬화 도입으로 컨트롤러 단 toLong 제거는 잘 되었습니다.
src/main/kotlin/com/dh/baro/cart/application/CartFacade.kt (1)
22-24: 상품 조회 시 중복 ID 제거로 쿼리 최적화.같은 상품이 여러 카트아이템에 존재하면 불필요한 조회가 반복될 수 있습니다.
- val productIds = cartItems.map { it.productId } - val products = productQueryService.getAllByIds(productIds).associateBy { it.id } + val productIds = cartItems.map { it.productId }.distinct() + val products = productQueryService.getAllByIds(productIds).associateBy { it.id }src/main/kotlin/com/dh/baro/look/presentation/LookController.kt (1)
52-60: cursorId/size에 Bean Validation 추가 제안잘 동작하나, 유효성 한계를 명시하면 방어적입니다.
적용 예시(선택):
- @RequestParam(required = false) cursorId: Long?, - @RequestParam(defaultValue = "10") size: Int, + @RequestParam(required = false) @jakarta.validation.constraints.Positive cursorId: Long?, + @RequestParam(defaultValue = "10") + @jakarta.validation.constraints.Min(1) + @jakarta.validation.constraints.Max(50) + size: Int,또는 상단 import 추가 후 간단히 표기:
import jakarta.validation.constraints.Max import jakarta.validation.constraints.Min import jakarta.validation.constraints.Positivesrc/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt (1)
155-160: 최신 상품 API: Long 전환 OK + nextCursor 예시 타입 확인파라미터 Long 전환은 적절합니다. nextCursor.id의 직렬화 형태(숫자 vs 문자열)가 실제 런타임과 동일한지 함께 점검해 주세요.
설정이 문자열 직렬화라면 아래처럼 조정:- "nextCursor": { "id": 11 } + "nextCursor": { "id": "11" }검증 스크립트는 위 코멘트와 동일합니다.
src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt (1)
38-41: size/cursor 유효성 검증 애너테이션 제안런타임 방어 강화를 위해 컨트롤러 수준에서 경계값을 제한하는 것이 안전합니다.
적용 예시:
- @RequestParam(required = false) cursorId: Long?, - @RequestParam(required = false) cursorLikes: Int?, - @RequestParam(defaultValue = DEFAULT_PAGE_SIZE) size: Int, + @RequestParam(required = false) @jakarta.validation.constraints.Positive cursorId: Long?, + @RequestParam(required = false) cursorLikes: Int?, + @RequestParam(defaultValue = DEFAULT_PAGE_SIZE) + @jakarta.validation.constraints.Min(1) + @jakarta.validation.constraints.Max(50) + size: Int,필요 시 상단 import:
import jakarta.validation.constraints.Max import jakarta.validation.constraints.Min import jakarta.validation.constraints.Positive
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (38)
src/main/kotlin/com/dh/baro/cart/application/CartFacade.kt(1 hunks)src/main/kotlin/com/dh/baro/cart/application/CartItemBundle.kt(1 hunks)src/main/kotlin/com/dh/baro/cart/domain/CartItem.kt(2 hunks)src/main/kotlin/com/dh/baro/cart/domain/CartItemRepository.kt(0 hunks)src/main/kotlin/com/dh/baro/cart/domain/CartService.kt(1 hunks)src/main/kotlin/com/dh/baro/cart/presentation/CartController.kt(2 hunks)src/main/kotlin/com/dh/baro/cart/presentation/dto/AddItemRequest.kt(1 hunks)src/main/kotlin/com/dh/baro/cart/presentation/dto/CartItemResponse.kt(1 hunks)src/main/kotlin/com/dh/baro/cart/presentation/dto/CartResponse.kt(2 hunks)src/main/kotlin/com/dh/baro/cart/presentation/swagger/CartSwagger.kt(2 hunks)src/main/kotlin/com/dh/baro/core/Cursor.kt(1 hunks)src/main/kotlin/com/dh/baro/core/LongToStringSerializer.kt(1 hunks)src/main/kotlin/com/dh/baro/core/StringListToLongListDeserializer.kt(1 hunks)src/main/kotlin/com/dh/baro/core/StringToLongDeserializer.kt(1 hunks)src/main/kotlin/com/dh/baro/identity/presentation/dto/UserProfileResponse.kt(2 hunks)src/main/kotlin/com/dh/baro/look/presentation/LookController.kt(1 hunks)src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateRequest.kt(2 hunks)src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateResponse.kt(1 hunks)src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt(4 hunks)src/main/kotlin/com/dh/baro/look/presentation/dto/LookDto.kt(1 hunks)src/main/kotlin/com/dh/baro/look/presentation/swagger/LookSwagger.kt(4 hunks)src/main/kotlin/com/dh/baro/order/application/OrderCreateCommand.kt(1 hunks)src/main/kotlin/com/dh/baro/order/application/OrderFacade.kt(2 hunks)src/main/kotlin/com/dh/baro/order/presentation/OrderController.kt(1 hunks)src/main/kotlin/com/dh/baro/order/presentation/dto/OrderCreateRequest.kt(2 hunks)src/main/kotlin/com/dh/baro/order/presentation/dto/OrderDetailResponse.kt(3 hunks)src/main/kotlin/com/dh/baro/order/presentation/dto/OrderSummary.kt(1 hunks)src/main/kotlin/com/dh/baro/order/presentation/swagger/OrderSwagger.kt(2 hunks)src/main/kotlin/com/dh/baro/product/application/CategoryFacade.kt(1 hunks)src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt(1 hunks)src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryCreateRequest.kt(1 hunks)src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryResponse.kt(1 hunks)src/main/kotlin/com/dh/baro/product/presentation/dto/PopularCursor.kt(1 hunks)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt(2 hunks)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateResponse.kt(1 hunks)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductDetail.kt(2 hunks)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductListItem.kt(2 hunks)src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt(3 hunks)
💤 Files with no reviewable changes (1)
- src/main/kotlin/com/dh/baro/cart/domain/CartItemRepository.kt
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: codrin2
PR: S-BARO/server#17
File: src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt:38-59
Timestamp: 2025-08-12T13:52:29.449Z
Learning: codrin2 prefers direct field access for immutable id properties (val) rather than using getters, reasoning that immutable fields pose less encapsulation risk.
📚 Learning: 2025-08-01T08:00:47.771Z
Learnt from: codrin2
PR: S-BARO/server#8
File: src/main/kotlin/com/dh/baro/cart/domain/CartItem.kt:31-37
Timestamp: 2025-08-01T08:00:47.771Z
Learning: CartItem 엔티티에서 수량 관련 validation은 request DTO 레벨에서 처리하는 것을 선호함. AddItemRequest와 UpdateQuantityRequest에서 Min(1) 어노테이션을 사용하여 수량 검증을 수행.
Applied to files:
src/main/kotlin/com/dh/baro/cart/presentation/dto/AddItemRequest.kt
🧬 Code graph analysis (33)
src/main/kotlin/com/dh/baro/cart/application/CartItemBundle.kt (3)
src/main/kotlin/com/dh/baro/product/application/dto/ProductDetailBundle.kt (1)
product(6-9)src/main/kotlin/com/dh/baro/product/application/dto/ProductSliceBundle.kt (1)
productSlice(7-10)src/main/kotlin/com/dh/baro/look/application/dto/LookDetailBundle.kt (1)
look(7-12)
src/main/kotlin/com/dh/baro/cart/presentation/dto/AddItemRequest.kt (2)
src/main/kotlin/com/dh/baro/product/domain/InventoryItem.kt (1)
productId(3-6)src/main/kotlin/com/dh/baro/cart/presentation/dto/UpdateQuantityRequest.kt (1)
value(5-8)
src/main/kotlin/com/dh/baro/core/LongToStringSerializer.kt (2)
src/main/kotlin/com/dh/baro/core/event/JacksonEventSerializer.kt (2)
objectMapper(7-19)serialize(12-18)src/main/kotlin/com/dh/baro/core/event/EventSerializer.kt (2)
serialize(3-5)serialize(4-4)
src/main/kotlin/com/dh/baro/order/application/OrderFacade.kt (4)
src/main/kotlin/com/dh/baro/order/domain/OrderService.kt (1)
productRepository(10-57)src/test/kotlin/com/dh/baro/order/domain/OrderServiceTest.kt (1)
cmd(131-147)src/main/kotlin/com/dh/baro/order/domain/Order.kt (1)
name(10-65)src/main/kotlin/com/dh/baro/order/domain/service/OrderServiceV2.kt (3)
orderRepository(10-34)orderItem(19-29)createOrderV2(15-33)
src/main/kotlin/com/dh/baro/order/presentation/OrderController.kt (3)
src/test/kotlin/com/dh/baro/order/domain/OrderQueryServiceTest.kt (1)
classes(24-180)src/main/kotlin/com/dh/baro/order/domain/OrderQueryService.kt (1)
readOnly(9-32)src/main/kotlin/com/dh/baro/order/domain/OrderRepository.kt (1)
findByUserIdAndCursorId(10-27)
src/main/kotlin/com/dh/baro/order/application/OrderCreateCommand.kt (1)
src/main/kotlin/com/dh/baro/order/domain/OrderService.kt (3)
it(31-31)productRepository(10-57)_(32-36)
src/main/kotlin/com/dh/baro/core/StringListToLongListDeserializer.kt (1)
src/main/kotlin/com/dh/baro/core/event/JacksonEventSerializer.kt (1)
objectMapper(7-19)
src/main/kotlin/com/dh/baro/order/presentation/dto/OrderSummary.kt (2)
src/main/kotlin/com/dh/baro/order/domain/Order.kt (2)
name(10-65)getId(40-40)src/main/kotlin/com/dh/baro/product/infra/event/InventoryDeductionRequestedEvent.kt (1)
orderId(5-9)
src/main/kotlin/com/dh/baro/product/application/CategoryFacade.kt (4)
src/main/kotlin/com/dh/baro/product/presentation/CategoryController.kt (2)
categoryFacade(13-26)HttpStatus(19-25)src/main/kotlin/com/dh/baro/product/presentation/swagger/CategorySwagger.kt (1)
summary(24-67)src/test/kotlin/com/dh/baro/product/domain/CategoryServiceTest.kt (3)
categoryService(44-46)classes(19-78)categoryService(35-35)src/main/kotlin/com/dh/baro/product/domain/service/CategoryService.kt (1)
readOnly(9-29)
src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryResponse.kt (3)
src/main/kotlin/com/dh/baro/product/domain/repository/CategoryRepository.kt (1)
interface CategoryRepository : JpaRepository<Category, Long>(6-6)src/main/kotlin/com/dh/baro/product/domain/Category.kt (1)
name(5-14)src/main/kotlin/com/dh/baro/product/presentation/CategoryController.kt (1)
categoryFacade(13-26)
src/main/kotlin/com/dh/baro/core/StringToLongDeserializer.kt (1)
src/main/kotlin/com/dh/baro/core/event/JacksonEventSerializer.kt (2)
objectMapper(7-19)serialize(12-18)
src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryCreateRequest.kt (5)
src/main/kotlin/com/dh/baro/product/presentation/CategoryController.kt (1)
categoryFacade(13-26)src/main/kotlin/com/dh/baro/product/domain/Category.kt (1)
name(5-14)src/main/kotlin/com/dh/baro/product/presentation/swagger/CategorySwagger.kt (1)
summary(24-67)src/test/kotlin/com/dh/baro/product/domain/CategoryServiceTest.kt (1)
classes(19-78)src/main/kotlin/com/dh/baro/product/domain/repository/CategoryRepository.kt (1)
interface CategoryRepository : JpaRepository<Category, Long>(6-6)
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductListItem.kt (2)
src/main/kotlin/com/dh/baro/product/domain/InventoryItem.kt (1)
productId(3-6)src/main/kotlin/com/dh/baro/product/domain/Product.kt (2)
name(11-135)getId(58-58)
src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateResponse.kt (1)
src/main/kotlin/com/dh/baro/look/domain/Look.kt (1)
name(8-125)
src/main/kotlin/com/dh/baro/cart/application/CartFacade.kt (1)
src/main/kotlin/com/dh/baro/cart/domain/CartItemRepository.kt (1)
attributePaths(6-16)
src/main/kotlin/com/dh/baro/product/presentation/dto/PopularCursor.kt (1)
src/test/kotlin/com/dh/baro/product/domain/ProductQueryServiceTest.kt (1)
cursorLikes(70-84)
src/main/kotlin/com/dh/baro/look/presentation/dto/LookDto.kt (1)
src/main/kotlin/com/dh/baro/look/domain/Look.kt (1)
name(8-125)
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateResponse.kt (1)
src/main/kotlin/com/dh/baro/product/domain/Product.kt (1)
name(11-135)
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt (3)
src/main/kotlin/com/dh/baro/product/application/ProductCreateCommand.kt (1)
name(5-15)src/main/kotlin/com/dh/baro/product/domain/Product.kt (1)
name(11-135)src/main/kotlin/com/dh/baro/product/application/ProductFacade.kt (1)
storeService(12-51)
src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateRequest.kt (5)
src/main/kotlin/com/dh/baro/look/domain/Look.kt (2)
addProducts(90-100)getOrderedProductIds(59-63)src/main/kotlin/com/dh/baro/look/application/dto/LookDetailBundle.kt (1)
look(7-12)src/main/kotlin/com/dh/baro/look/application/LookCreateCommand.kt (1)
creatorId(3-10)src/test/kotlin/com/dh/baro/look/domain/LookServiceTest.kt (1)
cmd(103-119)src/main/kotlin/com/dh/baro/look/domain/LookProduct.kt (1)
name(6-42)
src/main/kotlin/com/dh/baro/order/presentation/dto/OrderCreateRequest.kt (3)
src/main/kotlin/com/dh/baro/product/domain/InventoryItem.kt (1)
productId(3-6)src/main/kotlin/com/dh/baro/order/domain/OrderItem.kt (1)
name(8-59)src/main/kotlin/com/dh/baro/product/infra/event/InventoryDeductionRequestedEvent.kt (1)
orderId(5-9)
src/main/kotlin/com/dh/baro/order/presentation/swagger/OrderSwagger.kt (3)
src/test/kotlin/com/dh/baro/order/domain/OrderQueryServiceTest.kt (1)
classes(24-180)src/main/kotlin/com/dh/baro/order/domain/OrderQueryService.kt (1)
readOnly(9-32)src/main/kotlin/com/dh/baro/order/domain/OrderRepository.kt (1)
findByUserIdAndCursorId(10-27)
src/main/kotlin/com/dh/baro/look/presentation/LookController.kt (2)
src/main/kotlin/com/dh/baro/look/domain/service/LookReactionService.kt (1)
lookRepository(10-48)src/main/kotlin/com/dh/baro/look/application/LookReactionFacade.kt (1)
recordReaction(14-21)
src/main/kotlin/com/dh/baro/cart/domain/CartService.kt (1)
src/main/kotlin/com/dh/baro/cart/domain/CartItemRepository.kt (1)
attributePaths(6-16)
src/main/kotlin/com/dh/baro/core/Cursor.kt (2)
src/main/kotlin/com/dh/baro/core/BaseTimeEntity.kt (1)
name(10-34)src/test/kotlin/com/dh/baro/product/domain/ProductQueryServiceTest.kt (1)
cursorId(116-129)
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductDetail.kt (2)
src/main/kotlin/com/dh/baro/product/application/dto/ProductDetailBundle.kt (1)
product(6-9)src/main/kotlin/com/dh/baro/product/domain/Product.kt (2)
name(11-135)getId(58-58)
src/main/kotlin/com/dh/baro/look/presentation/swagger/LookSwagger.kt (3)
src/main/kotlin/com/dh/baro/look/application/LookReactionFacade.kt (1)
recordReaction(14-21)src/main/kotlin/com/dh/baro/look/domain/service/LookReactionService.kt (1)
lookRepository(10-48)src/main/kotlin/com/dh/baro/look/domain/LookReaction.kt (1)
name(7-43)
src/main/kotlin/com/dh/baro/cart/domain/CartItem.kt (1)
src/main/kotlin/com/dh/baro/cart/domain/CartItemRepository.kt (2)
attributePaths(6-16)findByIdAndUserId(13-13)
src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt (1)
src/test/kotlin/com/dh/baro/product/domain/ProductQueryServiceTest.kt (1)
classes(24-197)
src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt (1)
src/main/kotlin/com/dh/baro/product/application/ProductFacade.kt (1)
storeService(12-51)
src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt (4)
src/main/kotlin/com/dh/baro/look/application/dto/LookDetailBundle.kt (1)
look(7-12)src/main/kotlin/com/dh/baro/look/application/LookFacade.kt (1)
getLookDetail(29-44)src/main/kotlin/com/dh/baro/look/domain/Look.kt (1)
name(8-125)src/main/kotlin/com/dh/baro/look/domain/LookProduct.kt (1)
name(6-42)
src/main/kotlin/com/dh/baro/identity/presentation/dto/UserProfileResponse.kt (4)
src/main/kotlin/com/dh/baro/identity/presentation/swagger/UserSwagger.kt (2)
summary(24-80)name(16-81)src/main/kotlin/com/dh/baro/identity/infra/KakaoUserInfoResponse.kt (2)
ignoreUnknown(18-21)ignoreUnknown(6-22)src/main/kotlin/com/dh/baro/identity/application/dto/AuthResult.kt (1)
userId(5-9)src/main/kotlin/com/dh/baro/identity/domain/repository/UserRepository.kt (1)
interface UserRepository : JpaRepository<User, Long>(6-6)
src/main/kotlin/com/dh/baro/order/presentation/dto/OrderDetailResponse.kt (2)
src/main/kotlin/com/dh/baro/order/domain/Order.kt (1)
name(10-65)src/main/kotlin/com/dh/baro/order/domain/OrderItem.kt (1)
name(8-59)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: CI-CD
🔇 Additional comments (39)
src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateRequest.kt (1)
26-33: toCommand 간소화는 적절합니다필드 타입을 Long으로 고정하고 변환을 제거한 점 좋습니다. 호출부/서비스 단 로직 단순화에 도움이 됩니다.
src/main/kotlin/com/dh/baro/order/application/OrderFacade.kt (1)
31-31: toLong 제거 적절합니다요청 DTO에서 Long을 직접 사용하도록 정리한 방향성 좋습니다. 호출부 일관성도 유지됐습니다.
Also applies to: 42-43
src/main/kotlin/com/dh/baro/product/presentation/dto/PopularCursor.kt (1)
3-9: ID Long→String 직렬화 적용 일관성 OK프론트 호환성을 유지하면서 내부 Long 타입을 보존하는 전략이 명확합니다. 동일 패턴을 다른 DTO에도 지속 적용해 주세요.
src/main/kotlin/com/dh/baro/order/presentation/dto/OrderSummary.kt (1)
11-12: Order ID 직렬화 전환 LGTMLongToStringSerializer 적용과 from 매핑이 일치합니다.
Also applies to: 20-20
src/main/kotlin/com/dh/baro/look/presentation/dto/LookDto.kt (1)
8-9: Look ID 직렬화 전환 LGTM (직접 필드 접근도 선호와 일치)look.id 직접 접근은 과거 선호도와도 맞습니다.
Also applies to: 15-15
src/main/kotlin/com/dh/baro/product/application/CategoryFacade.kt (1)
14-14: toLong 제거로 단순화 LGTM서비스 시그니처(Long)와 정확히 일치합니다.
src/main/kotlin/com/dh/baro/order/application/OrderCreateCommand.kt (1)
21-29: Long 키 매핑으로 정합성 회복 LGTMproductMap의 키(id: Long)와 요청 키 타입이 일치해 NPE/변환 비용이 제거되었습니다.
src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryResponse.kt (1)
3-9: ID 직렬화 전략 전환 일관성 확보됨Long → String 직렬화를 필드 단위로 적용했고, 매핑에서도
entity.id를 그대로 전달해 불필요한 변환이 사라졌습니다.Also applies to: 15-15
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductListItem.kt (1)
10-12: 불필요 변환 제거로 간결해졌습니다
product.id를 그대로 사용하고 직렬화만 문자열로 처리한 방향이 적절합니다.Also applies to: 21-21
src/main/kotlin/com/dh/baro/identity/presentation/dto/UserProfileResponse.kt (1)
9-10: ID Long 직렬화 적용 적절도메인-DTO 간 불필요 변환 제거, 직렬화 전략과 일관성 유지 모두 좋습니다.
Also applies to: 20-20
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductDetail.kt (1)
10-11: 세부 응답의 ID 직렬화 전환 OK직렬화 정책과 도메인 타입이 일치합니다.
Also applies to: 22-22
src/main/kotlin/com/dh/baro/core/LongToStringSerializer.kt (1)
7-15: 간단하고 안전한 구현입니다널 처리 포함, 목적에 충분합니다.
src/main/kotlin/com/dh/baro/cart/presentation/dto/CartResponse.kt (1)
14-24: 합계 계산 로직 명확
fold로 합산 후setScale(0, HALF_UP)적용이 KRW(정수 가격) 모델과 부합합니다.src/main/kotlin/com/dh/baro/cart/presentation/swagger/CartSwagger.kt (2)
121-126: PathVariable 타입 변경 LGTM컨트롤러와의 시그니처 일치가 확인됩니다.
145-149: 삭제 API PathVariable 타입 변경 LGTM일관성 확보되었습니다.
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt (3)
14-17: 입력 역직렬화 방향성 적절문자열 기반 ID를 Long으로 안전 변환하는 설정과 커맨드 매핑 단순화가 좋습니다.
Also applies to: 32-35
1-51: toLong()/toString() 변환 호출 없음 확인
DTO/컨트롤러에서 수동 변환 호출이 모두 제거되었습니다.
14-17: NUMBER 토큰도 정상 지원됨 – 추가 구현 불필요
StringToLongDeserializer는p.text.toLong()호출 시 VALUE_NUMBER_INT 토큰일 때도p.text가 숫자 문자열을 반환하므로 Long 변환이 정상 작동합니다.
StringListToLongListDeserializer는 내부적으로StringDeserializer가 숫자 토큰을 문자열로 처리하여it.toLong()매핑 시에도 문제없습니다.src/main/kotlin/com/dh/baro/cart/presentation/CartController.kt (2)
24-27: Cart 조회 응답 조립 단순화(LGTM)Facade에서 받은 번들을 그대로 DTO로 변환해 반환하는 흐름이 명확합니다.
40-42: updateQuantity 호출부-정의부 일치 확인됨
CartFacade의updateQuantity(userId: Long, itemId: Long, qty: Int)시그니처와 컨트롤러에서request.quantity(Int)를 전달하는 호출부가 일치하므로 추가 변경 불필요합니다.src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateResponse.kt (1)
15-16: toString() 제거 후 Long 그대로 매핑(LGTM)도메인
val id: Long을 그대로 사용해 직렬화 계층에 위임한 점이 일관되고 안전합니다.src/main/kotlin/com/dh/baro/order/presentation/dto/OrderCreateRequest.kt (2)
21-25: 요청 DTO에서 productId를 Long으로 전환(LGTM)중복
toLong()제거와 검증 어노테이션 전환(@notblank → @NotNull)이 의도에 맞습니다.
22-24: StringToLongDeserializer가 숫자 토큰도 지원합니다
JsonParser.getText()는 숫자(JSON number) 토큰일 때도 해당 숫자의 문자열 표현(예: "123")을 반환하므로p.text.toLong()으로 변환이 가능합니다. 별도 추가 조치 불필요합니다.src/main/kotlin/com/dh/baro/order/presentation/swagger/OrderSwagger.kt (2)
152-153: 타입 변경(Long) OK.경로 변수의 Long 전환이 서비스/리포지토리 시그니처와 일치합니다.
203-205: 타입 변경(Long?) OK.커서 파라미터가 Long?로 변경되어 리포지토리 쿼리(
o.id < :cursorId)와 정합적입니다.src/main/kotlin/com/dh/baro/order/presentation/dto/OrderDetailResponse.kt (1)
37-48: 정렬 기준 변경의 도메인 영향 확인.
items.sortedBy { productId }는 원래 순서(예: 추가 순서)와 다를 수 있습니다. 클라이언트 기대치가 “주문 생성 시 항목 순서”라면 영향이 있습니다. 요구사항 확인 부탁드립니다.src/main/kotlin/com/dh/baro/look/presentation/dto/LookCreateResponse.kt (1)
8-9: Long → String 직렬화 전환 LGTM.불필요한 toString() 제거와 불변 id 직접 접근(look.id)이 일관적입니다. 팀 선호도(immutable id direct access)와도 부합합니다.
Also applies to: 15-15
src/main/kotlin/com/dh/baro/cart/domain/CartService.kt (1)
13-16: @entitygraph(attributePaths = ["product"]) 어노테이션이 더 이상 존재하지 않습니다.
CartItemRepository에서 해당 어노테이션이 발견되지 않아, product 연관 제거에 따른 부팅 오류 우려가 해소되었습니다.src/main/kotlin/com/dh/baro/order/presentation/OrderController.kt (1)
45-49: Long 바인딩 전환 LGTM.toLong() 제거로 컨트롤러 단 단순화 및 오버헤드 제거가 잘 되었습니다.
src/main/kotlin/com/dh/baro/cart/application/CartFacade.kt (1)
33-41: 존재성 검증 추가 LGTM.user/product 존재 체크를 파사드에서 선제 수행해 도메인 단 예외를 줄인 점 좋습니다.
src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt (1)
9-11: ID Long→String 직렬화, 필드 매핑 일치 LGTM.orderedProductIds 기반 정렬 보존, 누락 엔티티 mapNotNull 처리도 합리적입니다.
Also applies to: 20-21, 41-41, 50-50
src/main/kotlin/com/dh/baro/look/presentation/swagger/LookSwagger.kt (1)
94-96: 경로/쿼리 파라미터 Long 전환 LGTM.스펙과 컨트롤러/DTO 타입이 일치합니다.
Also applies to: 116-117, 163-165, 206-206
src/main/kotlin/com/dh/baro/look/presentation/LookController.kt (2)
33-39: Long 기반 PathVariable 및 파사드 전달 일관성 확보 OKString → Long 전환이 컨트롤러·파사드 호출까지 잘 반영되었습니다.
45-46: 취소(reaction) API도 Long 전환 반영 OK시그니처와 위임 호출 모두 일관적입니다.
src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt (1)
74-74: 상품 상세 PathVariable Long 전환 OK스키마가 int64로 문서화될 것이며 런타임 타입과 일치합니다.
src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt (4)
30-33: 상품 상세: Long 기반 전달로 단순화 OKtoLong() 제거되어 흐름이 간결하고 파사드 시그니처와 일치합니다.
38-53: 인기 상품 목록: 커서 XOR 검증 유지·일관성 OK
- (cursorId == null) xor (cursorLikes == null) 검증으로 API 계약을 명확히 보장합니다.
- ID는 val 접근(it.id), likes는 getter 사용으로 도메인 의도를 잘 반영합니다.
59-70: 최신 상품 목록: Long 전환 및 커서 구성 OK
- categoryId/cursorId Long? 파라미터 전환 적절
- Cursor(it.id)로 nextCursor 추출 일관성 확보
43-44: IllegalArgumentException이 400 Bad Request로 매핑됨 확인
GlobalExceptionHandler.kt에서 IllegalArgumentException이@ResponseStatus(HttpStatus.BAD_REQUEST)로 처리되고 있음을 확인했습니다.
| val currentItemCnt = cartItemRepository.countByUserId(userId) | ||
| if (!cartPolicy.canAddMoreItems(currentItemCnt)) { | ||
| throw IllegalStateException(ErrorMessage.CART_ITEM_LIMIT_EXCEEDED.message) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
장바구니 최대 개수 정책, 동시성에 취약.
count→insert 사이 창(window)로 초과 삽입이 가능합니다. 사용자별 아이템 행을 비관적 락으로 선점하거나(예: findByUserId with PESSIMISTIC_WRITE) DB 측에서 보장(별도 집계/트리거, 제한 컬럼)하는 방식을 검토하세요.
원치 않으면 최소한 limit 체크를 "신규 생성 시도 직후" 재검증하는 방어 로직을 추가하세요.
🤖 Prompt for AI Agents
In src/main/kotlin/com/dh/baro/cart/domain/CartService.kt around lines 27-31,
the current count→insert flow is racy and allows exceeding the cart item limit
under concurrency; update the logic to either (a) obtain a pessimistic write
lock on the user's cart row(s) (e.g., use repository.findByUserId(...,
PESSIMISTIC_WRITE)) and re-check the count under that lock before inserting, or
(b) enforce the limit in the DB (unique/aggregate constraint, trigger or a
dedicated counter column updated transactionally) so the DB rejects excess
inserts; if you choose not to change locking/DB, add a defensive re-validation
immediately after attempting to persist the new item and rollback/throw if the
limit is exceeded so no over-limit state is committed.
Issue Number
#35
As-Is
To-Be
✅ Check List
📸 Test Screenshot
Additional Description