Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Claude Code 작업 지시사항

이 프로젝트에서 코드 작업 시 반드시 따라야 할 지시사항입니다.

## 코드 작성 원칙

### 1. 로깅 규칙
- 모든 서비스 레이어 메서드에 SLF4J 로깅 추가
- 로그 레벨 가이드:
- `info`: 비즈니스 로직 시작/완료, 이벤트 발행
- `debug`: 조회 작업, 상세 디버깅 정보
- `error`: 예외 발생, 실패 케이스
- 로그 형식: 구조화된 로깅 (변수는 {} placeholder 사용)
```kotlin
logger.info("Creating order - customerId: {}, productId: {}", customerId, productId)
```

### 2. 문서화 규칙
- 모든 public 클래스와 메서드에 KDoc 추가
- KDoc 포함 사항:
- 클래스: 역할과 책임 설명
- 메서드: 파라미터, 반환값, 발생 가능한 예외
- 예시:
```kotlin
/**
* 주문 서비스 구현체
*
* Clean Architecture의 Application Layer에 위치하며,
* 주문 생성, 조회, 상태 변경 유스케이스를 처리합니다.
*/
```

### 3. 트랜잭션 관리
- 읽기 전용 작업: `@Transactional(readOnly = true)`
- 쓰기 작업: `@Transactional`
- 클래스 레벨보다 메서드 레벨 어노테이션 우선

### 4. 예외 처리
- 예외 발생 시 반드시 로깅
- 도메인 특화 예외 사용 (커스텀 예외 클래스)
- 예외 메시지에 컨텍스트 정보 포함

### 5. 이벤트 처리
- 이벤트 발행은 비즈니스 로직 완료 후 수행
- 이벤트 발행 시 로깅 추가
- 이벤트 발행 실패 처리 전략 고려

## 아키텍처 가이드

### Clean Architecture 준수
- **Domain Layer**: 비즈니스 로직, 엔티티, 도메인 서비스
- **Application Layer**: 유스케이스, 서비스, 포트
- **Adapter Layer**: 컨트롤러, 리포지토리, 외부 연동

### 이벤트 기반 아키텍처
- 서비스 간 통신은 이벤트 기반
- Saga 패턴 적용 (분산 트랜잭션)
- 이벤트 발행/구독 구조 유지

## 코드 리뷰 체크리스트

작업 완료 전 다음 사항을 확인하세요:

- [ ] 로깅이 적절히 추가되었는가?
- [ ] KDoc 문서화가 완료되었는가?
- [ ] 트랜잭션 범위가 적절한가?
- [ ] 예외 처리가 적절한가?
- [ ] 테스트 코드가 작성되었는가?
- [ ] 이벤트 발행/구독이 올바르게 구현되었는가?
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,87 @@ import io.readingrecord.order.application.port.out.OrderPersistencePort
import io.readingrecord.order.domain.command.PlaceOrderCommand
import io.readingrecord.order.domain.command.UpdateOrderStatusCommand
import io.readingrecord.order.domain.model.Order
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

/**
* 주문 서비스 구현체
*
* Clean Architecture의 Application Layer에 위치하며,
* 주문 생성, 조회, 상태 변경 유스케이스를 처리합니다.
*/
@Service
@Transactional
class OrderService(
private val orderPersistencePort: OrderPersistencePort,
private val orderEventPublisher: OrderEventPublisher
) : OrderUseCase {

private val logger = LoggerFactory.getLogger(javaClass)

/**
* 주문 생성
*
* @param command 주문 생성 커맨드
* @return 생성된 주문
*/
override fun placeOrder(command: PlaceOrderCommand): Order {
val order = command.toPlaceOrder()
logger.info("Placing order - customerId: {}, productId: {}, quantity: {}",
command.customerId, command.productId, command.quantity)

val order = command.toPlaceOrder()
val savedOrder = orderPersistencePort.save(order)

logger.info("Order placed successfully - orderId: {}", savedOrder.id)

val orderPlacedEvent = savedOrder.toOrderPlacedEvent()
orderEventPublisher.publishOrderPlacedEvent(orderPlacedEvent)

logger.info("OrderPlaced event published - orderId: {}", savedOrder.id)

return savedOrder
}

/**
* 주문 조회
*
* @param orderId 주문 ID
* @return 조회된 주문
* @throws OrderNotFoundException 주문을 찾을 수 없는 경우
*/
@Transactional(readOnly = true)
override fun getOrder(orderId: Long): Order {
val savedOrder = orderPersistencePort.findById(orderId)
?: throw OrderNotFoundException("주문을 찾을 수 없습니다. orderId: ${orderId}")
logger.debug("Fetching order - orderId: {}", orderId)

return savedOrder
return orderPersistencePort.findById(orderId)
?: throw OrderNotFoundException("주문을 찾을 수 없습니다. orderId: $orderId")
}

/**
* 주문 상태 변경
*
* @param command 상태 변경 커맨드
* @throws OrderNotFoundException 주문을 찾을 수 없는 경우
* @throws OrderUpdateFailedException 상태 변경에 실패한 경우
*/
override fun updateOrderStatus(command: UpdateOrderStatusCommand) {
orderPersistencePort.findById(command.orderId)
logger.info("Updating order status - orderId: {}, newStatus: {}",
command.orderId, command.newStatus)

val existingOrder = orderPersistencePort.findById(command.orderId)
?: throw OrderNotFoundException("주문을 찾을 수 없습니다. orderId: ${command.orderId}")

val success = orderPersistencePort.updateStatus(command.orderId, command.newStatus)

if (!success) {
logger.error("Failed to update order status - orderId: {}", command.orderId)
throw OrderUpdateFailedException("주문 상태 업데이트에 실패했습니다. orderId: ${command.orderId}")
}

logger.info("Order status updated successfully - orderId: {}, oldStatus: {}, newStatus: {}",
command.orderId, existingOrder.status, command.newStatus)

//TODO 주문 상태 변경 이벤트 발행
}
}