스프링부트 이벤트 구현
이 글은, 스프링부트에서 제공하는 @EventListener에 대해서 소개한다.
목차
1. Overview : 글 소개
2. Spring Event(1) : 스프링 4.2 이전 버전에서의 이벤트
3. Spring Event(2) : 스프링 @EventListener
4. Spring ApplicationEvent : 스프링 부트에서 제공하는 이벤트
스프링 개발 경험이 많은 개발자는 지루한 이 글을 전부 읽지 말고,
@EventListener 에 대해서 소개하는 3장부터 읽는 것을 추천한다.
복잡한 도메인을 개발하다 보면, 도메인 사이에 강한 의존성으로 인해서 시스템이 복잡해지는 경우가 발생한다. 이 글에서는 복잡한 도메인 의존성을 줄이기 위한 방법 중 이벤트에 대해서 소개한다.
샘플코드는 상품 주문 도메인에 대한 사례이다. Order라는 이름의 주문 모델은, ChargeService 라는 응용 서비스를 사용해서 결제를 요청한다. (주문 도메인에서는 결제 처리 기능을 호출하게 된다.)
상품 상태에 대한 정의는 아래와 같다.
ChargeService 는 결제 로직을 수행하는 응용서비스이다.
Order 클래스의 pay 메서드에서 결제를 요청하는데, ChargeService 클래스의 charge() 메서드를 실행한다. startPay(), completedPay() 메서드에서는 상품 상태를 변경한다. Order 도메인에서 상품 상태에 대한 변경 역할을 담당한다.
상품 상태를 변경하는 역할은 Order 의 역할이 맞다. 하지만, 결제 로직은 Order 모델의 핵심 역할이 아니다. 결제는 ChargeService 에서 담당하며, 결제를 처리하는 응용서비스를 주입받아서 실행한다.
주문을 담당하는 Order에서 결제 로직을 실행하기 때문에, 주문 로직과 결제 로직이 섞여 있다.
어떻게 하면 의존성을 줄일 수 있을까?
이 글에서는, 도메인 모델 사이의 의존성을 줄일 수 있는 방법으로 이벤트를 소개한다.
참고로 이 글은 최범균님의 "DDD Start 도메인 주도 설계 구현과 핵심 개념 익히기"라는 책의 사례와 매우 유사하지만, 책의 내용을 그대로 인용하지 않았다. 해당 책은 DDD 입문자에게 괜찮은 책이지만 현재는 품절되었다. 회사 책장에 한 권씩은 굴러다니고 있을 테니, 찾아서 읽어보기를 바란다.
이번 장에서는 스프링 4.2 이전 버전에서의 스프링 이벤트에 대해서 소개한다. 스프링 4.2 버전부터 @EventListener 어노테이션을 사용할 수 있으며, 해당 방법은 Spring Event(2)에서 확인하길 바란다.
생략
샘플 코드의 패키지 레이어는 아래와 같다.
ApplicationEvent를 상속받아서 Event 객체를 정의하자.
이벤트 리스너는 ApplicationListener 인터페이스를 구현해야 한다.
이벤트가 발생했을 때 수신처리하는 구문은, onApplicationEvent 메서드에 작성한다. 리스너는 결제 서비스를 처리해야하기 때문에, ChargeService 응용서비스에 대한 의존성을 갖는다.
Order 도메인 모델에서는 이벤트를 발생시킨다. ApplicationEventPublisher를 사용해서 이벤트를 발생시킬 수 있는데, 위에서 정의했던 ChargeRequestEvent 객체를 생성해서 발행해야 한다.
정리해보면, Order 도메인에서는 ChargeService를 직접 실행하지 않는다. Order에서는 이벤트 객체를 전달하며, 이벤트를 핸들링하는 리스너에서 결제 로직을 실행하게 된다. 즉, Order와 ChargeService 두 클래스 사이에 의존성이 약하다.
OrderService(응용서비스)에서는 Order 도메인 모델의 pay() 메서드를 실행하면서 ApplicationEventPublisher를 전달하는 방식으로 구현하였다.
이 글을 보는 개발자분들 중에서, publisher를 Order 도메인 모델에 전달하는 방법이 부자연스럽다고 생각할 수 있다. 다른 방법으로는, OrderService에서 publisher를 직접 사용해서 이벤트를 직접 발생시키는 방법도 가능하다. 단, 이 경우에는 도메인 모델에 대한 상세 로직이 응용 서비스 레벨로 나오게 된다.
어떤 방식이 좋은지 허접한 개발자인 필자는 잘 모르겠다. 더 좋은 방법이 있다면 부족한 개발자인 필자에게 꼭 알려주길 바란다. 어쨌든, 이 글은 스프링 Event에 대한 주제이므로, 상세하게 설명하지는 않겠다.
중요한 사실은, publisher 객체의 publishEvent 메서드를 실행해서 이벤트를 발생시킬 수 있다는 점이다. 이때, 이벤트 객체인 ChargeRequestEvent를 생성해서 전달해야 한다.
테스트 코드를 실행하였다.
https://github.com/sieunkr/spring-eventlistener/tree/master/spring-application-event
스프링 4.2부터 사용 가능한 @EventListener를 사용해서 코드를 개선해보자.
스프링 4.2부터 사용 가능한 @EventListener 어노테이션에 대해서 알아보자.
생략
위에서 소개했던 샘플 코드의 방법(ApplicationEvent, ApplicationListener를 상속, 구현)할 필요가 없다. Event를 정의할 때 ApplicationEvent를 상속받을 필요가 없다.
이벤트 리스너는 ApplicationListener를 구현할 필요가 없고, @EventListener 어노테이션을 메서드 상단에 선언해주면 이벤트 리스너로 등록이 된다. 매개변수에 Event 클래스를 정의하면 해당 클래스 이벤트가 발생했을 때 이벤트를 수신해서 처리할 수 있다.
아주 심플하다.
같은 이벤트에 대한 수신을 2개 이상의 핸들러 메서드에 정의할 수 있다. ChargeRequestEvent 이벤트가 발생하면 선언된 모든 메서드가 실행된다. 이 경우에, Order 어노테이션을 사용해서 메서드 실행 순서를 정의할 수 있다.
이벤트 리스닝을 비동기로 구현해보자. 비동기 메서드가 동작할 수 있도록 최상단 클래스에 @EnableAsync 어노테이션을 선언한다.
비동기로 실행하길 원하는, 메서드에 @Async 어노테이션을 선언한다.
비동기에 대해서는 필자의 예전 글을 꼭 읽어보길 바란다.
https://brunch.co.kr/@springboot/267
https://brunch.co.kr/@springboot/401
이해를 돕기 위해서, 필자가 간단하게 그림을 그렸다.
이벤트 발행하는 주체는 Order 도메인이다. Order 도메인에서 publisher 객체의 publishEvent 메서드로 이벤트를 발생시킨다. 이때, ChargeRequestEvent 객체를 전달한다. 스프링의 ApplicationContext에서 이벤트 채널의 역할을 수행하며, @EventListener 어노테이션으로 선언한 메서드에서 ChargeRequestEvent 이벤트를 받아서 처리하게 된다.
https://github.com/sieunkr/spring-eventlistener/tree/master/spring-event-listener
스프링에서 기본적으로 제공하는 이벤트에 대해서 아주 간단하게 알아보자.
ApplicationEvent를 상속받는 SpringApplicationEvent라는 클래스를 알아보자.
SpringApplicationEvent 클래스는 스프링에서 동작하는 기본적인 이벤트를 정의하는 클래스이다. 해당 클래스를 상속받아서 정의한 이벤트는 아래와 같다.
- ApplicationContextInitializedEvent
- ApplicationEnvironmentPreparedEvent
- ApplicationPreparedEvent
- ApplicationReadyEvent
- ApplicationStartedEvent
- ApplicationFailedEvent
- ApplicationStartingEvent
스프링 애플리케이션이 동작하면 해당 이벤트를 자동으로 발생시켜 줄 것이다. ApplicationStartedEvent 이벤트를 수신하는 메서드를 아래와 같이 작성해보자.
스프링 내부 로직에서 ApplicationStartedEvent 이벤트를 발생시키는데, 해당 리스너는 애플리케이션이 처음 실행할 때 작동하게 된다.
그렇다면, 이벤트는 어디서 발생시켜주는 걸까?
스프링부트를 제일 먼저 실행하는 run 메서드를 살펴보자.
SpringApplication 클래스의 run 메서드를 유심히 살펴보면, listeners.started 메서드가 있다.
메서드를 따라가 보면, publishEvent 해주는 구문을 찾을 수 있다.
어쨋든,
스프링이 처음 실행할 때 비즈니스 로직을 추가하고 싶다면, 스프링의 SpringApplicationEvent를 수신하는 이벤트 리스너를 구현하면 된다.
자세한 건 생략하겠다... 각자 알아서 공부하길 바란다.
이번 글에서는 스프링에서 Event를 사용하는 방법에 대해서 아주 간단하게 소개하였다. 실무에서 꽤 많이 사용하는 기능인데, 의외로 모르는 개발자가 꽤 있는 것 같다. 조금이라도 도움이 되었기를 바라며 글을 마치겠다.