예외 발생시 이벤트 처리하기

2024. 9. 11. 22:18프로젝트/[EATceed] 몸무게 증량 어플

728x90

 

EATceed 프로젝트를 진행하며, 회원가입 API에 아래와 같은 요구사항이 추가되었습니다.


이메인 인증 미완료시 이메일 재발송하기

 

EATceed에서는 이메일 인증을 완료하지 않은 사용자가 회원가입을 다시 시도할 경우, 이메일을 재전송하고, "이미 존재하는 이메일입니다. 이메일 인증을 해주세요"와 같은 응답을 반환해야합니다.

 

이메일 재전송 API를 만들 수도 있었지만 사용자 경험 저하 및 AOS 측 이메일 저장 문제로 인해 회원가입 API에서 "이메일 재전송"을 추가하기로 하였습니다.

 

이벤트를 사용해서 해결하기

기존에 AlreadyEmailException이 터지면  @RestControllerAdvice를 사용해 특정 예외 에 맞는 응답을 반환하고 있었습니다.

따라서, 응답에 관한 요구사항은 해결되었습니다.


다음 요구사항을 만족하기려면, 예외가 발생하더라도 이메일을 보내야합니다.

특정 비즈니스 예외를 발생시키는 것과 이메일을 재전송하는 것은 독립적으로 수행되어야한다고 생각했기 때문에 스프링 이벤트를 사용하기로 하였습니다.

 

 

@EventListener 

 

Spring에서 Event를 구독하는 방법은 @EventListener 또는 @TransactionEventListner를 사용하는 것입니다.

기본적으로 스프링에서의 Event는 동기 방식으로 동작하는데, 이는 이벤트 발행과 이벤트 구독이 하나의 트랜잭션으로 묶일 수 있다는 뜻입니다.

하나의 트랜잭션으로 묶인다는 말은 이벤트를 발행하는 곳과 이벤트를 구독하는 곳 중 하나라도 예외가 발생하면 해당 트랜잭션에 rollback mark가 묻어 롤백이 된다는 뜻입니다.

 

따라서, @EvnetListner만 사용한다면 위의 요구사항인 "예외가 발생하더라도 이벤트를 발행/구독"을 만족시키지 못합니다. 

 

 

그렇다면 어떻게 해야할까요??

 

 

@EventListener와 @Async 사용

 

Listner에 @EvnetListner와 @Async를 같이 사용한다면, 이벤트를 발행하는 곳에서 예외가 발생하더라도 이벤트를 정상적으로 구독할 수 있습니다. 왜냐하면, @Async를 사용하면, 기존의 스레드를 사용하지 않고 새로운 스레드를 사용하기 때문에 같은 기존의 트랜잭션을 사용하지 않기때문입니다. (새로운 커넥션을 얻어와서 사용한다.)

즉, 이벤트를 발행하는 곳에서 예외가 발생하여 롤백되더라도 이벤트 리스너는 영향을 받지 않고 정상적으로 작동할 수 있습니다.

 

 

이에대한 코드는 아래와 같습니다.


이벤트 발행

@Service
@RequiredArgsConstructor
public class ExampleService {
    ...

    @EventPublisherStatus
    @Transactional(readOnly = true)
    public void execute(SomeThingCommand command) {
        ...
        if (isSomeThing()) {
            // 예외 발생
        }
        Events.raise(IncompleteSignUpEvent.from(command.XXX()));
        throw AlreadyEmailException.EXECPTION;
    }

}

 

이벤트 구독

 

@Component
@RequiredArgsConstructor
public class InCompleteSignUpMemberEventListener {

    ...

    @EventListener(classes = IncompleteSignUpEvent.class)
    @Async
    public void handle(IncompleteSignUpEvent event) {
        ...
        // 이메일 재전송
    }
}

 

 

 

감사합니다!

728x90