Event 처리는 비동기가 아니다?
728x90
반응형

비동기 처리를 위해서 이벤트를 발행하는 방식을 선택하는 경우가 있다. 나 역시도 그랬는데 사용하다가 궁금증이 생겼다. 그냥 Async 어노테이션을 달아 놓은 것과 EventListener를 적용한 것은 어떤 차이가 있을까? 결과부터 얘기하자면 모든 Event 처리 방식은 비동기가 아니다. 그것을 확인할 수 있는 테스트를 해보았다. 해당 테스트는 정아마추어님 블로그상당히 많이 참고했다.

 

테스트 전 준비

 

먼저 Event 모델을 만들었다. EventListener를 그냥 사용했을 때와 Asnyc 어노테이션과 함께 사용했을 때를 구분하기 위한 모델을 각각 만들었다.

data class SampleEvent(
    val name: String,
    val email: String
)

// 비동기용
data class SampleAsyncEvent(
    val name: String,
    val email: String
)

 

이 이벤트를 발행할 Controller를 추가한다.

@RestController
@RequestMapping("sample")
class SampleController(
    private val applicationEventPublisher: ApplicationEventPublisher
) {

    @PostMapping("/sync")
    fun createSample(@RequestBody sampleEvent: SampleEvent) {
        logger.info("sync  >>> Controller Thread ID : {}", Thread.currentThread().id)
        applicationEventPublisher.publishEvent(sampleEvent)
        logger.info("sync  >>> after publishEvent!! : {}", Thread.currentThread().id)
    }

    @PostMapping("/async")
    fun createSampleAsync(@RequestBody sampleAsyncEvent: SampleAsyncEvent) {
        logger.info("async >>> Controller Thread ID : {}", Thread.currentThread().id)
        applicationEventPublisher.publishEvent(sampleAsyncEvent)
        logger.info("async >>> after publishEvent!! : {}", Thread.currentThread().id)
    }

}

 

이제 이벤트를 수신할 부분을 만들 차례인데 그 전에 Application에서 Async를 허용하기 위한 EnableAsync 어노테이션을 추가한다.

@SpringBootApplication
@EnableAsync
class DemoApplication

fun main(args: Array<String>) {
    runApplication<DemoApplication>(*args)
}

 

이제 이벤트를 수신할 서비스와 핸들러를 만든다. 2개를 만드는 이유는 쓰레드가 어떻게 구분되는지 확인하기 위해서다.

@Service
class SampleService {

    @EventListener(SampleEvent::class)
    fun createSample(sampleEvent: SampleEvent) {
        logger.info("sync  >>> SampleService Thread Id : {}, event name : {}", Thread.currentThread().id, sampleEvent.name)
    }

    @EventListener(SampleAsyncEvent::class)
    @Async
    fun createSampleAsync(sampleAsyncEvent: SampleAsyncEvent) {
        logger.info("async >>> SampleService Thread Id : {}, event name : {}", Thread.currentThread().id, sampleAsyncEvent.name)
    }

}
@Component
class SampleEventHandler {

    @EventListener
    fun sampleEventListener(sampleEvent: SampleEvent) {
        logger.info("sync  >>> TempComponent Thread Id : {}, event name : {}", Thread.currentThread().id, sampleEvent.name)
    }

    @EventListener
    @Async
    fun sampleAsyncEventListener(sampleAsyncEvent: SampleAsyncEvent) {
        logger.info("async >>> TempComponent Thread Id : {}, event name : {}", Thread.currentThread().id, sampleAsyncEvent.name)
    }

}

 

 

테스트 코드 작성 및 출력

 

이제 테스트코드를 작성해보자. 각각을 별도로 호출해서 한눈에 쓰레드 구분과 호출 순서를 확인할 수 있도록 하자.

@Test
    fun testSampleEvent() {
        val url = "http://localhost:$localServerPort/sample/sync"
        val urlAsync = "http://localhost:$localServerPort/sample/async"
        val param = mapOf("name" to "test", "email" to "sample@sample.com")
        restTemplate.postForObject<SampleEvent>(url, param)
        Thread.sleep(500)
        restTemplate.postForObject<SampleAsyncEvent>(urlAsync, param)
    }

 

위 테스트를 구동해보면 아래와 같은 결과를 얻을 수 있다.

sync  >>> Controller Thread ID : 29
sync  >>> SampleService Thread Id : 29, event name : test
sync  >>> TempComponent Thread Id : 29, event name : test
sync  >>> after publishEvent!! : 29
async >>> Controller Thread ID : 30
async >>> after publishEvent!! : 30
async >>> SampleService Thread Id : 42, event name : test
async >>> TempComponent Thread Id : 43, event name : test

 

 

테스트 분석

sync로 시작하는 로그를 보자. 쓰레드 ID가 동일하다. 누가봐도 동기처리 형태이다. 즉, Event 처리방식을 적용한다고 비동기 처리를 하는 것은 아니라는 뜻. 이벤트 처리는 말 그대로 이벤트를 발행하는 것 자체가 목적이다. 발행되고 수신하는 프로세스일 뿐 이게 비동기 처리는 아니다. 비동기로 동작하기 위해서는 별도의 비동기 처리 구현이 필요하다. @Async를 추가해서 비동기로 동작한 로그를 보자. Controller와 Service, Handler 모두 쓰레드가 다르다. 이벤트 방식을 비동기로 처리한 모습을 볼 수 있다.

 

이벤트 방식을 비동기적으로 사용을 많이 한다고해서 단순히 비동기 동작을 목적으로 사용해서는 안된다. 서비스 간 결합, 트랜잭션 등을 고려해서 하나의 기능이 부가적인 작업에 의존적이지 않도록 하고 동시에 부가적인 작업을 어디서든 호출할 수 있도록 하고 싶다면 이벤트 처리를 권장한다.

 

 

 

참고자료
- Spring ApplicationEvent 비동기로 처리될 것만 같지?
- Transaction을 주의하여 Async 사용하기
- Async를 이용하여 EventListener 사용하기
- Spring Event + Async + AOP 적용해보기

.

728x90
반응형