티스토리 뷰

JAVA

Spring EventListener 관련한 이슈

Voyager Woo 2020. 6. 3. 03:20
반응형

비즈니스 로직과 상관없지만 해야하는 일들이 있을 때, Spring 프레임워크에서 제공하는 Event를 사용하여 비즈니스 코드와 비즈니스와 관련 없는 코드의 의존을 분리한다.

예를 들어, 비즈니스 특정 행위를 로그 테이블에 적재해야 할 때, 나의 판단에는 비즈니스의 흐름에 로그 테이블에 적재하는 코드를 넣는 것은 비즈니스 흐름을 읽는 데 방해가 된다. 그래서 이벤트를 발생시키고 해당 이벤트의 Listener 객체가 해당 작업을 수행하도록 한다.

참고; https://javacan.tistory.com/entry/Handle-DomainEvent-with-Spring-ApplicationEventPublisher-EventListener-TransactionalEventListener

 

스프링 ApplicationEventPublisher, @EventListener, @TransactionalEventListener를 이용한 도메인 이벤트 처리

다음과 같은 간단한 이벤트 관련 코드를 만들 일이 생겼다. 도메인 객체가 트랜잭션 범위에서 이벤트를 발생하면 핸들러로 처리 트랜잭션이 커밋된 이후에 이벤트 핸들러에 이벤트 전달해야 ��

javacan.tistory.com

문제

최근에 컨트롤러에서 특정 익셉션이 발생하면, 이벤트를 발행하여 문제를 해결하도록 코드를 작성했다.

@GetMapping(path = ["/{id}"])
fun getSomething(
    @RequestAttribute("appId") appId: Long,
    @PathVariable id: Long
): ApiResponse<Something?> {
    return try {
        ApiResponse.okay(somethingQueryService.getSomething(appId, id))
    } catch (e: SomethingException) {
        log.warn(e) { "problem!" }
        Events.raise(SomethingProblemEvent(id, appId))
        ApiResponse.error("something error")
    }
}
import mu.KotlinLogging
import org.springframework.stereotype.Component
import org.springframework.transaction.event.TransactionalEventListener

@Component
class SomethingProblemEventListener(
    private val somethingProblemService: SomethingProblemService
) {
    private val log = KotlinLogging.logger { }

    @TransactionalEventListener
    fun onSomethingProblem(event: SomethingProblemEvent) {
        log.info { "event: $event" }
        somethingProblemService.handle(event.id, event.appId)
    }
}

 

그런데 이 코드는 동작하지 않았다. 

원인


If the event is not published within an active transaction, the event is discarded unless the fallbackExecution() flag is explicitly set.

@TransactionalEventListener의 경우 트랜잭션 외부에서는 이벤트가 버려진다. 나는 controller에서 해당 이벤트를 발행했기 때문에 이런 문제가 발생했다.

해결

해결책은 @EventListener 를 사용하는 것! 해당 이벤트를 핸들링하는 코드가 익셉션이 발생한 비즈니스 코드와 트랜잭션으로 묶일 필요도 없고 실패하면 다시 실행하면 되는 상황이라 쉽게 결정할  수 있었다.

import mu.KotlinLogging
import org.springframework.context.event.EventListener
import org.springframework.stereotype.Component

@Component
class SomethingProblemEventListener(
    private val somethingProblemService: SomethingProblemService
) {
    private val log = KotlinLogging.logger { }

    @EventListener
    fun onSomethingProblem(event: SomethingProblemEvent) {
        log.info { "event: $event" }
        somethingProblemService.handle(event.id, event.appId)
    }
}

 

반응형
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함