상품을 주문하는 코드를 작성한다. 이 때 상품이 주문되면 저장되어있던 수량이 차감되고, 만약 주문하는 수량이 저장된 수량보다 크다면 주문에 실패하게 된다.
@EventListener
public synchronized void handleOrderCreateEvent(OrderCreateEvent event) {
Option option = optionRepository.findById(event.getOptionId())
.orElseThrow(() -> new FailedToFindException("해당 옵션이 존재하지 않습니다."));
if (option.getQuantity() < event.getQuantity()) {
throw new OutOfStockException("주문할 수 있는 수량을 초과하였습니다.");
}
}
상품을 주문했을때 option에 저장되어 있는 수량을 차감하는 코드다.
이 과제를 해결하는 데에 있어서 수량은 중요한 요소라고 생각했다.
상품 주문 요청 -> 수량 확인 -> 주문 진행
이런 식으로 플로우를 구성하면 주문보다 수량 확인이 먼저 되기 때문에 수량이 부족한 경우에는 주문 자체가 진행되지 않으므로 더 효율적이라고 생각했다.
만약 @EventListener가 붙은 메서드에서 수량이 초과해 예외가 던져지게 되면 기존 트랜잭션에는 어떤 영향을 미칠 것 같냐는 멘토님의 피드백이 있었다.
찾아보니 @EventListener로 처리를 하게 되면 여기서 예외가 던져졌을 때 기존 트랜잭션에는 영향을 미치지 않는다. 기존 트랜잭션에 영향을 주려면 @TransactionalEventListener를 사용해야 된다는 것이다.
그래서 이 부분은 @TransactionalEventListener로 리팩토링해주었다.
그런데 또 중요한건, 해당 로직은 주문에 있어서 중요한 로직이기 때문에,@TrasactionalEventListener의 기본 설정인 AFTER_COMMIT이 아닌, BEFORE_COMMIT으로 설정을 변경해주었다.
두 설정의 차이는 간단하게 설명해보자면, 기존 트랜잭션이 커밋된 이후에 실행되냐 커밋되기 이전에 실행되냐의 차이다.
커밋되고 나서 실행이 되면 이미 주문이 처리가 되는 것이기 때문에 그때 옵션을 체크해도 의미가 없다. 그래서 커밋 이전에 옵션을 체크하고, 옵션 수량이 부족하다면 롤백을 하게끔 코드를 다시 작성했다.
그냥 멀티 스레드 환경을 가정하고 synchronized 키워드도 붙였었는데, 이 부분에 대해서도 피드백을 받았다.
synchronized 키워드는 멀티 프로세스 환경에서는 의미가 없고, 멀티 스레드로 가정한다고 하더라도 모니터락에 의해서 성능에 영향을 준다고 한다.
여기서 멘토님이 제안해주신 방식은 ConcurrentHashMap을 활용해서 객체에 락을 걸어주는 방식인데, 다른 과제를 수행하는 데에도 시간이 조금 촉박했고 중간에 빌드 문제도 발생했기 때문에 해당 방식을 도입해보지는 못했다.
추후에 동시성에 대해서 공부를 해야된다면 그때 다시 공부를 해보려고 한다.