본문으로 건너뛰기
뒤로

이벤트로 다음 작업을 시작해 본다

초안아직 소유자가 검토하고 실습해 확인하지 않은 글입니다. 학습 기록이 아니라, 확인 전의 글입니다.

읽기 약 5분 · 실습 시간 별도

예약이 완료됐다는 이벤트를 변환 서비스가 받고, 변환 완료 이벤트를 저장 서비스가 받는 방식으로 업무를 연결할 수 있다. 다음 단계의 선택이 하나의 조정 코드에 모여 있지 않고 각 참여자의 이벤트 처리 코드에 나뉜다. 이런 Saga 조정 방식을 코레오그래피라고 한다. 이벤트가 있다는 사실만으로 Saga가 되는 것은 아니며, 관련된 로컬 변경과 실패 후 대응이 하나의 업무를 이루도록 설계해야 한다.

가상 문서 처리에 적용하면 예약 서비스는 예약을 저장한 뒤 ReservationCreated를 전달한다. 변환 서비스는 문서 식별자를 이용해 결과를 만들고 ConversionCompleted를 전달한다. 저장 서비스가 결과를 저장한 뒤 ResultStored를 전달하면 예약 서비스가 확정한다. 이름은 학습 예제용이며 실제 메시지 계약으로 고정한 것은 아니다.

다음 단계는 이벤트를 받는 쪽에서 정한다

코레오그래피에서는 생산자가 모든 후속 소비자를 알아야 하는 것은 아니다. 예약 서비스는 예약이 만들어졌다는 사실을 알리고 변환 서비스가 그 사실에 반응할 수 있다. 알림이나 감사 기록처럼 같은 사실에 독립적으로 반응하는 기능을 붙일 때 자연스럽다. 그러나 업무 전체의 순서가 없어지는 것은 아니다. 어느 이벤트를 누가 받아 어떤 조건에서 발행하는지에 순서가 표현된다.

받은 이벤트담당 참여자정상 처리 뒤 전달할 사실
예약 생성변환변환 완료
변환 완료결과 저장결과 저장 완료
결과 저장 완료예약 관리예약 확정
변환 실패예약 관리예약 해제

위 표는 메시지 전달 기술을 결정하지 않는다. 브로커를 사용할 수도 있고 실습에서는 제한된 기능의 메모리 큐로 흐름을 관찰할 수도 있다. 단, 메모리 큐에서 성공했다고 영속 메시지 전달이나 장애 복구를 검증한 것은 아니다. 실험 기록에는 큐가 프로세스 종료 후 살아남는지, 중복·순서·재전송을 어떻게 취급하는지 써야 한다.

코드의 형태는 다음처럼 작을 수 있다. 중요한 것은 이 handler가 전체 업무를 책임지는 코드가 아니라 변환 단계의 담당이라는 점이다.

on ReservationCreated(event):
  result = convert(event.documentId, event.revision)
  emit ConversionCompleted(event.requestId, result.reference)

변환이 실패했을 때는 어떻게 될까? 예외를 로그에 남기는 것으로 충분하지 않다. 예약을 해제할 담당자가 실패 사실을 알 수 있어야 한다. 재시도할 수 있는 일시 장애인지 잘못된 문서처럼 중단해야 하는 오류인지도 구분한다. 어떤 실패를 최종 실패 이벤트로 바꿀지는 변환 참여자와 전체 업무의 계약으로 정한다.

느슨하게 연결돼도 계약은 남는다

코레오그래피를 쓰면 참여자들이 서로의 주소를 직접 몰라도 되는 경우가 있다. 그렇다고 독립적으로 아무 변경이나 할 수 있는 것은 아니다. 이벤트의 의미, 필수 필드, 중복 처리 기준, 발행 시점이 계약이다. ConversionCompleted가 임시 파일 생성만 의미하는지 영구 보관이 끝났다는 뜻인지가 달라지면 저장 참여자의 행동도 달라진다.

이벤트가 과거 사실을 나타내는지 특정 작업을 요청하는지도 구분하면 좋다. ReservationCreated를 받았다고 언제나 변환해야 한다면 그 관계 자체가 업무 규칙이다. 나중에 승인 대기가 추가되면 누가 승인을 기다리고 어떤 이벤트에서 변환을 시작할지 변경해야 한다. 전체 흐름을 보려면 여러 handler와 이벤트 계약을 함께 읽게 된다.

이 특성은 무조건적인 단점이 아니다. 서로 독립적인 반응이 주된 시스템에서는 중앙 조정 코드를 추가하는 편이 불필요할 수 있다. 반면 순서와 분기가 계속 바뀌고 실패 후 후속 단계를 한곳에서 파악해야 한다면 분산된 조정 코드의 추적 비용이 커질 수 있다. 참여자 수만 세기보다 변경 시 함께 검토해야 하는 계약의 범위를 본다. AWS의 코레오그래피 설명도 단순한 흐름과 복잡한 의존을 구별해 검토한다.

변환 실패 하나를 끝까지 추적한다

이 글에서 제안하는 실험은 메시지 브로커의 성능 시험이 아니다. 동일 업무 식별자로 예약부터 실패 처리까지 이어지는지 확인한다. 각 참여자는 받은 이벤트 식별자, 업무 식별자, 변경 전후 상태를 기록한다. 고유 메시지 식별자와 업무 식별자는 목적이 다르다. 전자는 같은 전달의 중복을, 후자는 하나의 업무에 속한 여러 전달을 구별하는 데 사용한다.

  1. 예약 완료 이벤트를 전달해 변환 handler를 실행한다.
  2. 변환 함수에서 처리 불가능한 문서 오류를 발생시킨다.
  3. 최종 실패 이벤트를 받아 예약을 해제하도록 연결한다.
  4. 이벤트 목록과 예약 원장을 대조한다.

예상 결과는 변환 실패가 예약 관리 쪽까지 전달되면 예약이 해제되는 것이다. 실패 이벤트를 받는 handler를 등록하지 않았다면 예약은 남을 수 있다. 그런 경우 메시지 플랫폼을 바꾸기 전에 누가 실패를 처리해야 하는지 계약부터 확인한다. 실제 실험 결과와 재현 코드는 후속 검토에서 보강한다.

이번 구조에서도 DB 변경 직후 이벤트 발행 전에 종료될 수 있다. 그 문제는 코레오그래피라는 조정 방식만으로 해결되지 않는다. 중복 이벤트를 받았을 때 같은 변환이나 예약 확정이 반복되는 문제도 별도다. 앞으로 outbox와 멱등성을 배울 때 이 예제를 그대로 사용한다.

이해 확인

변환 실패가 로그에는 있지만 예약은 그대로라면 어느 계약과 handler부터 살펴봐야 할까? 또 생산자와 소비자가 서로의 HTTP 주소를 몰라도 이벤트 필드를 마음대로 바꾸면 안 되는 이유는 무엇일까?

참고 자료



댓글