10. 이벤트
#
시스템 간 강결합의 문제- 쇼핑물에서 구매를 취소하면 환불을 처리해야 합니다.
- 보통 결제 시스템은 외부에 존재하므로 외부의 환불 서시스템 서비스를 호출하는데 두가지 문제가 발생합니다.
- 외부 서비스가 정상이 아닐 경우 트랜잭션 처리를 어떻게 해야 할지 애매하다는 것입니다.
- 환불을 처리하는 외부 시스템의 응답시간이 길어질수록 대기시간이 발생하는 성능에 대한 이슈가 발생합니다.
- 이 문제 외에도 설계상 문제가 나타날 수 있습니다.
- 위와 같은 강한 결함을 없앨 수 있는 방법으로 이벤트를 사용해 결합을 나출 수 있습니다.
#
이벤트 개요- 해당 챕터에서 이벤트(event)는 "과거에 벌어진 어떤 것"을 의미합니다.
- 이벤트가 발생하면 상태가 변경됐다를 의미합니다.
#
이벤트 관련 구성요소- 도메인 모델에 이벤트를 도입하려면 같은 네 개의 구성요소를 구현해야 합니다.
- 도메인 모델에서 이벤트 주체는 엔티티, 밸류, 도메인 서비스와 같은 도메인 객체입니다.
- 도메인 객체는 도메인 로직을 실행해서 상태가 바뀌면 관련 이벤트를 실행합니다.
- 이벤트 핸들러(handler)는 이벤트 생성 주체가 발생한 이벤트에 반응합니다.
- 이벤트 생성 주체와 이벤트 핸들러를 연결해 주는 것이 이벤트 디스패처(dispatcher)입니다.
#
이벤트의 구성- 이벤트는 발생한 이벤트에 대한 정보를 담습니다.
- 이벤트 종류 : 클래스 이름으로 이벤트 종류를 표현
- 이벤트 발생 시간
- 추가 데이터 : 주문번호, 신규 배송지 정보 등 이벤트와 관련된 정보
- 이벤트는 이벤트 핸들러가 작업을 수행하는 데 필요한 최소한의 데이터를 담아야합니다.
- 이벤트는 데이터를 담아야 하나 이벤트 자체와 관련 없는 데이터를 포함할 필요는 없습니다.
#
이벤트 용도- 이벤트는 크게 두 가지 용도로 쓰입니다.
- 첫번째 용도는 트리거입니다.
- 두번째 용도는 서로 다른 시스템 간의 데이터 동기화입니다.
#
이벤트 장점- 이벤트를 사용하면 서로 다른 도메인 로직이 섞이는 것을 방지할 수 있습니다.
#
이벤트, 핸들러, 디스패처 구현- 이벤트와 관련된 코드는 다음과 같습니다.
- 이벤트 클래스
- 이벤트 핸들러(EventHandler) : 이벤트 핸들러를 위한 상위 타입으로 모든 핸들러는 이 인터페이스를 구현합니다.
- 이벤트(Events) : 이벤트 디스패처, 이벤트 발행, 이벤트 핸들러 등록, 이벤트를 핸들러에 등록하는 등의 기능을 제공
#
이벤트 클래스- 이벤트 자체를 위한 상위 타입은 존재하지 않습니다.
- 이벤트 구성에서 설명한 것처럼 이벤트 클래스는 이벤트를 처리하는 데 필요한 최소한의 데이터를 포함해야합니다.
- 모든 이벤트가 공통으로 갖는 프로퍼티가 존재한다면 관련 상위 클래스를 만들 수도 있습니다.
#
EventHandler 인터페이스- EventHandler 인터페이스는 이벤트 핸들러를 위한 상위 인터페이스입니다.
- EventHandler 인터페이스를 상속받는 클래스는 handle() 메서드를 이용해서 필요한 기능을 구현하면 됩니다.
#
이벤트 디스패처인 Events 구현- 이벤트 핸들러 등록을 쉽게 하기 위해 정적 메서드를 이용해서 구현합니다.
- 이벤트를 발생시킬 때에는
Events.raise()
메서드를 사용합니다. Events.raise()
를 이용해서 이벤트를 발생시키면Events.raise()
메서는 이벤트를 처리할 핸들러를 찾아 handle()메서드를 실행합니다.
#
흐름 정리#
AOP를 이용한 Events.reset() 실행- 응용 서비스가 끝나면 ThreadLocal에 등록된 핸들러 목록을 초기화하기 위해 Events.reset() 메서드를 실행합니다.
#
동기 이벤트 처리 문제- 이벤트를 사용하면 강결합 문제는 해결이 되지만, 외부 서비스에 영향을 받는 문제가 있습니다.
- 외부 서비스의 성능 저하가 시스템의 성능 저하로 연결됩니다.
- 또한 이는 성능 저하뿐만 아니라 트랜잭션도 문제가 됩니다.
- 외부 시스템과의 연동을 동기로 처리할 때 발생하는 성능과 트랜잭션 범위 문제를 해소하는 방법 중 하나가 이벤트를 비동기로 처리하는 것입니다.
#
비동기 이벤트 처리- "A하면 이어서 B하라"는 내용을 담고 있는 요구사항은 실제로 "A하면 최대 언제까지 B하라" 인 경우가 많습니다.
- 이러한 요구사항은 이벤트를 비동기로 처리하는 방식으로 구현할 수 있습니다.
- 다음의 네가지 방식으로 비동기 이벤트 처리를 구현할 수 있습니다.
- 로컬 핸들러를 비동기로 실행하기
- 메시지 큐를 사용하기
- 이벤트 저장소와 이벤트 포워더 사용하기
- 이벤트 저장소와 이벤트 제공 API 사용하기
#
로컬 핸들러의 비동기 실행- 이벤트 핸들러를 비동기로 실행하는 방법은 이벤트 핸들러를 별도 스레드로 실행하는 것입니다.
- 동기나 비동기로 실행할 이벤트 핸들러를 처리하는 방식은 거의 유사하나, 비동기 이벤트 핸들러는 스레드 풀에 핸들러 실행작업을 등록하나 동기로 실행할 이벤트 핸들러는 바로 실행한다는 사실 입니다.
#
메시징 시스템을 이용한 비동기 구현- 비동기로 이벤트 처리해야 할 때 사용하는 또 다른 방법은 RabbitMQ와 같은 메시징 큐를 사용하는 것입니다.
- 이벤트가 발생하면 이벤트 디스패처는 이벤트를 메시지 큐에 보냅니다.
- 메시지 큐는 이벤트를 메시지 리스너에 전달하고, 메시지 리스터는 알맞은 이벤트 핸들러를 이용해서 이벤트를 처리합니다.
- 이벤트를 메시지 큐에 저장하는 과정과 메시지 큐에서 이벤트를 읽어와 처리하는 과정은 별도 스레드나 프로세스로 처리됩니다.
- 필요하다면 이벤트를 발생하는 도메인 기능과 메시지 큐에 이벤트를 저장하는 절차를 한 트랜잭션으로 묶을 수도 있습니다.
- 글로벌 트랜잭션을 사용하면 안전하게 이벤트를 메시지 큐에 전달할 수 있는 장점이 있지만, 반대로 글로벌 트랜잭션으로 인해 전체 성능이 떨어지는 단점도 있습니다.
- 많은 경우 메시지 큐를 사용하면 보통 이벤트를 발생하는 주체와 이벤트 핸들러가 별도 프로세스에서 동작합니다.
- 일반적으로 많이 사용되는 메시징 시스템은 글로벌 트랜잭션 지원과 함께 클러스터와 고가용성을 지원하기 때문에 안정적으로 메시지를 전달할 수 있는 장점이 있습니다.
- 또한 다양한 개발 언어와 통신 프로토콜을 지원하고 있습니다.
#
이벤트 저장소를 이용한 비동기 처리- 비동기로 이벤트를 처리하기 위한 또 다른 방법은 이벤트를 일단 DB에 저장한 뒤에 별도 프로그램을 이용해서 이벤트 핸들러에 전달하는 것입니다.
- 이벤트가 발생하면 핸들러는 스토리지에 이벤트를 저장합니다. 포워더는 주기적으로 이벤트 저장소에서 이벤트를 가져와 이벤트 핸들러를 실행합니다.
- 도메인 상태 변화와 이벤트 저장이 로컬 트랜잭션으로 처리됩니다.
- 이벤트 저장소를 이용한 두번째 방법은 이벤트를 외부에 제공하는 API를 사용하는 것입니다.
- API와 포워더의 방식의 차이는 이벤트를 전달하는 방식에 있으며, 포워더 방식에서는 포워더를 이용해서 이벤트를 이용해서 이벤트를 외부에 전달하는 방식이라면, API 방식에서는 외부 핸들러가 API 서버를 통해 이벤트 목록을 가져오는 방식입니다.
#
이벤트 저장소 구현- 포워드 방식과 API 방식 모두 이벤트 저장소를 사용하므로 이벤트를 저장할 저장소가 필요합니다.
- 이벤트는 과거에 벌어진 사건이므로 데이터가 변경되지 않습니다. 따라서
EventStore
인터페이스는 새로운 이벤트를 추가하는 기능과 조회하는 기능만 제공하고 기존 이벤트 데이터를 수정하는 기능은 제공하지 않습니다.
#
이벤트 저장을 위한 이벤트 핸들러 구현EventStoreHandler
를 이벤트 핸들러로 사용하려면 응용 서비스의 메서드 마다Events.handle()
로 등록해야 합니다.- 모든 응용 서비스에 대해 코드를 추가하면 많은 중복이 발생하므로 중복을 제거하기 위해 AOP를 사용하면 좋습니다.
#
REST API 구현- REST API는 단순합니다.
- 클라이언트 API를 이용해서 언제든지 원하는 이벤트를 가져올 수 있기 때문에 이벤트 처리에 실패하면 다시 실패한 이벤트로부터 읽어와 이벤트를 재처리할 수 있습니다.
#
포워드 구현- 포워더는 앞의 API 방식에서 클라이언트의 구현과 유사합니다.
- 포워더는 일정 주기로
EventStore
로부터 이벤트를 읽어와 이벤트 핸들러에 전달하면 됩니다.
#
이벤트 적용 시 추가 고려사항- 이벤트를 구현할 때 추가로 고현할 점은 다섯가지입니다.
- 첫번째는 이벤트 소스를
EventEntry
에 추가할지 여부입니다. - 두번째는 포워더에서 전송 실패를 얼마나 허용할 것이냐에 대한 것입니다.
- 세번째는 이벤트 손실에 대한 것입니다.
- 네번째는 이벤트 순서에 대한 것입니다.
- 다섯번째는 이벤트 재처리에 대한 것입니다.
- 첫번째는 이벤트 소스를