Hyun's Wonderwall

[EFUB 4기 BE Lead] 도메인 주도 개발 시작하기 - 10장. 이벤트 본문

Study/Java, Spring

[EFUB 4기 BE Lead] 도메인 주도 개발 시작하기 - 10장. 이벤트

Hyun_! 2024. 5. 13. 19:41

EFUB 4기 BackEnd Lead_ 도메인 주도 개발 스터디

  • 스터디 커리큘럼: 최범균, "도메인 주도 개발 시작하기: DDD 핵심 개념 정리부터 구현까지"
  • 3주차 과제: Chapter 10. 이벤트, Chapter 11. 애그리거트 트랜잭션 관리

Chapter 10. 이벤트

  • Keywords: 이벤트의 용도와 장점, 핸들러 디스패처와 핸들러 구현, 비동기 이벤트 처리

10.1 시스템 간 강결합 문제

구매 취소 및 환불 로직을 어떻게 구현해야 할까?

현재까지의 코드에서는 주문 바운디드 컨텍스트와 결제 바운디드 컨텍스트 간에 강결합(high coupling)이 존재한다. 주문이 결제와 강하게 결합되어 있어 영향을 받는 것이다.

이벤트를 사용하면 이런 강한 결합을 없앨 수 있다. 특히 "비동기 이벤트" -> 두 시스템 간의 결합을 크게 낮출 수 있다.

한번 익숙해지면 모든 연동을 이벤트와 비동기로 처리하고 싶을 정도로 강력하고 매력적인 것이 이벤트다.

10.2 이벤트 개요

이벤트(event): 과거에 벌어진 어떤 것.

- 이벤트가 발생함 = 상태가 변경됨. (ex. '암호를 변경했음', '주문을 취소했음')

- 이벤트는 이벤트 발생 시  그에 반응하여 원하는 동작을 수행하는 기능을 구현한다.

- UI 컴포넌트의 이벤트와 비슷하게 도메인의 상태 변경을 이벤트로 표현할 수 있다.

  (ex. '~할 때', '~가 발생하면', '만약 ~하면' 과 같은 요구사항들을 이벤트로 구현)

10.2.1 이벤트 관련 구성요소

도메인 모델에 이벤트를 도입하려면

(1)이벤트, (2)이벤트 생성 주체, (3)이벤트 디스패처(퍼블리셔), (4)이벤트 핸들러(구독자)를 구현해야 한다.

이벤트 생성 주체는 이벤트를 생성해서 디스패처에 이벤트를 전달하고,

디스패처는 해당 이벤트를 처리할 수 있는 핸들러에 이벤트를 전파한다.

  • 이벤트 생성 주체: 엔티티, 밸류, 도메인 서비스와 같은 도메인 객체.
    - 이들 도메인 객체는 도메인 로직을 실행해서 상태가 바뀌면 관련 이벤트를 발생시킨다.
  • 이벤트 핸들러: 이벤트 생성 주체가 발생시킨 이벤트에 반응하여, 전달받은 이벤트에 담긴 데이터를 이용해 원하는 기능을 실행한다. (ex. '주문 취소됨 이벤트'를 받는 이벤트 핸들러는 해당 주문의 주문자에게 SMS로 주문 취소 사실을 통지할 수 있다.)
  • 이벤트 디스패처: 이벤트 생성 주체와 이벤트 핸들러를 연결해 준다.
    - 이벤트 디스패처의 구현 방식에 따라 이벤트 생성/처리 방식의 동기 or 비동기 결정됨.

10.2.2 이벤트의 구성

이벤트는 발생한 이벤트에 대한 정보를 담는다. 데이터는 이벤트로 구현하려는 로직과 관련있는 정보만 담으면 된다.

  • 이벤트 종류: 클래스 이름으로 표현
  • 이벤트 발생 시간
  • 추가 데이터: 이벤트와 관련된 정보 - ex. 주문번호, 신규 배송지 정보 등

Changed..

10.2.3 이벤트 용도

1. 트리거 Trigger

2. 서로 다른 시스템 간의 데이터 동기화

10.2.4 이벤트 장점

서로 다른 도메인 로직이 섞이는 것을 방지할 수 있다.

- 구매 취소 로직에 이벤트를 적용 -> 환불 로직이 없어짐! 환불 실행 로직은 주문 취소 이벤트를 받는 이벤트 핸들러로 이동함. 이로써 주문 도메인에서 결제 도메인으로의 의존이 제거됨.

- 이벤트 핸들러를 사용하면 도메인 로직에 영향 없이 기능 확장이 용이.

10.3 이벤트, 핸들러, 디스패처 구현

이벤트 클래스: 이벤트를 표현한다.

디스패처: 스프링이 제공하는 ApplicationEventPublisher를 이용한다.

Events: 이벤트를 발행한다. 이벤트 발행을 위해 ApplicationEventPublisher를 사용한다.

이벤트 핸들러: 이벤트를 수신해서 처리한다. 스프링이 제공하는 기능을 사용한다.

10.3.1 이벤트 클래스

이벤트 자체를 위한 상위 타입은 존재하지 않는다. 원하는 클래스를 이벤트로 사용하면 된다.

이벤트 클래스의 이름을 명명할 때에는 과거 시제를 사용해야 한다. (ex. OrderCanceledEvent, OrderCanceled)

 

이벤트 클래스는 이벤트를 처리하는 데 필요한 최소한의 데이터를 포함해야 한다. 그래야 관련 핸들러에서 후속 처리 가능.

 

모든 이벤트가 공통으로 가질 프로퍼티를 지정한다면 관련 상위 클래스를 만들 수도 있다. (ex. Event 추상 클래스 생성, timestamp 필드)

10.3.2 Events 클래스와 ApplicationEventPublisher

이벤트 발생과 출판을 위해 ApplicationEventPublisher를 사용한다.

스프링 컨테이너(ApplicationContext)가 이 인터페이스의 구현체를 빈으로 등록해 제공한다.

커스텀 클래스 Events의 Events.raise()가 이벤트를 발생시키고, Events 클래스가 사용할 퍼블리셔 객체는setPublisher()  메서드를 통해 전달받는다. (그를 위해 EventsConfiguration 클래스 생성하여 작성함, Events 클래스 빈 객체 초기화 메서드 작성함)

10.3.3 이벤트 발생과 이벤트 핸들러

Events.raise(), Order#Events

@EventListener 애너테이션 사용해 이벤트를 처리할 핸들러 구현


10.3.4 흐름 정리

다이어그램 그림

 

10.4 동기 이벤트 처리 문제

외부 시스템과의 연동을 동기로 처리 시 성능과 트랜잭션 범위 문제가 발생한다.

해소 방법 2가지: 1. 비동기, 2. 이벤트와 트랜잭션 연계

10.5 비동기 이벤트 처리

비동기 이벤트 처리 구현 방법: (1) 로컬 핸들러를 비동기로 실행, (2) 메시지 큐를 사용, (3)이벤트 저장소와 이벤트 포워더 사용, (4) 이벤트 저장소와 이벤트 제공 API 사용

 

각각의 구현 방법,,,

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

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

 성능과 트랜잭션 범위 문제가 발생한다.

해소 방법 2가지: 1. 비동기, 2. 이벤트와 트랜잭션