[service] 메일을 보내는 방법 (event 를 사용하는 이유)

GuruneLee·2023년 1월 1일
0

Let's Study 공부해요~

목록 보기
34/36
post-thumbnail

문제 상황

API 개발을 하며 다음과 같은 요구사항이 있었다.

  • 어느 서비스에 등록한 사용자가 등록을 철회하려 한다
  • 등록 철회 시, 사용자에게 email이 전송된다
  • 등록 철회 절차에서 Transaction commit 실패로 인한 Rollback이 일어나면, email이 전송되면 안된다.

해결책

해결 1. 이메일을 트랜잭션 종료 후 보내도록 한다.

다음과 같이 등록 철회를 처리하는 메서드가 있다. 이 메서드는 정말 등록 철회만 한다.

@Transactional
fun withdraw(subjectKey: Long) {
    val subject = subjectRepository.findByKey(subjectKey)
    subject.withdraw()
}

철회 후 이메일이 전송되고, 만약 롤백이 되면 전송이 되지 않게 하려면 withdraw() 트랜잭션 종료 후 email 을 전송하면 되는 일이다.

fun withdrawAndSendEmail(subjectKey: Long) {
    withdraw(subjectKey)
    sendEmail(subjectKey, "you're withdrawn")
}

다만, 이를 SpringMVC의 Controller-Service-Domain 레이어에서 구현하려면, sendEmail의 책임이 Controller 에게 넘어가게 된다.

@Controller
class WithdrawController(
    private val withdrawService: WithdrawService
    private val emailService: EmailService
) {
    @Put(/withdraw/{subjectKey})
    fun withdraw(subjectKey:Long) {
       withdrawService.withdraw(subjectKey)
        emailService.send(subjectKey, "you're withdrawn")  
    }
}

혹은, withdrawService 내에서 또 트랜잭션을 나눠 갖가 트랜잭션을 가진 두 함 수를 묶는 함수를 만들어 사용할 수 있다.

그러나, 위 두 방법 모두 객체의 책임 부여 측면에서 좋지 않아 보인다.

가장 좋은 방법은 withdraw 서비스 메서드에서 메일까지 보내주는 것이다 (철회시키는 놈이 메일을 보내달라고 하는게 가장 자연스럽지 않은가?)

해결 2. 이벤트를 발행한다.

다음 코드를 보자

@Transactional
fun withdraw(
    subjectKey: Long,
) {
    val subject = subjectRepository.findByKey(subjectKey)
    subject.withdraw()
 
    publisher.publishEvent(
        ApplicantWithdrawnEvent()
    )
 
    save(subject)
}
 
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
fun listen(event: ApplicantWithdrawnEvent) {
    emailSender.send()
}
  • withdraw 함수에선 subject.withdraw() , ApplicantWithdrawEvent를 발행한다.
  • 그런데, 그냥 @EventListener 가 아닌 @TransactionalEventListener 를 사용하였다. (이 블로그에 설명이 잘 돼있다)
    • @TransactionalEventListener : event publisher의 트랜잭션에 맞춰 event를 handling 할 수 있게 함
    • 예를 들어, 위 코드에서 save가 실패해도 아직 이벤트가 발생하지 않았으므로 이벤트 발행도 롤백하는 형태가 됨 (일반적인 @EventListener를 사용했다면 이미 이벤트가 발행된 후에 롤백 되므로, 이벤트 발행을 막을 수 없다)

@Transactional 로 묶인 트랜잭션에 @TransactionalEventListener가 함께 묶여서 이벤트를 컨트롤 할 수 있게된다.

1의 방법에 비해 책임의 분배가 잘 되어있다고 느꼈고, 또 이 책임의 분배를 AOP를 통해 잘 풀어낸 사례라고 생각한다.

profile
Today, I Shoveled AGAIN....

1개의 댓글

comment-user-thumbnail
2024년 1월 4일

지금 제시해주신 2가지 방법 모두 다음의 문제를 가질 것이라 예상됩니다.

  1. email send 요청이 실패한다면 api 는 실패
  2. email send 의 latency 가 오래 걸린다면, 그 만큼 api 응답은 느려짐.

어떤 방식으로 해결할 수 있을까요?

답글 달기