Skip to content

Conversation

@codrin2
Copy link
Member

@codrin2 codrin2 commented Aug 16, 2025

Issue Number

#18

As-Is

  • Store 관련 도메인이 존재하지 않아 매장 관리 기능 개발이 불가능했음
  • 공통 예외 처리 로직이 부족하여 다양한 예외에 대한 일관된 응답 제공이 어려웠음
  • ErrorResponse가 단순 문자열 메시지 형태로만 구성되어 있어 입력 검증 상세 정보를 클라이언트에 제공할 수 없었음

To-Be

  • Store 엔티티 도메인 및 상태(enum) 정의

    • User와 연관된 매장 정보를 관리하기 위한 Store 엔티티 생성
    • 매장 상태를 표현하기 위한 StoreStatus enum 도입
    • 생성 시 DRAFT 상태로 초기화되는 정적 팩토리 메서드 추가
  • 검증/예외 응답 구조 고도화

    • ErrorResponse를 확장하여 fieldErrors, violationErrors 등 구조적 오류 응답 포맷 지원
    • BindingResult, ConstraintViolationException 기반 오류에 대한 명확한 필드 응답 제공
  • 예외 처리 전면 개선

    • HttpMessageNotReadableException, BindException, MethodArgumentTypeMismatchException 등 추가 대응
    • 클라이언트 전송 중단 및 잘못된 요청에 대한 구체적인 로그 및 메시지 반환
  • ErrorMessage enum 확장

    • 공통 예외 메시지 (INVALID_JSON, FIELD_ERROR, METHOD_NOT_SUPPORTED, ALREADY_DISCONNECTED 등) 항목 추가

✅ Check List

  • Have all tests passed?
  • Have all commits been pushed?
  • Did you verify the target branch for the merge?
  • Did you assign the appropriate assignee(s)?
  • Did you set the correct label(s)?

📸 Test Screenshot

Additional Description

@coderabbitai
Copy link

coderabbitai bot commented Aug 16, 2025

Walkthrough

유효성 검증 라이브러리 추가, 에러 도메인(ErrorMessage/ErrorResponse/GlobalExceptionHandler) 확장, Store/StoreStatus 엔티티 도입 및 User 컬럼 제한 조정, Order/Look에 AggregateRoot 메타데이터 추가, Product에 storeId 추가 및 관련 요청/명령/서비스/스웨거/테스트 일괄 반영.

Changes

Cohort / File(s) Summary of changes
Validation & Error Handling
gradle/spring.gradle, src/main/kotlin/com/dh/baro/core/ErrorMessage.kt, src/main/kotlin/com/dh/baro/core/ErrorResponse.kt, src/main/kotlin/com/dh/baro/core/advice/GlobalExceptionHandler.kt
Bean Validation 스타터 추가. ErrorMessage에 검증/요청/미디어/메서드 관련 상수 다수 추가. ErrorResponse에 fieldErrors/violationErrors, 관련 DTO 및 factory 메서드 추가. GlobalExceptionHandler에 다양한 스프링/도메인 예외 핸들러 추가 및 시그니처 정비.
Identity Domain
src/main/kotlin/com/dh/baro/identity/domain/Store.kt, .../StoreStatus.kt, .../User.kt
Store 엔티티 추가(테이블 매핑, 필드, 팩토리 newStore). StoreStatus enum 추가. User의 컬럼 길이 제약(이름 100, 전화번호 30) 및 newUser에서 이름 trim 적용.
Order & Look Domain
src/main/kotlin/com/dh/baro/order/domain/Order.kt, src/main/kotlin/com/dh/baro/look/domain/Look.kt
Order, Look@AggregateRoot 추가. Order의 status 컬럼명을 order_status로 변경하고 items 연관에 fetch = FetchType.LAZY 명시.
Product Domain & Application
src/main/kotlin/com/dh/baro/product/domain/Product.kt, .../application/ProductCreateCommand.kt, .../domain/service/ProductService.kt
ProductstoreId 컬럼/생성자 인자 추가. ProductCreateCommand에 storeId 추가. Service에서 newProduct 호출에 storeId 전달.
Product Presentation
src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt, .../presentation/dto/ProductCreateRequest.kt, .../presentation/swagger/ProductSwagger.kt
요청 DTO에 storeId: Long(@positive) 추가 및 toCommand 매핑 업데이트. Swagger 예제에 storeId, imageUrls 반영. 컨트롤러 시그니처 포맷 정리(기능 변경 없음).
Tests / Fixtures
src/test/kotlin/com/dh/baro/order/Fixture.kt, src/test/kotlin/com/dh/baro/product/domain/Fixture.kt, src/test/kotlin/com/dh/baro/product/domain/ProductServiceTest.kt
테스트/픽스처에서 Product 생성 및 ProductCreateCommand 생성 시 storeId = 123L 반영.

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
Loading

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 Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#18

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 or @coderabbitai 요약 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.

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: 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.storeId
src/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,
     ): ProductCreateResponse
src/main/kotlin/com/dh/baro/identity/domain/Store.kt (3)

21-23: @lob와 columnDefinition(MEDIUMTEXT) 중복 지정 — 하나로 통일 권장

동일 필드에 @LobcolumnDefinition = "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.

📥 Commits

Reviewing files that changed from the base of the PR and between bf884d7 and b22d119.

📒 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 반영 LGTM

Command에 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 문자열 매핑, 시간 감사 상속까지 일관성 있게 잘 잡혀 있습니다.
  • newStoreDRAFT로 초기화되는 것도 도메인 의도에 부합합니다.
  • 불변 키(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

@S-BARO S-BARO deleted a comment from coderabbitai bot Aug 16, 2025
@S-BARO S-BARO deleted a comment from coderabbitai bot Aug 16, 2025
@S-BARO S-BARO deleted a comment from coderabbitai bot Aug 16, 2025
@S-BARO S-BARO deleted a comment from coderabbitai bot Aug 16, 2025
@S-BARO S-BARO deleted a comment from coderabbitai bot Aug 16, 2025
@S-BARO S-BARO deleted a comment from coderabbitai bot Aug 16, 2025
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: 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.

📥 Commits

Reviewing files that changed from the base of the PR and between b22d119 and 3f706c1.

📒 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으로 변경하지 않아도 무방합니다.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] Store 도메인 생성 및 예외 처리 구조 개선

2 participants