비동기 처리를 위해서 이벤트를 발행하는 방식을 선택하는 경우가 있다. 나 역시도 그랬는데 사용하다가 궁금증이 생겼다. 그냥 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 적용해보기
.
'Spring' 카테고리의 다른 글
비밀번호 검증용 Custom validation 어노테이션을 만들고 테스트까지 (0) | 2022.09.28 |
---|---|
Enum Converter와 Enumerated 어노테이션 (0) | 2022.08.19 |
스프링부트에서 쿠키(Cookie)를 구워보자 (0) | 2022.07.28 |
Mockito가서 몰디브 한 잔, 긔? (with Kotlin) (0) | 2022.07.20 |
data.sql이 동작하지 않을 때, 의심해봐야 할 것 (0) | 2022.07.13 |
Comment