ApplicationEventPublisher
이벤트를 발행하는 Publisher와 이를 감시하는 Observer(또는 Subscriber) 사이의 결합도를 낮추면서 이벤트를 Observer에게 전달하고 싶을 때 사용한다.
회원가입이 완료된 회원에게 가입 완료 메일을 보내는 예제이다
@Service
@Transactional
class MemberService(
private val memberRepository: MemberRepository,
private val applicationEventPublisher: ApplicationEventPublisher
) {
fun signUp(signUpRequestDto: SignUpRequestDto): SignUpResponseDto {
val member = memberRepository.save(signUpRequestDto.toEntity())
applicationEventPublisher.publishEvent(EmailSendEvent(member.email, this))
return SignUpResponseDto(member.id)
}
}
class EmailSendEvent(
val email: String,
source: Any
) : ApplicationEvent(source)
@Component
class EmailEventListener(
private val sesService
) {
@EventListener
fun onEmailSendEventHandler(event: EmailSendEvent) {
// 이메일 전송
sesService.sendEmail(event.email)
}
}
@Component
를 사용해서 빈으로 등록한다.@EventListener
어노테이션을 통해 Handler를 구현한다. 정해진 이벤트가 발생하면 이 어노테이션이 달린 메소드가 실행된다.@Service
@Transactional
class MemberService(
private val memberRepository: MemberRepository,
private val applicationEventPublisher: ApplicationEventPublisher
) {
fun signUp(signUpRequestDto: SignUpRequestDto): SignUpResponseDto {
val member = memberRepository.save(signUpRequestDto.toEntity())
applicationEventPublisher.publishEvent(EmailSendEvent(member.email, this))
// 회원가입 축하 쿠폰 발행 로직
return SignUpResponseDto(member.id)
}
}
@Component
class EmailEventListener(
private val sesService
) {
@TransactionalEventListener
fun onEmailSendEventHandler(event: EmailSendEvent) {
// 이메일 전송
sesService.sendEmail(event.email)
}
}
@EventListener
를 사용할 경우 이벤트를 발행하는 시점에 바로 리스닝을 진행하기 때문에 쿠폰 발행 로직에서 예외가 발생해 회원가입이 진행되지 않아도 이메일을 전송하는 문제가 생긴다.@TransactionalEventListener
으로 리스너를 등록하게 되면 해당 트랜잭션이 Commit된 이후에 리스너가 동작한다.@Async
어노테이션을 지정하고, 프로젝트 최상위 클래스 main 클래스에 @EnableAsync
어노테이션을 지정하면 된다.@Async
추가를 통해 해당 메서드는 기존 스레드와 분리되고 자연스럽게 트랜잭션도 분리된다.@Component
class EmailEventListener(
private val sesService
) {
@Async
@EventListener
fun onEmailSendEventHandler(event: EmailSendEvent) {
// 이메일 전송
sesService.sendEmail(event.email)
}
}
@SpringBootApplication(scanBasePackages = ["jakarta.persistence"])
@EnableAsync
class ExampleApplication {
companion object {
@JvmStatic
fun main(args: Array<String>) {
runApplication<ExampleApplication>(*args)
}
}
}
.
.
.
참고
https://cheese10yun.github.io/event-transaction/
https://tecoble.techcourse.co.kr/post/2020-09-30-event-publish/