2차 프로젝트에서 주문 도메인을 맡게 되었습니다. 개발하는 과정에서 마주했던 트러블 슈팅을 해결했던 경험을 기록하고자 합니다.
배경
Order Service에서 주문을 생성하는 과정에서 order 정보를 저장하고, 외부 서비스 Delivery Service에 배송 생성 FeignClient 요청한다. Delivery생성에서는 해당 주문이 실제 있는지 Order Service에 FeignClient 요청하는 상황이다.
Order Service -- feignClient: createDelivery() --> Delivery Service -- feignClient: getOrderDetail() --> Order Service
이런 상황이다. Order Service가 Delivery Service에 의존하고, Delivery Service가 Order Service에 다시 의존하는 양방향 의존성이 발생한다.
발생한 문제
이런 구조에서 Order Service가 방금 저장한 orderId를 전달했음에도 불구하고, Delivery Service의 검증 Not Found 오류 발생하는 문제가 생겼다.
feign.FeignException$NotFound: [404] ... "해당 주문을 찾을 수 없음"
원인
커밋 지연 : Order Service의 트랜잭션이 아직 커밋되지 않아, Delivery Service가 Order Service의 DB를 조회할 때 (Order Service의 getOrder호출 시) Dirty Read를 허용하지 않는다면해당 주문을 찾지 못한 상황이다.

@TransactionalEventListener 로 해결
ApplicationEventPublisher 와 @TransactionalEventListener 를 통해 해결했다.
1. 이벤트 발생 : 주문 생성 완료 후, ApplicationEventPublisher를 통해 OrderCreatedEvent를 발행
2. 실행 분리 : DeliveryNotifier 클래스로 delivery 생성 호출하도록 역할 분리
3. @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 을 통해 Order Service의 DB 트랜잭션이 성공적으로 커밋되고 영구 저장된 직후에 OrderCreatedEvent를 리스너하여 배달 생성 로직 실행
이로 인해 Delivery Service 호출 시 Not Found 문제를 해결했다.
이 과정에서 전달되는값이 있는데,
퍼블리쉬 이벤트와 이벤트 리스너 사이에 전달되는 값 : OrderCreatedEvent
기존엔 savedOrder를 전달을 했다. 하지만 리스너를 사용할 땐 따로 객체(OrderCreatedEvent)를 만들어서 사용하는 이유는 뭘까?
트랜잭션 이벤트 리스너를 사용하면서 OrderCreatedEvent 레코드를 따로 만들었다. 이유는 이벤트 리스너는 호출자 트랜잭션이 커밋된 후에 실행된다. 즉 이벤트 리스너가 실행될 때, createOrder메서드 내의 로컬 변수들 (예:savedOrder객체 자체)은 이미 소멸되었거나Spring 세션 관리 영역에서 벗어났을 수 있다. 따라서 OrderCreatedEvent에 필요한 데이터 (ID, Hub Id, Vendor Id 등) 단순한 값을 복사하여 담아두고, 이벤트 리스너가 실행될 때는 이 복사된 값을 사용하여 전달한다.

'Trouble Shooting' 카테고리의 다른 글
| @OneToOne 관계에서 mappedBy 쪽은 fetch.Lazy 적용이안됨 (0) | 2025.12.08 |
|---|---|
| @Autowired 사용 후 "오토와이어링할 수 없습니다." 오류 해결 방법 (0) | 2025.11.23 |
| rebase merge된 develop 브랜치에서 작업중인 내 브랜치로 pull 하는 방법 (1) | 2025.11.10 |