API 개발을 하며 다음과 같은 요구사항이 있었다.
다음과 같이 등록 철회를 처리하는 메서드가 있다. 이 메서드는 정말 등록 철회만 한다.
@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 서비스 메서드에서 메일까지 보내주는 것이다 (철회시키는 놈이 메일을 보내달라고 하는게 가장 자연스럽지 않은가?)
다음 코드를 보자
@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()
}
@Transactional 로 묶인 트랜잭션에 @TransactionalEventListener가 함께 묶여서 이벤트를 컨트롤 할 수 있게된다.
1의 방법에 비해 책임의 분배가 잘 되어있다고 느꼈고, 또 이 책임의 분배를 AOP를 통해 잘 풀어낸 사례라고 생각한다.
지금 제시해주신 2가지 방법 모두 다음의 문제를 가질 것이라 예상됩니다.
어떤 방식으로 해결할 수 있을까요?