Loopers 2기

재고 차감, 검증은 몇 번 해야 할까?

고구마와 감자 2025. 11. 14. 17:41

재고 차감, 검증은 몇 번 해야 할까?

TL;DR: 주문 생성 시 재고 검증과 차감이 중복·분산돼 혼란스러웠다. 검증을 한 곳에 명확히 두고, 메서드 호출 방식을 통일하는 게 핵심임을 배웠다.


문제 발견

주문 생성 로직을 구현하다 보니 재고 처리 방식이 어색했다.

// Order.create()
product.validateStock(quantity)   // 검증
OrderItem.of(product, quantity)

// OrderService.createOrder()
order.items.forEach {
    it.product.decreaseStock(it.quantity)  // 차감만
}
  • Order 생성 시 검증
  • OrderService에서 차감검증을 두 번? 한 번? 헷갈렸다.

Product에 메서드가 3개나…

fun validateStock(quantity: Quantity) { ... }           // 검증만
fun decreaseStock(quantity: Quantity) { ... }           // 차감만 (VO 내부 검증)
fun decreaseStockWithValidation(quantity: Quantity) { ... } // 검증+차감

"어느 걸 써야 정답이지?" → 일관성 없는 설계의 신호


원인

  • decreaseStock()은 재사용성을 위해 검증 없이 설계
  • Stock VO 내부에도 검증 존재 → 검증이 두 곳에 중복
  • 에러 메시지에 상품명을 넣고 싶어 Product에도 검증 추가

선택지

방식 설명 장점 단점

1. 검증+차감 통합 decreaseStock()이 항상 검증 호출 측 부담 ↓ VO 검증 중복
2. VO 검증만 믿기 엔티티 검증 제거 중복 없음 에러 메시지 제한

내가 선택한 방식: 2번 + 타협

// Product.kt
fun decreaseStock(quantity: Quantity) {
    stock = stock.decrease(quantity.value)  // VO가 검증
}

fun validateStock(quantity: Quantity) {     // 상품명 포함 에러용
    if (!hasEnoughStock(quantity)) {
        throw CoreException(ErrorType.INSUFFICIENT_STOCK,
            "재고 부족. 상품: $name (요청: ${quantity.value})")
    }
}
  • Order.create() → validateStock() (구체적 에러)
  • OrderService → decreaseStock() (VO 검증 위임)

여전히 불편한 점

  1. validateStock()이 public이라 오용 가능성
  2. 검증 후 차감 사이 동시성 문제 (트랜잭션/락 필요)
  3. 메서드 2개 → 여전히 많음

배운 점

  1. 검증은 한 곳에 명확히 (VO or 엔티티)
  2. 메서드 많으면 설계 의심
  3. 일관성 > 완벽함

다음엔 이렇게

fun decreaseStock(quantity: Quantity) {
    if (!hasEnoughStock(quantity)) {
        throw CoreException(ErrorType.INSUFFICIENT_STOCK,
            "재고 부족. 상품: $name (요청: ${quantity.value}, 재고: ${stock.quantity})")
    }
    stock = stock.decrease(quantity.value)
}

메서드 하나, 검증+차감+에러 메시지 통일


재고 차감은 단순해 보이지만, "누가 검증할까? 몇 번 할까? 어떻게 일관되게 할까?" 이 고민이 좋은 설계를 만든다.

다음엔 더 간결하게!