[DDD] 도메인 주도 개발 시작하기 - 10장

Y_Sevin·2023년 9월 9일
0

10.1 시스템 간 강결합 문제

도메인 서비스에서 외부서비스를 호출 할때 다양한 문제가 발생할 수 있음
ex) 구매를 취소하여 환불을 진행할때 도메인 서비스에서 외부서비스를 호출하여 환불 진행

public class Order {

    public void cancel(RefundService refundService) {

        // 주문 로직
        verifyNotYetShopped();
        this.state = OrderState.CANCELED;

        // 결제 로직
        this.refundStatus = State.REFUND_STARTED;
        try {
            refundSvc.refund(getPaymentId());
            this.refundStatus = State.REFUND_COMPLETED;
        } catch(Exception e) {
            ...
        }

    }
}

보통 결제 시스템은 외부에 존재하므로 외부서비스를 도메인 서비스에서 호출함
-> 이때 외부 서비스를 호출할때 2가지 문제가 발생할 수 있음

  1. 외부 서비스가 정상이 아닐 경우 (트랙잭션 처리를 어떻게 할 것 인가.. -> 롤백, 커밋)

  2. 외부 서비스의 응답 시간 등 성능에 문제가 생길 경우

  • 설계상 문제가 나타날 경우 (주문 도메인에 활불로직이 뒤섞이는 문제)

이러한 문제는 바운디드 컨텍스트간의 강결합 때문임
--> 이벤트를 통해 해결하자

10.2 이벤트 개요

이벤트 : 과거에 벌어진 어떤 것
ex) 암호를 변경했다면 암호를 변경했음 이벤트가 벌려졌다고 할 수 있음

10.2.1 이벤트 관련 구성요소

도메인 모델에 이벤트를 도입하려면 아래와 같은 네 개의 구성요소를 구현해야함

  • 이벤트
  • 이벤트 생성 주체
  • 이벤트 디스패처(퍼블리셔)
  • 이벤트 핸들러(구독자)

10.2.2 이벤트의 구성

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

public class ShippingInfoChangedEvent {

    private String orderNumber;
    private long timestamp;
    private ShippingInfo newShippingInfo;

    // 생성자, getter
}

이벤트는 현재 기준으로 과거에 벌어진 것을 표현하기 때문에 과거 시제('Changed') 사용함

public class Order {

  public void changeShippingInfo(ShippingInfo newShippingInfo) {
      verifyNotYetShipped();
      setShippingInfo(newShippingInfo);
      Events.raise(new ShippingInfoChangedEvent(number, newShippingInfo));
  }
  ...
}

이벤트는 데이터를 담아야 하지만, 이벤트 자체와 관련 없는 데이터를 포함할 필요는 없음.

10.2.3 이벤트 용도

  • 트리거
    도메인의 상태가 바뀔때 후처리가 필요할 경우 후처리를 실행하기 위한 트리거로 이벤트를 사용할 수 있음
  • 서로 다른 시스템 간의 데이터 동기화
    서로 다른 도메인 로직이 섞이는 것을 이벤트를 통해 방지

10.2.4 이벤트 장점

10.3

이벤트 클래스 : 이벤트를 표현
디스패처 : 스프링이 제공하는 ApplicationEventPublisher 이용
Events : 이벤트를 발행, 이벤트 발행을 위해 ApplicationEventPublisher 사용
이벤트 핸들러 : 이벤틀르 수신해서 처리, 스프링이 제공하는 기능

10.3.1 이벤트 클래스

이벤트클래스는 이벤트를 처리하는데 필요한 최소한의 데이터를 포함시킴

public class OrderCanceledEvent extends Event {
    private String orderNumber;

    public OrderCanceledEvent(String number) {
        super();
        this.orderNumber = number;
    }

    public String getOrderNumber() {
        return orderNumber;
    }

}

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

이벤트를 발생시키기 위해 Events.raise() 사용
이벤트를 처리 핸들러 : @EventListener 사용

10.4 동기 이벤트 처리 문제

ex)응용 서비스 코드에서 외부 연동 과정에서 예외 발생시 트랜잭션 처리

이벤트 핸들러에서 외부 서비스 호출시 느려지거나 예외가 발생할때
-> 이벤트를 비동기로 처리하거나 이벤트와 트랜잭션을


@Transactional
public void cancel(OrderNo orderNo) {
    Order order = findOrder(orderNo);
}

@Service
public class OrderCanceledEventHandler {

    ...

    @EventListener(OrderCanceledEvent.class)
    public void handle(OrderCanceledEvent event) {
        // refundService.refund()
        refundService.refund(event.getOrderNumber());
    }
}

10.5 비동기 이벤트 처리

  • 로컬 핸들러를 비동기로 실행하기

  • 메시지 큐 사용

  • 이벤트 저장소와 이벤트 포워더 사용

  • 이벤트 저장소와 이벤트 제공 API 사용

  • 스프링에서는 @Async 어노테이션을 사용

10.5.2 메시징 시스템을 이용한 비동기 구현
카프카(Kafka), 래빗MQ(RabbitMQ) 와 같은 메세징 시스템 이용

  1. 이벤트 발생시 디스패처는 이벤트를 메세지 큐에 보냄
  2. 메세지 큐는 이벤트를 메세지 리스너에 전달
  3. 메세지 리스너는 알맞은 이벤트 핸들러를 이용해서 이벤트를 처리
profile
매일은 아니더라도 꾸준히 올리자는 마음으로 시작하는 개발블로그😎

0개의 댓글