# 10. 이벤트

# 10.1 시스템 간 강결합 문제

  • 환불 기능과 같이 외부 서비스 의존성을 가지고 있을 경우 외부 서비스에 직접적인 영향을 받게 됨
  • 또한 환불 후 취소 내용 통지와 같은 기능이 추가로 들어가면 로직이 섞이고 트랜잭션 처리가 복잡해질 수 있음
  • 이는 도메인의 바운디드 컨텍스트들의 강결합 떄문임
  • 이를 비동기 이벤트를 사용하여 두 시스템 간의 결합을 낮출 수 있음

# 10.2 이벤트 개요

  • 예를 들어 주문 취소됨 이벤트를 활용하여 구현 가능

  • 도메인 모델에 이벤트를 도입하려면 이벤트, 이벤트 생성 주체, 이벤트 디스패처(퍼블리셔), **이벤트 핸들러(구독자)**를 구현해야 함
  • 이벤트 생성 주체 : 엔티티, 밸류, 도메인 서비스와 같은 도메인 객체로, 도메인 로직을 실행해서 상태가 바뀌면 관련 이벤트를 발생시킴
  • 이벤트 핸들러 : 이벤트 생성 주체가 발행한 이벤트를 전달받아 이벤트에 담긴 데이터를 이용해서 원하는 기능 실행
  • 이벤트 디스패처 : 이벤트 생성 주체로부터 이벤트를 전달받고 이를 처리할 수 있는 핸들러에 전파

# 이벤트 구성

  • 이벤트 종류, 이벤트 발생 시간, 추가 데이터 등으로 구성되어 있음
public class ShippingInfoChangedEvent {
    
    private String orderNumber;
    private long timestamp;
    private ShippingInfo newShippingInfo;

    // 생성자, getter
}
  • 이벤트는 과거에 벌어진 것을 표현하기 때문에 과거 시제 사용
public class ShippingInfoChangedHandler {

    @EventListener(ShippingInfoChangedEvent.class)
    public void handle(ShippingInfoChangedEvent event) {
        // handle
    }
}
  • 핸들러는 디스패처부터 이벤트를 전달받아 필요한 작업을 수행

# 이벤트 용도

  • 트리거 : 도메인의 상태가 바꾸리 때 다른 후처리가 필요하면 후처리를 실행하기 위한 트리거로 이벤트 사용 가능
  • 데이터 동기화 : 서로 다른 시스템 간의 데이터를 동기화하는데 사용 가능

# 이벤트 장점

  • 서로 다른 도메인 로직이 섞이는 것을 방지할 수 있음
  • 주문 도메인에서 결제 도메인으로의 의존 제거

  • 기능 확장도 용이함 → 예를 들어 구매 취소 시 취소 내용을 이메일로 보내고 싶다면 이메일 발송을 처리하는 핸들러를 구현하면 됨

# 10.5 이벤트 비동기 처리

# 로컬 핸들러 비동기 실행

  • 이벤트 핸들러를 별도 스레드로 실행
  • 스프링이 제공하는 @Async 어노테이션으로 손쉽게 비동기로 이벤트 핸들러 실행 가능

# 메시징 시스템을 이용한 비동기 구현

  • 카프카나 RabbitMQ와 같은 메시징 시스템을 사용하는 것
  • 이때 이벤트를 메시지 큐에 저장하는 과정과 메시지 큐에서 이벤트를 읽어와 처리하는 과정은 별도 스레드나 프로세스로 처리

# 이벤트 저장소를 이용한 비동기 처리

  • 이벤트를 일단 DB에 저장한 뒤 별도 프로그램을 이용해서 이벤트 핸들러에 전달
  • 포워더가 주기적으로 이벤트 저장소에서 이벤트를 가져와 이벤트 핸들러를 실행
  • 외부 핸들러가 API 서버를 통해 이벤트 목록을 가져가서 처리

# 10.6 이벤트 적용 시 추가 고려 사항

1. 이벤트 소스

  • 이벤트를 발생시킨 주체에 대한 정보를 저장해서, 특정 주체가 발생시킨 이벤트만 조회하는 기능을 구현할 수 있음

2. 포워더 전송 실패 허용

  • 포워더에서 이벤트마다 재전송 횟수 제한을 두어, 특정 이벤트의 전송을 일정 횟수만큼 실패 시 해당 이벤트는 생략하고 다음 이벤트로 넘어가는 등의 정책을 둘 수 있음

3. 이벤트 손실

  • 이벤트 저장소를 사용하면 트랜잭션에 성공하면 이벤트가 저장소에 보관됨
  • 로컬 핸들러를 사용하여 이벤트를 비동기로 처리할 경우 이벤트 처리에 실패하면 이벤트를 유실하게 됨

4. 이벤트 순서

  • 이벤트 발생 순서대로 외부 시스템에 전달해야 할 경우, 이벤트 저장소를 사용하는 것이 좋음
  • 반면 메시징 시스템은 사용 기술에 따라 이벤트 발생 순서와 메시지 전달 순서가 다를 수 있음

5. 이벤트 재처리

  • 동일한 이벤트를 다시 처리해야 할 때 어떻게 할지 결정해야 함
  • 이벤트 순번 기억 혹은 멱등하게 이벤트를 처리하는 방법이 있음

# 이벤트 처리와 DB 트랜잭션 고려

  • 이벤트 처리르 ㄹ동기로 하든 비동기로 하든 트랜잭션 실패를 함께 고려해야 함
  • 트랜잭션이 성공할 때만 이벤트 핸들러를 실행하는 방식 등을 사용 가능
@TransactionalEventListener(
    classes = OrderCanceledEvent.class,
    phase = TransactionalPhase.AFTER_COMMIT
)
public void handle(OrderCanceledEvent event) {
    // handle
}
  • 해당 방식 사용 시 이벤트 핸들러를 실행했는데 트랜잭션이 롤백되는 상황이 발생하지 않음
  • 이벤트 저장소를 DB로 사용해도 트랜잭션이 성공할 떄만 이벤트가 DB에 저장되므로, 트랜잭션은 실패했는데 이벤트 핸들러가 실행되는 상황은 발생하지 않음
  • 이로써 트랜잭션이 성공할 때만 이벤트 핸들러를 실행하게 되므로 트랜잭션 실패에 대한 경우의 수가 줄어드므로 이벤트 처리 실패만 고민하면 됨
  • 또한 이벤트 특성에 따라 재처리 방식을 결정하면 됨