부르미와 드리미의 상호 동의가 끝나면 주문은 IN_PROGRESS로 전이되고, 매칭 엔진이 DeliveryService.startDelivery()를 호출해 DELIVERY 행을 생성한다.
기존 구조에서는 주문 상태 변경 트랜잭션이 먼저 커밋된 뒤 별도의 트랜잭션에서 배달을 생성한다. 따라서 startDelivery()의 비즈니스 검증이 실패하면 DELIVERY 행은 생성되지 않지만, 앞서 커밋된 ORDERS.order_cd = IN_PROGRESS는 되돌릴 수 없다.
해결책으로 배달 시작 전에 판단할 수 있는 비즈니스 검증을 BoormiService.confirmDreami()로 이관하고, startDelivery()는 검증이 끝난 내부 요청을 실행하는 역할만 담당하도록 결정했다.
매칭 엔진의 인메모리 상태는 비동기 단일 스레드에서만 변경한다. HTTP 요청 스레드는 DB 트랜잭션을 처리한 뒤 이벤트를 발행하고, 트랜잭션이 커밋된 후에만 매칭 엔진에 액션을 제출한다.
현재 상호 동의 이후의 흐름은 다음과 같다.
부르미 확정 HTTP 요청
└─ BoormiService.confirmDreami() [트랜잭션 A]
├─ 확정 대상과 주문 검증
├─ ORDERS: PENDING_BOORMI_CONFIRMATION → IN_PROGRESS
├─ MATCHING 이력 저장
└─ BoormiConfirmedEvent 발행
↓ AFTER_COMMIT
MatchingService.onBoormiConfirmed()
└─ 매칭 엔진 큐에 AcceptByBoormi 제출
└─ MatchingService.applyAcceptByBoormi() [엔진 단일 스레드]
├─ 인메모리 오퍼: PENDING_BOORMI_CONFIRMATION → MATCHED
├─ DeliveryService.startDelivery() [트랜잭션 B]
│ └─ DELIVERY 행 저장
└─ 매칭 엔진의 드리미·오퍼·그룹 정리
MatchingStartRequestedEvent는 주문 접수 트랜잭션이 끝난 뒤 매칭을 시작하기 위한 이벤트다. 상호 동의 후 배달을 시작하는 흐름에서는BoormiConfirmedEvent가 사용된다.
기존 startDelivery()는 DELIVERY 행을 저장하기 전에 다음 비즈니스 조건을 다시 검사한다.
@Transactional
public void startDelivery(UUID orderId, UUID dreamiId, UUID boormiId) {
Orders order = orderService.getOrder(orderId);
if (order.getOrderCd() != OrderCd.IN_PROGRESS) {
throw new BusinessException(DeliveryErrorCode.DELIVERY_START_NOT_ALLOWED);
}
if (!userService.getUserInfo(dreamiId).isDreami()) {
throw new BusinessException(DeliveryErrorCode.DREAMI_NOT_ACTIVATED);
}
Delivery delivery = deliveryRepository.save(Delivery.create(orderId, dreamiId, boormiId));
alarmBoormiDeliveryStartedBySSE(delivery);
alarmDreamiDeliveryStartedBySSE(delivery);
}
예를 들어 확정된 사용자가 활성 드리미가 아니면 다음과 같은 불일치가 생긴다.
| 구분 | 결과 |
|---|---|
| 주문 트랜잭션 A | 이미 커밋되어 ORDERS.order_cd가 IN_PROGRESS로 유지됨 |
| 배달 트랜잭션 B | DREAMI_NOT_ACTIVATED 예외로 롤백되어 DELIVERY 행이 없음 |
| 매칭 엔진 | 오퍼는 이미 MATCHED로 바뀌었지만 예외 때문에 후속 인메모리 정리가 실행되지 않음 |
즉, DB에는 "진행 중인 주문이지만 배달 정보가 없는 상태"가 남고, 매칭 엔진에도 완료되지 않은 오퍼와 그룹이 잔존할 수 있다.
핵심 원인은 하나의 비즈니스 상태 전이가 서로 다른 두 트랜잭션과 인메모리 상태 변경에 걸쳐 있다는 점이다.
confirmDreami()가 끝날 때 커밋된다.BoormiConfirmedEvent는 AFTER_COMMIT 리스너를 거쳐 매칭 엔진에 전달된다.startDelivery()는 이후 별도의 트랜잭션 B에서 실행된다.