Skip to content

Latest commit

 

History

History
126 lines (69 loc) · 4.9 KB

06. 이벤트 적용시 추가 고려 사항.md

File metadata and controls

126 lines (69 loc) · 4.9 KB

정리

이벤트를 구현할 때 다양한 고려 사항이 존재한다.

이벤트를 구현할 때 추가적으로 고려할 점이 있다.

이벤트 소스를 EventEntry에 추가할지에 대한 여부

앞에 구현했던 EventEntry는 이벤트 발생 주체에 대한 정보를 갖지 않는다.

특정 주체가 발생시킨 이벤트만 조회하는 기능을 구현하게 된다면 이벤트에 발생 주체 정보를 추가해야 한다.

포워더에서 전송 실패를 얼마나 허용할 것인가

포워더는 이벤트 전송에 실패하면 실패한 이벤트부터 다시 읽어와 전송을 시도한다.

하지만 특정 이벤트에서 계속 전송에 실패하면 나머지 이벤트를 전송할 수 없게 된다.

따라서 실패한 이벤트의 재전송 횟수 제한을 두어야 한다.

  • 처리에 실패한 이벤트를 생략하지 않고 실패용 DB나 메시지 큐에 저장하기도 한다.

이벤트 손실

이벤트 저장소를 사용하는 방식은 이벤트 발생과 저장을 한 트랜잭션으로 처리하기 대문에 트랜잭션에 성공하면 이벤트가 저장소에 보관된다는 것을 보장한다.

하지만 로컬 핸들러를 이용해 이벤트를 비동기로 처리하면 이벤트 처리에 실패하면 이벤트를 유실하게 된다.

이벤트 순서

이벤트 발생 순서대로 외부 시스템에 전달해야 할 경우, 순서대로 저장하는 이벤트 저장소를 사용하는 것이 좋다.

반면 메시징 시스템은 사용 기술에 따라 이벤트 발생 순서와 메시지 전달 순서가 다를 수도 있다.

이벤트 재처리

동일한 이벤트를 다시 처리해야 할 때 이벤트를 어떻게 할지 결정해야 한다.

가장 쉬운 방법은 마지막으로 처리한 이벤트의 순번을 기억해 두었다가 이미 처리한 순번의 이벤트가 도착하면 해당 이벤트를 처리하지 않고 무시하는 것이다.

이 외에 이벤트를 멱등으로 처리하는 방법도 있다.

멱등이란? 연산을 여러 번 적용해도 결과가 달라지지 않는 성질을 멱등성(idempotent)이라고 한다.

이벤트 핸들러가 멱등성을 가지면 시스템 장애로 인해 같은 이벤트가 중복해서 발생해도 결과적으로 동일 상태가 되어 중복 발생이나 중복 처리에 대한 부담을 줄여준다.

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

이벤트를 처리할 때 DB 트랜잭션을 함께 고려해야 한다.

주문 취소와 환불 기능을 다음과 같이 이벤트를 이용해 구현했다.

  • 주문 취소 기능은 주문 취소 이벤트를 발생시킨다.
  • 주문 취소 이벤트 핸들러는 환불 서비스에 환불 처리를 요청한다.
  • 환불 서비스는 외부 API를 호출해서 결제를 취소한다.

이벤트 발생과 처리를 모두 동기로 처리하면 실행 흐름은 다음과 같다.

  1. CancelOrderController

  2. CancelOrderService.cancel()

  3. Order.cancel()

  4. Event.raise()

  5. 퍼블리셔.publishEvent()

  6. OrderCanceledEventHandler.handle()

  7. RefundService.refund()

  8. 외부 시스템 - 결제 취소 API 호출

  9. (6번) 반대로

  10. (5번) 반대로

  11. (4번) 반대로

  12. (3번) 반대로

  13. (2번) 반대로

  14. DB 업데이트

  15. (1번) 반대로

이런 상황에서 13번 과정에서 실패하면 결제는 취소됐는데 DB에는 주문이 취소되지 않은 상태로 남게 된다.

이벤트를 비동기로 처리할 때도 DB 트랜잭션을 고려해야 한다.

  1. CancelOrderController

  2. CancelOrderService.cancel()

  3. Order.cancel()

  4. Event.raise()

  5. 퍼블리셔.publishEvent()

  6. OrderCanceledEventHandler.handle()

  7. (4번) 반대로

  8. (3번) 반대로

  9. (2번) 반대로

  10. DB 업데이트

  11. (1번) 반대로

  12. RefundService.refund()

  13. 외부 시스템 - 결제 취소 API 호출

  14. (11번) 반대로

다음과 같은 상황에서 DB 업데이트와 트랜잭션을 커밋한 뒤 11~13번 과정을 실행했을 때 12번 과정이 실패하게 되면 DB에는 주문이 취소된 상태이지만 결제는 취소되지 않은 상태로 남게 된다.

스프링의 @TransactionalEventListener 어노테이션을 이용해 트랜잭션 상태에 따라 이벤트 핸들러를 실행할 수 있다.

느낀점

이벤트 처리를 할 때 트랜잭션 처리, 멱등성, 이벤트 손실 등 고려해야 할 점이 많이 존재한다.

때문에 완벽한 처리를 위해 많은 고민이 필요하고 많은 노력이 필요하다고 생각한다.

이벤트를 DB에 저장한다면 순서를 보장하고, 손실을 막고, 트랜잭션 처리에 대한 고민을 줄일 수 있다고 생각한다.

메시징 시스템이나 로컬 핸들러를 통한 구현보다 복잡할 수 있지만 더욱 안전하게 처리할 수 있을 것이다.

멱등성과 관련된 내용은 절대값과 비슷한 개념으로 이해를 했고 자세한 내용은 더 찾아봐야할 것 같다.