LostCatBox

hanghae-8week

Word count: 527Reading time: 3 min
2025/05/17 Share

이번주 발재 요약

  • 이벤트로 개발할 경우 장점?

    • 각 도메인을 독립적으로 관리한다. 주문은 주문. 결제는 결제.

    • 이벤트로 안한다면 하나의 TX에 모든 로직들 구현시 강결합된 상태로 계속 늘어날수밖에없다

    • 보통 이벤트 발생시점 = 정상적으로 로직이 동작했어요.

    • 하나의 큰 트랜잭션에서 결제, 재고 차감을 한다면 복잡한 구현 없이 트랜잭션 처리로 롤백이 가능해요.
      결제는 결제대로 
      재고는 재고대로 로직을 수행해야 하죠.
      
      결제가 성공하고 해당 이벤트(결제완료)를 발행하면
      재고 도메인이 해당 이벤트를 수집해서 재고를 차감해요.
      이때 재고가 없는 케이스..
      
      우리는 주문을 실패처리해요. 
      근데... 이미 위에 트랜잭션은 커밋이 완료된 상황.
      
      재고 차감에 실패했어요~ 라는 이벤트(재고차감 실패)를 다시 발행해서 주문 서비스에 해당 주문의 취소, 실패 등의 상태로 변경해야한다.
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17

      -





      # 공개 Q/A

      - 파사드 없이 이벤트로만 하려면 요청의 시작은 어떻게 해야하나요? 예를 들어, 주문의 경우에는 주문 서비스에서 먼저 주문 상태를 변경하는 것으로 시작하고 그 이벤트를 전파하는 형태로 가야하는건가요?
      - 유저 입장에서는 주문을 결제한다. ., 즉 시작점은 주문을 결제하는것이다.
      - 맞아요



      - 각 도메인의 이벤트 객체, 이벤트 발행서비스, 이벤트 구독서비스(샘플 코드에 있는 `PaymentSuccessEvent`, `PaymentEventPublisher`, `PaymentEventListener`)는 클린 레이어드 아키텍처 구조에서 어느 레이어에 위치해야 할까요? 만약, 도메인 레이어에 위치해야 한다면 MSA 구조에서는 상관 없겠지만 **다른 도메인의 서비스를 호출해서 DIP**에 위배될 것 같은데 코치님 의견이 궁금합니다!
      MSA에서는 잘은 모르지만 `StockClient` 같은 걸 주입받기 때문에 DIP는 위배되지 않는 것 같습니다.
      package kr.hhplus.be.ecommerce.domain.payment; // 📦 결제 도메인 패키지

    @Component
    public class PaymentEventListener {

    private final StockService stockService; // ⚠️ 재고 도메인 서비스를 직접 주입받아 DIP 위배
    
    public PaymentEventListener(StockService stockService) {
        this.stockService = stockService;
    }
    
    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void paymentSuccessHandler(PaymentSuccessEvent event) {
        PaymentSuccessPayload payload = new PaymentSuccessPayload(event);
        stockService.deduct(payload); // 📌 재고 차감
    }

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    - 이벤트 발행은 주문, 이벤트 구독은 결제이다.
    - 단순히 이름이 Order라고해도, 구독해서 처리해야하는 비즈니스로직은 order 패키지가 아닌 payment 패키지에서 위치시킴.







    - 여러 개의 @TransactionalEventListener(Async)로 비동기 후처리를 구성하려고 할 때, 예: “결제 완료 → 주문 정보 전송 → 영수증 발급”처럼 순서가 중요한 후속 작업이라면, 이벤트 리스너 간 순서 보장을 Spring 단에서 어떻게 처리할 수 있을까요? 또는 외부 시스템을 이용해야 할까요?
    - 순서가 매우 중요하다면, 동기적+같은 Thread에서 처리해야함



    - 파사드 안에서 각 서비스에 대한 성공/실패 이벤트가 필요한건가요?

    결제파사드(){
    결제
    결제 기록
    }

    
    - 따로가 아니라 파사드 모두 성공했을경우, 이벤트 발행
    
    
    
  • 특정 로직에서 2가지의 이벤트가 발행되는 경우도 있나요 ? 아니면 다수의 이벤트가 발행되어야 하는 경우에는 관심사가 많다고 판단하여 로직을 분리하는 것이 좋나요 ?
    • 행위에 대한 성공, 실패가 나뉨. 행위에 다음로직을 포함하지않으므로
      • “결제완료” 이벤트 발생이 끝임
      • 2가지의 이벤트가 발행되는 케이스가 없음.
  • 이벤트를 어떻게 정의해하고 호출하고 리스닝할지 고민하던 중. 아래와같은 고민이 생겼습니다.

    1. 이벤트가 도메인에만 정의될 수 있을까요? 어플리케이션에도 정의가 필요할까요?

      1. 도메인에서는 dto개념이라, 애플리케이션에서 구현함
    2. 이벤트를 호출하는 것은 어플리케이션 레이어와 도메인 중 어디에서 해야할까요?

      1. 애플리케이션에서 event발생, 호출하는것도 발생
    3. 이벤트 리스닝은 어플리케션 레이어에서만 하면 될까요?

      1. 애플리케이션에서 리스닝함.
  • 외부 API호출 에 대해서 어플리케이션 레이어에서 포트인터페이스를 만들고, 인프라스트럭쳐에서 그 포트를 구현해줘도 괜찮을까요? 아니면 무조건 도메인을 통해서 호출하시는 것을 선호하시나요?

    • 인터페이스의 호출만 하면됨. 구현체는 그냥 단순히 Bean
  • 이벤트 기반 아키텍처인 경우, 서비스 전체 파악은 어떻게 진행되나요? (주문 완료가 일어나면 무슨 일들이 일어나나)

    A이벤트 발행B에서 A 수신 → B’ 이벤트 발행C에서 B’이벤트 수신 → C’ 발행D에서 A 수신 → ………E에서 C’ 이벤트 수신 → …

    이렇게 진행될 경우 전체적인 흐름 파악이 어려워 관리가 어려울 것 같은데.. 전체 흐름 파악 필요도 없다고도 보시는 것인지.. 다른 방법이 있는 것인지 궁금합니다

    • 이벤트 기반 아키텍처의 가장 큰 단점.
CATALOG
  1. 1. 이번주 발재 요약