도메인 서비스에서 외부서비스를 호출 할때 다양한 문제가 발생할 수 있음
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가지 문제가 발생할 수 있음
외부 서비스가 정상이 아닐 경우 (트랙잭션 처리를 어떻게 할 것 인가.. -> 롤백, 커밋)
외부 서비스의 응답 시간 등 성능에 문제가 생길 경우
이러한 문제는 바운디드 컨텍스트간의 강결합 때문임
--> 이벤트를 통해 해결하자
이벤트 : 과거에 벌어진 어떤 것
ex) 암호를 변경했다면 암호를 변경했음 이벤트가 벌려졌다고 할 수 있음
도메인 모델에 이벤트를 도입하려면 아래와 같은 네 개의 구성요소를 구현해야함
이벤트 종류: 클래스 이름을 이벤트 종류를 표현
이벤트 발생 시간
추가 데이터: 주문번호, 신규 배송지 정보 등 이벤트와 관련된 정보
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));
}
...
}
이벤트는 데이터를 담아야 하지만, 이벤트 자체와 관련 없는 데이터를 포함할 필요는 없음.
이벤트 클래스 : 이벤트를 표현
디스패처 : 스프링이 제공하는 ApplicationEventPublisher 이용
Events : 이벤트를 발행, 이벤트 발행을 위해 ApplicationEventPublisher 사용
이벤트 핸들러 : 이벤틀르 수신해서 처리, 스프링이 제공하는 기능
이벤트클래스는 이벤트를 처리하는데 필요한 최소한의 데이터를 포함시킴
public class OrderCanceledEvent extends Event {
private String orderNumber;
public OrderCanceledEvent(String number) {
super();
this.orderNumber = number;
}
public String getOrderNumber() {
return orderNumber;
}
}
이벤트를 발생시키기 위해 Events.raise() 사용
이벤트를 처리 핸들러 : @EventListener 사용
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());
}
}
로컬 핸들러를 비동기로 실행하기
메시지 큐 사용
이벤트 저장소와 이벤트 포워더 사용
이벤트 저장소와 이벤트 제공 API 사용
스프링에서는 @Async 어노테이션을 사용
10.5.2 메시징 시스템을 이용한 비동기 구현
카프카(Kafka), 래빗MQ(RabbitMQ) 와 같은 메세징 시스템 이용