skarltjr / Memory_Write_Record

나의 모든 학습 기록
0 stars 0 forks source link

Issue - 트랜잭션 commit시점시 발생하는 ObjectOptimisticLockingFailureException예외 catch하기 #101

Open skarltjr opened 2 years ago

skarltjr commented 2 years ago

기존 코드 :

@Transactional
@KafkaListener(topics = "orderCompletedEvent", groupId = "product", containerFactory = "orderCompletedEventListener")
public void consumeOrderCompletedEvent(Events.OrderCompletedEvent event) {
    Product product = productService.findById(event.getProductId());
    try {
        product.orderProduct(event.getOrderedProductCount());
    } catch (ObjectOptimisticLockingFailureException e) {
        log.error("동시 접근으로인한 재고 수정 실패");
    }
}

문제 :

1. ObjectOptimisticLockingFailureException은 트랜잭션 커밋 시점에 충돌여부를 확인하여 예외를 발생시킨다
2. 그럼 지금 위 코드에서 catch는 정말로 예외를 catch할 수 있을까?

-> @Transactional을 통해 명시적으로 트랜잭션을 시작한다. 동시에 영속성 컨텍스트를 연다
-> 따라서 커밋 시점은 트랜잭션이 종료되는, 즉 해당 매서드가 종료되는 시점에 커밋이 발생한다
-> 즉 매서드 종료 후 커밋이 되기때문에 catch에 절대 걸리지않는다.

해결 방안 :

우리의 프로젝트에서 facade는 도메인 상태정보를 갖지 않는 협업의 공간으로 활용하기로 약속했다.
productService는 상품의 도메인 서비스다
즉 facade는 협업의 공간이다. 이를 활용한다

해결 방법 : 
1. 현재 문제점은 매서드 종료시점에 발생하는 예외를 매서드 내부에서 잡으려고하는 바보짓
-> 자바의 예외는 외부 내부 매서드가 존재한다고 했을 때 내부의 매서드가 throw하면 외부의 매서드에서 이를 catch할 수 있다.
2. 따라서 외부의 매서드로 한 겹 감싼다.
-> 아래 코드에서 editProductStock()매서드가 트랜잭션을 시작하면서 영속성 컨텍스트를 열고 매서드 종료시점에 변경감지를 통해 update
-> 이 때 version 충돌이 일어나면 예외를 던진다.
-> 그럼 외부 facade의 consumeOrderCompletedEvent()매서드는 2번에서 발생한 예외를 catch할 수 있고 이 방법으로 해결

---- facade 
@KafkaListener(topics = "orderCompletedEvent", groupId = "product", containerFactory = "orderCompletedEventListener")
public void consumeOrderCompletedEvent(Events.OrderCompletedEvent event) {
    try {
        productService.editProductStock(event);
    } catch (ObjectOptimisticLockingFailureException e) {
        log.error("동시 접근으로인한 재고 수정 실패");
    }
}

----- productService
@Transactional
public void editProductStock(Events.OrderCompletedEvent event) throws ObjectOptimisticLockingFailureException{
    Product product = productService.findById(event.getProductId());
    product.orderProduct(event.getOrderedProductCount());
}

아쉬운 점 :

사실 매서드를 분리하고 트랜잭션 전파 레벨 new를 통해 항상 새로운 트랜잭션을 시작하도록하면 해결할 수 있을줄알았는데 x
-> 아래 코드에서 editProductStock()가 시작할때 트랜잭션을 시작하고 종료할 때 커밋하면서 예외를 던질 줄 알았는데 x

@KafkaListener(topics = "orderCompletedEvent", groupId = "product", containerFactory = "orderCompletedEventListener")
public void consumeOrderCompletedEvent(Events.OrderCompletedEvent event) {
    try {
        editProductStock(event);
    } catch (ObjectOptimisticLockingFailureException e) {
        log.error("동시 접근으로인한 재고 수정 실패");
    }
}

@Transactional(propagetion = Propagation.REQUIRES_NEW)
protected void editProductStock(event) throw ObjectOptimisticLockingFailureException{
    Product product = productService.findById(event.getProductId());
    product.orderProduct(event.getOrderedProductCount());
}
skarltjr commented 2 years ago

추가 ++

facade보단 별도의 event listen용 클래스에서 동일 내용을 다루기로 했다. 
facade는 협업의 공간 및 단일 제어판으로 사용하며 event listen의 책임은 별도의 listener에게 위임한다