환불 기능을 제공하는 도메인 서비스를 파라미터로 받고 취소 도메인 기능에서 도메인 서비스 실행
public class Order {
// 외부 서비스를 실행하기 위해 도메인 서비스를 파라미터로 전달받음
public void cancel(RefundService refundService) {
verifyNotYetShipped();
this.state = OrderState.CANCELED;
this.refundStatus = State.REFUND_STARTED;
try {
refundService.refund(getPaymentId());
this.refundStatus = State.REFUND_COMPLETED;
} catch (Exception ex) {
// TODO: 예외 처리 로직 작성 (ex 로그 기록, 상태 변경 등)
}
}
또는 응용 서비스에서 환불 기능 실행
public class CancelOrderService {
private RefundService refundService;
@Transactional
public void cancel(OrderNo orderNo) {
Order order = findOrder(orderNo);
order.cancel();
order.refundStarted();
try {
refundService.refund(order.getPaymentId());
order.refundCompleted();
} catch (Exception ex) {
// TODO: 예외 처리 로직 작성
}
}
외부에 존재해서 환불 서비스 호출
이벤트 생성 주체 : 엔티티, 밸류, 도메인 서비스 같은 도메인 객체
이벤트 디스패처 : 이벤트 퍼블리싱 (이벤트 발생), 생성 주체와 이벤트 연결
이벤트 핸들러 : 생성된 이벤트에 반응
이벤트 종류: 클래스 이름으로 이벤트 종류를 표현
이벤트 발생시간
추가 데이터: 주문번호 신규 배송지 정보 등 이벤트와 관련된 정보
이벤트 예시 > 변경된 배송지 정보를 물류 서비스에 전송하는 핸들러
public class ShippingInfoChangedHandler {
@EventListener(ShippingInfoChangedEvent.class)
public void handle(ShippingInfoChangedEvent evt) {
shippingInfoSynchronizer.sync(
evt.getOrderNumber(),
evt.getNewShippingInfo()
);
}
}
이벤트 용도
트리거도메인 상태가 바뀔 때 다른 후처리 필요한 경우

서로 다른 시스템 간의 데이터 동기화이벤트 장점
서로 다른 도메인 로직이 섞이는 것을 방지
기능을 확장해도 도메인 로직은 수정할 필요가 없다.

public class Events {
private static ApplicationEventPublisher publisher;
static void setPublisher(ApplicationEventPublisher publisher) {
Events.publisher = publisher;
}
public static void raise(Object event) {
if (publisher != null) {
publisher.publishEvent(event);
}
}
}
@Configuration
public class EventsConfiguration {
@Autowired
private ApplicationContext applicationContext;
@Bean
public InitializingBean eventsInitializer() {
return () -> Events.setPublisher(applicationContext);
}
}
public void cancel() {
verifyNotYetShipped();
this.state = OrderState.CANCELED;
Events.raise(new OrderCanceledEvent(number.getNumber()));
}
@Service
public class OrderCanceledEventHandler {
private final RefundService refundService;
public OrderCanceledEventHandler(RefundService refundService) {
this.refundService = refundService;
}
@EventListener(OrderCanceledEvent.class)
public void handle(OrderCanceledEvent event) {
refundService.refund(event.getOrderNumber());
}
}

// 1. 응용 서비스 코드
@Transactional
public void cancel(OrderNo orderNo) {
Order order = findOrder(orderNo);
order.cancel(); // order.cancel()에서 OrderCanceledEvent 발생
}
// 2. 이벤트를 처리하는 코드
@Service
public class OrderCanceledEventHandler {
private final RefundService refundService;
public OrderCanceledEventHandler(RefundService refundService) {
this.refundService = refundService;
}
@EventListener(OrderCanceledEvent.class)
public void handle(OrderCanceledEvent event) {
// refundService.refund()에서 느려지거나 익셉션 발생 시 어떻게 처리할지 고려 필요
refundService.refund(event.getOrderNumber());
}
}
내부 로직 처리, 외부 서비스 후처리회원 가입 신청 이후 이메일 보내는 상황
‘A 하면 최대 언제까지 B 하라’ 가 포인트
비동기 이벤트 처리 방법
로컬 핸들러를 비동기로 실행
메시지 큐 사용

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

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

포워더 방식과 차이점은 이벤트를 전달하는 방식이 다르다.
API 방식은 외부 핸들러가 API 서버를 통해 이벤트 목록을 가져간다.
API 방식에서는 이벤트 목록을 요구하는 외부 핸들러가 자신이 어디까지 이벤트를 처리했는지 기억
스토리지에서 이벤트 ID나 타임스탬프 추적

offset 대신 cursor 기반 페이징을 하면 5개 이벤트 고정적으로 제공 가능
SELECT *
FROM member
WHERE created_at < :lastCreatedAt
ORDER BY created_at DESC
LIMIT 5;
자동 증가 컬럼 주의 사항
CDC 사용DB 반영 순서에 맞춰 이벤트 소비12 → 11 순서로 기록이벤트 소스를 EventEntry에 추가할지 여부
주문 취소와 환불 기능 예시
주문 취소 기능은 주문 취소 이벤트를 발생시킨다.
주문 취소 이벤트 핸들러는 환불 서비스에 환불 처리를 요청한다.
환불 서비스는 외부 API를 호출해서 결제를 취소한다.

