Search

Event

개요

Event 란 애플리케이션에서 특정 작업이나 상태 변경이 되었을 때 이를 다른 컴포넌트에 알리는 데 사용된다.
비동기적으로 코드를 실행하고 이를 통해 서비스간의 의존성을 줄이고 느슨한 결합을 가능하게 한다.
이벤트를 발행하는 publish 로직과 이벤트를 수신하는 Listener 로직을 분리하여 작성한다.

Event를 왜 사용할까?

시나리오 가정

만약 결제 기능을 구현해야 한다고 생각해보자
결제서비스는 간단하게 아래의 기능으로 나눌 수 있다.
1.
결제내역 생성 (결제 서비스)
2.
상품재고 차감 (상품 서비스)
3.
사용자의 잔액 차감 (유저 서비스 )
정도가 있을 것이다.
위 작업들은 하나의 논리적인 단위로 처리가 되어야 하는 작업들이다.
만약 사용자의 잔액만 차감을 하고 상품재고를 차감하지 않는다던가 결제내역을 생성하지 않으면 굉장히 큰 오류가 발생하는 것이다.
이 경우에는 그럼 Facade 패턴을 생각해볼 수 있다.
결제 Facade를 만들어 그 안에서 결제 서비스, 상품 서비스, 유저 서비스를 각각 호출하여 반환값을 적절하게 조합하여 사용자게에 반환할 수 있다.
하지만 이 경우에는 트랜잭션 범위에 문제가 있다.
Facade단에 @Transaction 어노테이션을 걸면 트랜잭션의 범위가 커진다.
이는 성능 저하와 DB에 대한 부하로 이어진다.

트랜잭션의 범위

위의 상황에서 1,2번까지의 로직은 성공했는데 3번의 로직에서 실패를 한다면?
1, 2번은 전부 rollback 처리 될 것이다.
만약 서비스의 로직이 커져 3개보다 훨씬 많은 작업을 처리하는 경우에는 더욱 더 트랜잭션은 거대해지고 위와 같은 상황이 더 자주 발생할 것이다…
이 상황에서 Event를 사용하여 트랜잭션의 범위를 줄이고
결제 서비스, 상품 서비스, 유저 서비스의 의존성을 낮추고 각 서비스는 본인이 수행해야 할 로직을 처리한 후 이벤트를 발행하면 된다.
후위 작업을 진행해야 하는 서비스들은 선위 작업을 수행한 서비스가 발행한 EventListen하여 해당 로직을 처리하면 되는 것이다.

느슨한 결합

트랜잭션의 범위 파트에서 설명한 것과 같이 Event를 통해
각 서비스의 로직 구분을 명확히 하고 서비스간 결합도를 낮춘다.
이는 추후 서비스의 유지보수를 쉽게 해주고 오류가 발생하는 곳을 명확히 찾을 수 있다.
시나리오에서는 어플리케이션 프로젝트 내 서비스 로직만 수행을 예로 들었다.
만약 위에서 결제 완료 알림을 카카오톡으로 보낸다던가 결제를 외부 API에 위임한다고 가정해보자
결제알림 전송 카카오톡 외부 API가 실패한 경우
서버 자체 로직인 1~3 로직들은 전부 빠르게 성공을 하였다. 하지만 카카오톡의 오류로 알림 전송 API가 실패하면
앞서 진행한 1~3 로직들도 전부 rollback 된다.
결제 완료 알림 전송 기능이 핵심적인 기능인가? 부터 생각해보자
결제를 완료하고 사용자에게 안내하는 부가적인 기능이지 알림 전송이 가지 않았다고 해서 결제가 실패한 것은 아니라고 봐야 한다.
(결제 알림 전송을 동의하지 않은 유저가 있을수도 있고, 모든 결제 서비스가 사용자에게 알림을 전송하는 것은 아니다…)
그렇다면 알림 외부 API는 결제 완료 시 호출하도록 Event 를 통해 구현하면 외부 API의 성공 실패 여부에 따라
비즈니스 로직이 영향을 받지 않고 외부 API와의 의존성을 제거할 수 있다.

성능

만약 결제를 하고 난 후 부가적인 외부 API나 로직을 수행하는데 3초가 걸린다고 가정해보자
Event 를 사용하지 않고 이를 메인 로직에 함께 묶어 둔다면
아무리 비즈니스 로직이 빠르게 수행된다고 해도 부가적인 로직때문에 응답하기까지 무조건 3초 이상의 시간이 걸리게 된다.
Event로 처리하게 되면 호출한 API에서는 사용자에게 응답을 하고 후에 수행되어야 할 로직을 비동기로 처리할 수 있다.
비즈니스 로직이 0.5s가 소모된다고 가정해보면
Event로 처리할 경우 : 0.5초 (외부로직 3초는 비동기로 처리)
Event를 사용하지 않은 경우 : 0.5초 + 3초 = 3.5초
위와 같이 차이가 발생하게 된다.