-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT] Store 도메인 생성 및 예외 처리 구조 개선 #19
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
Walkthrough유효성 검증 라이브러리 추가, 에러 도메인(ErrorMessage/ErrorResponse/GlobalExceptionHandler) 확장, Store/StoreStatus 엔티티 도입 및 User 컬럼 제한 조정, Order/Look에 AggregateRoot 메타데이터 추가, Product에 storeId 추가 및 관련 요청/명령/서비스/스웨거/테스트 일괄 반영. Changes
Sequence Diagram(s)sequenceDiagram
participant C as Client
participant Ctrl as Controller
participant Svc as Service
participant H as GlobalExceptionHandler
participant R as ErrorResponse
C->>Ctrl: HTTP 요청
Ctrl->>Svc: 비즈니스 호출 (create / process)
alt 정상 응답
Svc-->>Ctrl: 결과 반환
Ctrl-->>C: 성공 응답(JSON)
else 예외 발생
Svc-->>Ctrl: 예외 전파
Ctrl-->>H: 예외 처리 요청
H->>H: 예외 유형 판별 및 매핑
H-->>R: ErrorResponse 생성
R-->>C: 에러 응답(JSON)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
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 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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
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: 6
🔭 Outside diff range comments (1)
src/main/kotlin/com/dh/baro/product/application/ProductCreateCommand.kt (1)
5-15: Service 계층에 storeId 소유권 검증 로직 추가 필요현재 코드 흐름상 클라이언트가 임의의
storeId를 지정해도 소유권 검증이 전혀 이루어지지 않아 보안상 큰 위험이 있습니다. 아래 위치에 반드시userId전달 및 검증 로직을 추가해 주세요.•
ProductController#createProduct
- 27~31행:
productFacade.createProduct(request.toCommand())호출부에서userId를 함께 넘기도록 변경
•ProductFacade#createProduct- 시그니처를
fun createProduct(userId: Long, cmd: ProductCreateCommand): Product로 확장- 내부에서
storeService.validateOwnership(userId, cmd.storeId)호출
•ProductService#createProduct- 상품 생성 직전에 소유권 검증 로직(
storeService.validateOwnership(userId, cmd.storeId)) 삽입예시 스니펫(참고용):
// ProductController override fun createProduct( @CurrentUser userId: Long, @Valid @RequestBody request: ProductCreateRequest ): ProductCreateResponse { val cmd = request.toCommand() return ProductCreateResponse.from( productFacade.createProduct(userId, cmd) ) } // ProductFacade @Service class ProductFacade( private val productService: ProductService, private val categoryService: CategoryService, private val storeService: StoreService ) { fun createProduct(userId: Long, cmd: ProductCreateCommand): Product { storeService.validateOwnership(userId, cmd.storeId) val categories = categoryService.getCategoriesByIds(cmd.categoryIds) return productService.createProduct(cmd, categories) } }
🧹 Nitpick comments (21)
src/main/kotlin/com/dh/baro/identity/domain/StoreStatus.kt (1)
3-10: 상태 전이 규칙을 문서/코드로 명시하는 것을 권장상태 값만으로는 전이 가능/불가능 규칙이 불명확합니다. KDoc 또는 전이 표(예: from→to 허용 매트릭스)를 별도 객체로 명시하면 서비스 계층 검증이 명료해집니다.
예시(참고용):
/** * 허용 전이: * DRAFT -> ACTIVE, CLOSED * ACTIVE -> PAUSED, SUSPENDED, CLOSED * ... */ enum class StoreStatus { /* ... */ } // 또는 전이 규칙 객체 object StoreStatusTransitions { private val allowed = mapOf( DRAFT to setOf(ACTIVE, CLOSED), ACTIVE to setOf(PAUSED, SUSPENDED, CLOSED), // ... ) fun canTransit(from: StoreStatus, to: StoreStatus) = allowed[from]?.contains(to) == true }src/main/kotlin/com/dh/baro/product/domain/Product.kt (3)
18-20: storeId 필드 추가 타당 — 인덱스/외래키(옵션) 고려 권장조회 패턴을 고려하면 store_id에 인덱스를 추가하는 것이 유리합니다. 또한 Store와의 참조 무결성을 DB FK로 보장(선호 시)하거나, 최소한 마이그레이션에서 인덱스를 추가해 주세요.
인덱스 예시(엔티티 레벨; 선택):
// import jakarta.persistence.Index @Entity @Table( name = "products", indexes = [Index(name = "idx_products_store_id", columnList = "store_id")] ) class Product( /* ... */ )또는 마이그레이션에서:
- CREATE INDEX idx_products_store_id ON products(store_id);
- (선택) ALTER TABLE products ADD CONSTRAINT fk_products_store FOREIGN KEY (store_id) REFERENCES stores(id);
18-20: JPA 연관 매핑으로의 확장은 선택적으로 검토 가능현재는 단순 FK 값 보관으로 결합을 낮추는 장점이 있습니다. 추후 Store 상태/소유자 기반 정책을 도메인 규칙으로 강제하려면 LAZY ManyToOne 연관을 도입하는 것도 고려해볼 수 있습니다.
예시(참고용):
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "store_id", nullable = false, updatable = false, insertable = false) private var store: Store? = null주의: 양방향/캐스케이드는 신중히, N+1에 유의.
18-20: 마이그레이션 파일 미발견: store_id NOT NULL 컬럼 추가 검증 필요rg/fd 스크립트 실행 결과, SQL·Kotlin 기반 마이그레이션 또는 체인지로그 파일이 전혀 탐색되지 않았습니다.
아래 사항을 수동으로 반드시 확인해 주세요:
- Flyway/Liquibase(SQL 또는 Kotlin) 마이그레이션 파일에서
products테이블에store_id컬럼 추가가 정의되어 있는지- 신규 NOT NULL 컬럼에 대한 백필(back-fill) 또는 단계별(Nullable → 데이터 채움 → NOT NULL) 전환 로직이 포함되어 있는지
필요 시 샘플 스크립트(또는 migration 템플릿)를 제공해 드리겠습니다.
src/main/kotlin/com/dh/baro/order/domain/Order.kt (2)
31-36: fetch = LAZY 명시 LGTM + Set 사용 시 동치성 주의
- LAZY 명시는 명확성과 일관성 측면에서 좋습니다.
- MutableSet을 사용할 경우, OrderItem의 equals/hashCode 구현에 따라 중복 추가가 억제될 수 있습니다. 현재 equals/hashCode가 기본(참조 동등)이라면 문제 없으나, 추후 식별자 기반으로 재정의하면 동일 ID 아이템 중복 추가가 무음으로 실패할 수 있습니다. 중복 허용/제거 의도가 무엇인지에 따라 List로의 전환 혹은 동치성 정책을 문서화하는 것을 고려해 주세요.
43-48: updateTotalPrice 간결화 제안(동일 동작)가독성을 위해 fold 사용을 고려할 수 있습니다.
- fun updateTotalPrice() { - totalPrice = items - .map { it.subTotal() } - .reduceOrNull(BigDecimal::add) ?: BigDecimal.ZERO - .setScale(0, RoundingMode.HALF_UP) - } + fun updateTotalPrice() { + totalPrice = items + .fold(BigDecimal.ZERO) { acc, item -> acc + item.subTotal() } + .setScale(0, RoundingMode.HALF_UP) + }src/main/kotlin/com/dh/baro/core/ErrorMessage.kt (1)
8-16: 공통 에러 메시지 확장 LGTM + 문장부호 일관성(마침표) 제안추가된 항목들이 전역 예외 처리의 커버리지를 잘 넓혀줍니다. 메시지 문장부호만 일관화하면 더 좋겠습니다. MISSING_REQUEST_HEADER에 마침표 추가 제안드립니다.
- MISSING_REQUEST_HEADER("필수 요청 헤더가 누락되었습니다"), + MISSING_REQUEST_HEADER("필수 요청 헤더가 누락되었습니다."),src/test/kotlin/com/dh/baro/product/domain/Fixture.kt (1)
20-21: Fixture의 storeId 하드코딩 제거 제안테스트 재사용성을 위해 storeId를 파라미터로 노출하되 기본값을 유지하면 편합니다.
fun productFixture( id: Long, name: String, category: Category, + storeId: Long = 123L, likes: Int = 0, createdAtAgoDays: Long = 0L // 0=지금, 31=한달 전 ): Product { val product = Product( id = id, name = name, - storeId = 123L, + storeId = storeId, price = BigDecimal("19900"), quantity = 10, thumbnailUrl = "https://example.com/$id-thumb.jpg", likesCount = likes, )src/main/kotlin/com/dh/baro/product/domain/service/ProductService.kt (1)
15-30: 상품 생성 시 storeId 검증(존재/소유/상태) 필요 여부 확인PR에서 Store 도메인과 StoreStatus(DRAFT 초기화)가 도입되었습니다. 제품 생성 시 아래 검증이 필요한지 확인 부탁드립니다:
- storeId가 실제 존재하는지
- 현재 사용자(@currentuser)가 해당 Store의 소유자인지
- 허용 상태인지(예: DRAFT에는 생성 불가, ACTIVE만 허용 등)
컨트롤러에서 userId를 받고 있으나, ProductFacade → ProductService 호출 경로상 사용자-스토어 검증이 누락될 가능성이 있습니다(현 PR 범위 기준).
참고 스케치(적용 위치는 Facade/Service 중 정책에 맞춰 선택):
// 예시: Service 레이어에서 검증 val store = storeRepository.findById(cmd.storeId) ?: throw IllegalArgumentException(ErrorMessage.NO_RESOURCE_FOUND.message) if (store.ownerId != currentUserId) { throw IllegalStateException(ErrorMessage.FORBIDDEN.message) } if (store.status != StoreStatus.ACTIVE) { throw IllegalStateException(ErrorMessage.METHOD_NOT_SUPPORTED.message) }src/test/kotlin/com/dh/baro/product/domain/ProductServiceTest.kt (1)
60-68: storeId 영속 값에 대한 검증 추가 제안생성 결과의 storeId가 기대대로 저장되었는지까지 단언하면 회귀 방지에 도움이 됩니다.
val saved = shouldNotThrowAny { productService.createProduct(cmd, listOf(top, shoe)) } productRepository.existsById(saved.id) shouldBe true + // storeId가 정확히 반영되었는지 확인 + saved.storeId shouldBe cmd.storeIdsrc/test/kotlin/com/dh/baro/order/Fixture.kt (1)
19-19: 하드코딩된 storeId를 파라미터로 승격 제안테스트 픽스처에 하드코딩된 123L 대신, 기본값을 둔 파라미터로 받아 재사용성과 가독성을 높이는 편이 좋습니다. 기존 호출부는 그대로 두고 필요시 다른 storeId를 주입할 수 있습니다.
다음과 같이 변경을 권장합니다.
- storeId = 123L, + storeId = storeId,추가로, 함수 시그니처(변경 범위 밖)는 아래처럼 확장하십시오.
fun productFixture( id: Long, name: String, category: Category, price: BigDecimal = BigDecimal("1000"), quantity: Int = 10, storeId: Long = 123L, // 신규 파라미터(기본값) ): Product = ...src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt (1)
11-13: storeId 유효성 메시지 명시 권장다른 요청 DTO들처럼 검증 실패 메시지를 명시해주면 클라이언트/문서 일관성이 좋아집니다.
- @field:Positive + @field:Positive(message = "유효하지 않은 스토어 ID 입니다.") val storeId: Long,src/main/kotlin/com/dh/baro/identity/domain/User.kt (1)
53-55: email도 trim 처리 권장 (공백/중복 이슈 예방)이름은 trim 처리되나 email은 그대로라, 앞뒤 공백으로 인한 중복/유니크 제약 충돌 가능성이 있습니다. 안전하게 email도 trim 하세요. 필요하면 정책에 따라 lower-case 정규화도 고려하십시오.
- name = name.trim(), - email = email, + name = name.trim(), + email = email.trim(),src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt (1)
57-60: Swagger에 userId 숨김 처리 누락 — UI 오해 방지Cart/Order Swagger와 동일하게 userId는 서버가 세션에서 주입하는 값이므로 숨기는 편이 일관되고 안전합니다.
- fun createProduct( - userId: Long, + fun createProduct( + @Parameter(hidden = true) userId: Long, @RequestBody request: ProductCreateRequest, ): ProductCreateResponsesrc/main/kotlin/com/dh/baro/identity/domain/Store.kt (3)
21-23: @lob와 columnDefinition(MEDIUMTEXT) 중복 지정 — 하나로 통일 권장동일 필드에
@Lob와columnDefinition = "MEDIUMTEXT"를 함께 지정하면 DDL 생성 시 공급자에 따라 길이/타입 추론이 충돌할 수 있습니다. MySQL을 표준으로 사용한다면MEDIUMTEXT만으로 충분하므로@Lob제거를 권장합니다. 반대로 이식성을 우선한다면@Lob만 남기고 DB 타입 직접 지정은 제거하는 것도 방법입니다.아래와 같이 정리하는 것을 제안합니다(옵션 A: @lob 제거).
- @Lob - @Column(name = "description", columnDefinition = "MEDIUMTEXT") + @Column(name = "description", columnDefinition = "MEDIUMTEXT") private var description: String? = null,대안(옵션 B: columnDefinition 제거):
- @Lob - @Column(name = "description", columnDefinition = "MEDIUMTEXT") + @Lob + @Column(name = "description") private var description: String? = null,
53-69: 스토어명 공백 입력 방지 (도메인 불변식 보강 제안)DTO/Validator에서 잡더라도, 도메인 팩토리에서 최소 불변식(
name공백 금지)을 보강해두는 편이 안전합니다.fun newStore( owner: User, name: String, description: String?, phoneNumber: String?, address: String?, thumbnailUrl: String?, ): Store { - return Store( + val normalizedName = name.trim() + require(normalizedName.isNotEmpty()) { "Store name must not be blank" } + return Store( id = IdGenerator.generate(), owner = owner, - name = name.trim(), + name = normalizedName, description = description, phoneNumber = phoneNumber, address = address, thumbnailUrl = thumbnailUrl, status = StoreStatus.DRAFT, ) }
8-16: 조회 성능 대비: owner_id 인덱스 추가 권장Store 조회/필터링이 owner 기준으로 자주 발생한다면 인덱스가 유의미합니다. 스키마 마이그레이션에 반영 가능하다면 아래처럼
@Table에 인덱스 정의를 제안합니다.-@Table(name = "stores") +@Table( + name = "stores", + indexes = [Index(name = "ix_stores_owner_id", columnList = "owner_id")] +)src/main/kotlin/com/dh/baro/core/advice/GlobalExceptionHandler.kt (4)
117-121: ErrorResponse 생성 일관성 유지 (팩터리 메서드 사용)직접 생성 대신
ErrorResponse.from(...)를 사용하면 코드 일관성과 변경 용이성이 좋아집니다.- return ErrorResponse(ErrorMessage.NO_RESOURCE_FOUND.message) + return ErrorResponse.from(ErrorMessage.NO_RESOURCE_FOUND)
125-128: 동일 맥락 — 메서드 미지원 응답도 팩터리 사용 권장- return ErrorResponse(ErrorMessage.METHOD_NOT_SUPPORTED.message) + return ErrorResponse.from(ErrorMessage.METHOD_NOT_SUPPORTED)
133-136: 동일 맥락 — 미디어 타입 미지원 응답도 팩터리 사용 권장- return ErrorResponse(ErrorMessage.MEDIA_TYPE_NOT_SUPPORTED.message) + return ErrorResponse.from(ErrorMessage.MEDIA_TYPE_NOT_SUPPORTED)
77-83: Tomcat 종속 예외 처리 범위 확인(선택)
ClientAbortException은 Tomcat 전용입니다. 만약 다른 서블릿 컨테이너(예: Undertow, Jetty) 프로필이 있다면, 상위 타입(IOException 등)도 함께 처리하는 핸들러를 추가하는 것을 고려해볼 수 있습니다. 현재 환경이 Tomcat 고정이라면 그대로 두셔도 무방합니다.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (18)
gradle/spring.gradle(1 hunks)src/main/kotlin/com/dh/baro/core/ErrorMessage.kt(1 hunks)src/main/kotlin/com/dh/baro/core/ErrorResponse.kt(1 hunks)src/main/kotlin/com/dh/baro/core/advice/GlobalExceptionHandler.kt(1 hunks)src/main/kotlin/com/dh/baro/identity/domain/Store.kt(1 hunks)src/main/kotlin/com/dh/baro/identity/domain/StoreStatus.kt(1 hunks)src/main/kotlin/com/dh/baro/identity/domain/User.kt(2 hunks)src/main/kotlin/com/dh/baro/look/domain/Look.kt(1 hunks)src/main/kotlin/com/dh/baro/order/domain/Order.kt(2 hunks)src/main/kotlin/com/dh/baro/product/application/ProductCreateCommand.kt(1 hunks)src/main/kotlin/com/dh/baro/product/domain/Product.kt(4 hunks)src/main/kotlin/com/dh/baro/product/domain/service/ProductService.kt(1 hunks)src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt(2 hunks)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt(2 hunks)src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt(2 hunks)src/test/kotlin/com/dh/baro/order/Fixture.kt(1 hunks)src/test/kotlin/com/dh/baro/product/domain/Fixture.kt(1 hunks)src/test/kotlin/com/dh/baro/product/domain/ProductServiceTest.kt(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 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.423Z
Learning: codrin2 prefers direct field access for immutable id properties (val) rather than using getters, reasoning that immutable fields pose less encapsulation risk.
🧬 Code Graph Analysis (13)
src/main/kotlin/com/dh/baro/identity/domain/StoreStatus.kt (1)
src/main/kotlin/com/dh/baro/identity/domain/UserRole.kt (1)
BUYER(3-7)
src/main/kotlin/com/dh/baro/product/application/ProductCreateCommand.kt (1)
src/main/kotlin/com/dh/baro/order/application/OrderCreateCommand.kt (2)
productId(11-20)userId(5-31)
src/main/kotlin/com/dh/baro/look/domain/Look.kt (1)
src/main/kotlin/com/dh/baro/core/AggregateRoot.kt (1)
AnnotationTarget(3-5)
src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt (2)
src/main/kotlin/com/dh/baro/cart/presentation/swagger/CartSwagger.kt (1)
summary(61-95)src/main/kotlin/com/dh/baro/order/presentation/swagger/OrderSwagger.kt (1)
summary(24-93)
src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt (5)
src/main/kotlin/com/dh/baro/product/application/ProductFacade.kt (1)
productService(9-20)src/main/kotlin/com/dh/baro/identity/presentation/UserController.kt (1)
UserRole(12-23)src/main/kotlin/com/dh/baro/core/auth/CurrentUserArgumentResolver.kt (1)
sessionManager(10-25)src/main/kotlin/com/dh/baro/core/auth/CurrentUser.kt (1)
AnnotationTarget(3-5)src/main/kotlin/com/dh/baro/cart/presentation/CartController.kt (1)
UserRole(15-48)
src/main/kotlin/com/dh/baro/order/domain/Order.kt (3)
src/main/kotlin/com/dh/baro/order/domain/OrderItem.kt (1)
name(9-48)src/main/kotlin/com/dh/baro/order/domain/OrderService.kt (1)
readOnly(9-50)src/main/kotlin/com/dh/baro/core/AggregateRoot.kt (1)
AnnotationTarget(3-5)
src/main/kotlin/com/dh/baro/product/domain/service/ProductService.kt (1)
src/main/kotlin/com/dh/baro/product/application/ProductFacade.kt (2)
productService(9-20)createProduct(15-19)
src/main/kotlin/com/dh/baro/core/ErrorResponse.kt (4)
src/main/kotlin/com/dh/baro/order/presentation/OrderDetailResponse.kt (1)
orderId(8-41)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductResponse.kt (1)
id(6-25)src/main/kotlin/com/dh/baro/identity/presentation/dto/UserProfileResponse.kt (1)
id(6-23)src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryResponse.kt (1)
id(5-16)
src/main/kotlin/com/dh/baro/product/domain/Product.kt (1)
src/main/kotlin/com/dh/baro/product/domain/ProductImage.kt (1)
name(6-22)
src/test/kotlin/com/dh/baro/product/domain/ProductServiceTest.kt (1)
src/main/kotlin/com/dh/baro/product/application/ProductFacade.kt (1)
productService(9-20)
src/main/kotlin/com/dh/baro/identity/domain/User.kt (3)
src/main/kotlin/com/dh/baro/identity/domain/service/UserService.kt (1)
readOnly(13-43)src/main/kotlin/com/dh/baro/identity/presentation/dto/UserProfileResponse.kt (1)
id(6-23)src/main/kotlin/com/dh/baro/identity/domain/SocialAccount.kt (1)
name(7-39)
src/main/kotlin/com/dh/baro/identity/domain/Store.kt (2)
src/main/kotlin/com/dh/baro/order/domain/OrderItem.kt (1)
name(9-48)src/main/kotlin/com/dh/baro/cart/domain/CartItem.kt (1)
name(9-49)
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt (4)
src/main/kotlin/com/dh/baro/cart/presentation/dto/AddItemRequest.kt (1)
message(6-11)src/main/kotlin/com/dh/baro/order/application/OrderCreateCommand.kt (2)
productId(11-20)userId(5-31)src/main/kotlin/com/dh/baro/order/presentation/OrderCreateRequest.kt (1)
message(18-24)src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryCreateRequest.kt (1)
id(6-12)
⏰ 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 (13)
gradle/spring.gradle (1)
7-7: Bean Validation 스타터 추가 적절Spring Boot 3.x에서 기본 포함되지 않는 Bean Validation을 명시적으로 추가한 점 좋습니다. 컨트롤러/DTO에 선언된 제약 애노테이션(@Valid, jakarta.validation.*)이 정상 동작하겠습니다.
src/main/kotlin/com/dh/baro/look/domain/Look.kt (1)
4-11: Aggregate Root 마킹 일관성 좋은 변경도메인 경계를 명확히 하기 위해 @AggregateRoot 애노테이션을 도입·적용한 점 좋습니다. DDD 관점에서 Look을 루트로 다루는 의도가 코드에서 잘 드러납니다.
src/main/kotlin/com/dh/baro/product/application/ProductCreateCommand.kt (1)
7-7: storeId 추가로 생성 커맨드의 표현력이 개선됨상품-스토어 연관을 생성 시점부터 강제하는 방향은 도메인 일관성 측면에서 타당합니다. 하위 레이어(서비스/엔티티)까지 반영된 것으로 보입니다.
src/main/kotlin/com/dh/baro/identity/domain/StoreStatus.kt (1)
1-10: StoreStatus 정의 추가 적절도메인 상태가 명확히 열거되어 가독성과 추론 가능성이 좋아졌습니다. CLOSED/TERMINATED 등 종료 성격의 상태를 분리한 점도 좋습니다.
src/main/kotlin/com/dh/baro/product/domain/Product.kt (1)
112-130: newProduct에 storeId 반영 적절팩토리 메서드에서 storeId를 필수 인자로 받아 일관되게 생성 경로를 통제한 점 좋습니다. 상위 레이어에서의 소유권 검증만 보완되면 생성 플로우가 깔끔해집니다.
src/main/kotlin/com/dh/baro/order/domain/Order.kt (1)
4-4: @AggregateRoot 도입과 어노테이션 추가 LGTM도메인 경계를 명확히 드러내 주며, 기능적 부작용 없이 의도를 잘 표현합니다.
Also applies to: 10-10
src/main/kotlin/com/dh/baro/product/domain/service/ProductService.kt (1)
17-25: storeId 전달 반영 LGTM도메인 생성 팩토리로 storeId를 전달하도록 일관되게 반영되었습니다.
src/test/kotlin/com/dh/baro/product/domain/ProductServiceTest.kt (1)
44-58: 테스트 데이터에 storeId 반영 LGTMCommand에 storeId를 포함하는 변경이 테스트에도 잘 반영되었습니다.
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt (1)
35-38: toCommand에 storeId 전달 LGTM새 필드가 커맨드까지 잘 전달됩니다. 도메인 계층과의 연결이 명확해졌습니다.
src/main/kotlin/com/dh/baro/identity/domain/User.kt (1)
16-23: 스키마 마이그레이션 및 기존 데이터 길이 초과 여부 반드시 확인 필요DB 제약 조건을 코드에 추가한 것은 적절하나, 운영 환경에 반영된 마이그레이션 스크립트가 실제로 존재하고 배포되었는지 확인해야 합니다. 또한 기존 테이블에 길이 초과(>100, >30) 레코드가 없는지도 점검하세요.
src/main/resources/db/또는db/migration/등 Flyway/Liquibase 스크립트 디렉터리에서
• users 테이블 생성·변경 DDL
• user_name(length=100), phone_number(length=30) 제약 반영 여부- 운영 DB에서 직접 데이터 검사
• SELECT COUNT() FROM users WHERE CHAR_LENGTH(user_name) > 100;
• SELECT COUNT() FROM users WHERE CHAR_LENGTH(phone_number) > 30;src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt (1)
36-44: Swagger 예제에 storeId/imageUrls 추가 LGTM예제 페이로드가 실제 스키마와 동기화되어 좋습니다. 문서-구현 간 괴리 감소에 도움 됩니다.
src/main/kotlin/com/dh/baro/identity/domain/Store.kt (1)
7-37: 엔티티 모델링/팩토리 메서드 전반 LGTM
- 연관관계, 컬럼 제약, Enum 문자열 매핑, 시간 감사 상속까지 일관성 있게 잘 잡혀 있습니다.
newStore가DRAFT로 초기화되는 것도 도메인 의도에 부합합니다.- 불변 키(id)는
val로 노출하고, 나머지는 private var + getter만 제공하는 스타일은 이전 도메인들과도 일관적입니다. (learned preference 반영: 불변 id는 필드 접근)Also applies to: 51-71
src/main/kotlin/com/dh/baro/core/ErrorResponse.kt (1)
9-14: 구조화된 에러 응답 포맷 도입 LGTM
fieldErrors/violationErrors를 분리해 제공하는 설계는 핸들러 코드 가독성과 클라이언트 파싱 측면에서 명확합니다.JsonInclude.NON_NULL적용으로 페이로드도 깔끔합니다.Also applies to: 17-34
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: 0
♻️ Duplicate comments (1)
src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt (1)
27-30: 권한 검증 누락: @currentuser 미사용으로 storeId 소유 검증이 불가합니다STORE_OWNER 권한 체크만으로는 부족합니다. 현재 사용자의 userId를 전달하지 않아, 요청 바디의 storeId가 현재 사용자 소유인지(및 스토어 상태가 생성 허용 상태인지) Facade/Service에서 검증할 수 없습니다. 과거 코멘트에서도 동일 이슈가 지적되었습니다.
아래처럼
@CurrentUser userId를 주입하고, Facade 시그니처를createProduct(userId, …)로 변경해 전달하세요.적용 제안 (컨트롤러):
@CheckAuth(UserRole.STORE_OWNER) - override fun createProduct( - @Valid @RequestBody request: ProductCreateRequest, - ): ProductCreateResponse = - ProductCreateResponse.from(productFacade.createProduct(request.toCommand())) + override fun createProduct( + @CurrentUser userId: Long, + @Valid @RequestBody request: ProductCreateRequest, + ): ProductCreateResponse = + ProductCreateResponse.from(productFacade.createProduct(userId, request.toCommand()))동반 변경 예시 (다른 파일, 참고용):
// ProductSwagger (인터페이스)도 동일한 시그니처로 업데이트 필요 override fun createProduct(@CurrentUser userId: Long, @RequestBody request: ProductCreateRequest): ProductCreateResponse // ProductFacade fun createProduct(currentUserId: Long, cmd: ProductCreateCommand): Product { // 1) 소유권 검증: storeRepository.findByIdAndOwnerId(cmd.storeId, currentUserId) // 2) 스토어 상태 검증: 예) ACTIVE만 허용, DRAFT/BLOCKED 등은 거부 // 3) 카테고리 조회 및 Product 생성 } // ProductCreateCommand에는 storeId가 포함되어 있어야 합니다.원하시면 Facade/Service/Repository까지 포함한 전체 변경 패치도 만들어드리겠습니다.
🧹 Nitpick comments (1)
src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt (1)
7-7: 사용하지 않는 import입니다 (임시 코드 흔적).
CurrentUser를 임포트했지만 사용하지 않습니다. 위 권한 전달 리팩터를 적용하면 필요해집니다. 적용하지 않을 경우 임포트를 제거하세요.-import com.dh.baro.core.auth.CurrentUser
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt(2 hunks)src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt
🧰 Additional context used
🧠 Learnings (1)
📓 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.423Z
Learning: codrin2 prefers direct field access for immutable id properties (val) rather than using getters, reasoning that immutable fields pose less encapsulation risk.
🧬 Code Graph Analysis (1)
src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt (3)
src/main/kotlin/com/dh/baro/product/application/ProductFacade.kt (2)
productService(9-20)createProduct(15-19)src/main/kotlin/com/dh/baro/product/domain/service/ProductService.kt (2)
productRepository(10-32)createProduct(15-31)src/main/kotlin/com/dh/baro/product/application/ProductCreateCommand.kt (1)
name(6-26)
🔇 Additional comments (1)
src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt (1)
40-41: IllegalArgumentException 처리 검증 완료 – 일관된 ErrorResponse 반환 보장GlobalExceptionHandler.kt(94–99)에 이미
@ExceptionHandler(IllegalArgumentException::class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
fun handleIllegalArgument(e: IllegalArgumentException): ErrorResponse {
return ErrorResponse.from(e)
}
구현이 있어, 해당 예외를 던질 경우 400과 ErrorResponse(message) 형태로 일관되게 응답됩니다.
따라서 별도 커스텀 예외나 ResponseStatusException으로 변경하지 않아도 무방합니다.
Issue Number
#18
As-Is
Store관련 도메인이 존재하지 않아 매장 관리 기능 개발이 불가능했음ErrorResponse가 단순 문자열 메시지 형태로만 구성되어 있어 입력 검증 상세 정보를 클라이언트에 제공할 수 없었음To-Be
Store엔티티 도메인 및 상태(enum) 정의User와 연관된 매장 정보를 관리하기 위한Store엔티티 생성StoreStatusenum 도입DRAFT상태로 초기화되는 정적 팩토리 메서드 추가검증/예외 응답 구조 고도화
ErrorResponse를 확장하여fieldErrors,violationErrors등 구조적 오류 응답 포맷 지원BindingResult,ConstraintViolationException기반 오류에 대한 명확한 필드 응답 제공예외 처리 전면 개선
HttpMessageNotReadableException,BindException,MethodArgumentTypeMismatchException등 추가 대응ErrorMessageenum 확장INVALID_JSON,FIELD_ERROR,METHOD_NOT_SUPPORTED,ALREADY_DISCONNECTED등) 항목 추가✅ Check List
📸 Test Screenshot
Additional Description