−Gregor Hohpe의 2003년 기업 통합 발표는 파일 전송, 공유 데이터베이스, 원격 호출, 메시징을 비교한다. 애플리케이션을 연결할 때 데이터 전달의 시점과 시스템 간 결합을 함께 다뤄야 했던 배경을 보여준다.[각주 3]
Hohpe와 Bobby Woolf의 Transactional Client 패턴은 메시징 세션에서 여러 송수신 동작을 하나의 커밋으로 묶는 방식을 설명한다. 이 웹 설명의 범위는 세션의 메시징 동작이다. 별도 DB의 업무 변경까지 같은 원자성에 넣으려면 그 자원과 트랜잭션 관리자의 참여 여부를 추가로 확인해야 한다.[각주 4]
메시징 안에서의 트랜잭션과 서비스 DB의 트랜잭션은 서로 다른 경계일 수 있다. 아웃박스가 놓이는 지점은 바로 그 사이이며, 업무를 확정했다는 사실을 이후 전달 가능한 기록으로 연결한다.
서비스별 DB와 사건 발행을 패턴으로 정리하다 (2018)
Chris Richardson의 『Microservices Patterns』는 2018년 출판됐고, 저자의 패턴 카탈로그는 서비스 데이터 변경과 메시지 발행을 연결하는 Transactional Outbox를 설명한다. 이 출판 기록은 패턴이 정리된 시점을 보여주는 자료로 쓴다.[각주 5][각주 2]
생산자는 업무 데이터와 발행할 사건을 같은 로컬 트랜잭션에 기록한다. 전달자는 커밋된 사건을 별도로 읽어 브로커에 보낸다. 업무 요청의 실행이 끝나더라도 전송할 일이 DB에 남으므로, 전달을 나중에 이어갈 수 있다.
이 구조에서 함께 확정되는 것은 생산자 DB의 두 기록이다. 생산자, 브로커, 수신자가 동시에 한 번의 커밋을 하는 구조는 아니다. 수신자는 자기 DB의 상태를 별도로 확정한다.
테이블 폴링에서 로그 기반 전달까지
Richardson의 카탈로그는 전달자를 구현하는 방식으로 폴링과 트랜잭션 로그 추적을 구분한다. 폴링은 커밋된 outbox 행을 조회하며, 로그 추적은 DB가 남긴 변경 기록을 이용한다. 두 방식은 전송 대상을 찾아내는 경로가 다르다.[각주 2]
Debezium의 Outbox Event Router는 변경 데이터 캡처(CDC)로 얻은 outbox 레코드를 이벤트로 라우팅하는 구체적인 도구다. 사건 ID, 집계 대상 식별자, 타입, 페이로드 같은 필드를 메시지에 연결한다.[각주 6]
폴링은 조회 주기·인덱스·작업 선점의 영향을 받고, 로그 기반 전달은 로그 보존·체크포인트·커넥터의 수명을 함께 관리한다. 어느 쪽이든 전달자가 중간에 멈췄을 때 다시 읽을 수 있는 위치와 기록이 필요하다.
중복 전달과 수신자의 로컬 원자성
전달자가 메시지를 보낸 뒤 “보냈다”는 상태를 기록하기 전에 종료되면 같은 사건을 다시 보낼 수 있다. AWS의 패턴 설명도 중복 전달 가능성과 수신자의 멱등 처리를 다룬다.[각주 7]
수신자는 사건 ID를 기준으로 이미 처리했는지 구별할 수 있다. 처리한 ID와 업무 효과를 같은 로컬 트랜잭션에 넣으면, 재전달된 사건이 업무 효과를 다시 만드는 일을 막는 데 사용할 수 있다. 사건 ID의 범위와 보존 기간도 그 계약에 포함된다.
이때 중복 방지는 DB 안에서 함께 기록한 효과의 범위에 적용된다. 외부 결제나 메일처럼 별도 시스템의 효과가 있다면 그 시스템과의 요청 식별·처리 결과 계약도 필요하다.
순서·보존·서비스 조정으로 넓어지는 책임
같은 주문의 생성과 취소처럼 순서가 의미를 바꾸는 사건은 전달 순서를 설계해야 한다. 수신자를 병렬화할 때도 어떤 집계 대상 안에서 순서를 유지할지 정한다. 메시지의 키와 파티션은 이 책임을 구현하는 수단이 된다.[각주 2][각주 6]
보존은 전달자의 진행 위치와 복구 가능성을 좌우한다. 커밋된 사건을 전달 전에 지우면 이어갈 기록이 사라지고, 전달 이후에도 무한히 쌓으면 저장 비용이 커진다. 확인 지점과 복구 기간에 맞춰 삭제 조건을 정한다.
아웃박스는 상태 변경을 전달하는 경계를 맡는다. 사가는 여러 서비스의 업무 단계를 조정하고, 이벤트 소싱은 사건을 상태의 원본으로 삼는다. 아웃박스 테이블을 쓴다는 사실만으로 업무 상태 전체가 이벤트 소싱으로 바뀌지는 않는다.
두 저장 경계와 전송 의도를 확인하는 예제
그림은 생산자와 수신자의 로컬 트랜잭션을 각각 묶어 보여준다. 아래 PostgreSQL 코드는 그중 생산자의 주문과 사건 기록만 실행한다. 전달자나 수신자 코드는 포함하지 않는다.
[SVG 다이어그램]
+Gregor Hohpe의 2003년 발표 자료에는 애플리케이션을 연결할 때 데이터를 언제 전달하는지와 시스템끼리 얼마나 결합되는지를 함께 다뤄야 했던 배경이 드러난다. Hohpe와 Bobby Woolf의 Transactional Client 패턴은 클라이언트와 메시징 시스템의 세션을 트랜잭션으로 만들어 클라이언트가 트랜잭션 경계를 정하게 한다. 보낸 메시지는 커밋해야 채널에 들어가고, 받은 메시지는 커밋해야 채널에서 빠진다.[각주 2][각주 5]
이 웹 설명이 다루는 범위는 세션의 메시징 동작까지다. 별도 DB의 업무 변경까지 같은 원자성에 넣으려면 그 자원과 트랜잭션 관리자가 참여하는지를 따로 확인해야 한다. 메시징 안의 트랜잭션과 서비스 DB의 트랜잭션은 서로 다른 경계일 수 있고, 그 사이에 놓인 아웃박스는 업무를 확정했다는 사실을 나중에 전달할 기록으로 남긴다.
서비스별 DB와 사건 발행의 패턴화 (2018)
Chris Richardson은 2018년 10월 출판된 『Microservices Patterns』에서 서비스별 DB 갱신과 사건 발행의 문제를 다뤘다. 그의 현재 패턴 카탈로그는 업무 데이터와 발행할 사건을 같은 로컬 트랜잭션에 기록하는 방식을 Transactional Outbox라는 이름으로 설명하고, 다른 이름으로 Application events를 병기한다. 전달자는 커밋된 사건을 따로 읽어 브로커에 보낸다. 업무 요청의 실행이 끝나도 전송할 일은 DB에 남아 있으므로 전달을 나중에 이어갈 수 있다.[각주 4][각주 3]
이 구조에서 함께 확정되는 것은 생산자 DB의 두 기록뿐이고, 한 번의 커밋이 묶는 범위도 거기까지다. 수신자는 자기 DB의 상태를 별도로 확정하며, 브로커는 그 사이에서 메시지를 옮긴다.
테이블 폴링과 로그 기반 전달
Richardson의 카탈로그는 전달자를 구현하는 방식으로 폴링과 트랜잭션 로그 추적을 구분한다. 폴링은 커밋된 outbox 행을 조회해서, 로그 추적은 DB가 남긴 변경 기록을 읽어서 전송 대상을 찾는다.[각주 4]
Debezium의 Outbox Event Router는 변경 데이터 캡처(CDC)로 얻은 outbox 레코드를 이벤트로 라우팅하는 구체적인 도구다. 기본 설정에서 사건 ID(id)는 메시지 헤더에, 집계 대상 식별자(aggregateid)는 메시지 키에 들어가고, 집계 대상 타입(aggregatetype)은 토픽 이름에 쓰이며, 페이로드는 메시지 값이 된다. 사건 타입 같은 다른 열은 table.fields.additional.placement 설정으로 헤더나 엔벨로프에 추가한다.[각주 6]
폴링은 조회 주기·인덱스·작업 선점의 영향을 받고, 로그 기반 전달은 로그 보존·체크포인트·커넥터의 수명을 함께 관리한다. 어느 쪽이든 전달자가 중간에 멈췄을 때 다시 읽기 시작할 위치와 기록이 필요하다.
중복 전달과 멱등한 수신
전달자가 메시지를 보낸 뒤 “보냈다”는 상태를 기록하기 전에 종료되면 같은 사건을 다시 보낼 수 있다. AWS의 패턴 설명도 중복 전달 가능성과 수신자의 멱등성 문제를 함께 다룬다.[각주 7]
수신자는 사건 ID를 보고 이미 처리한 사건인지 구별할 수 있다. 처리한 ID와 업무 효과를 같은 로컬 트랜잭션에 넣어 두면, 재전달된 사건이 업무 효과를 다시 만드는 일을 막을 수 있다. 사건 ID의 범위와 보존 기간도 이 중복 판별 계약에 포함된다.
이 중복 방지가 미치는 범위는 같은 DB에 함께 기록한 효과까지다. 외부 결제나 메일처럼 다른 시스템에 남는 효과가 있다면, 그 시스템과도 요청을 식별하고 처리 결과를 주고받는 계약이 필요하다.
순서, 보존, 다른 패턴과의 경계
같은 주문의 생성과 취소처럼 순서에 따라 의미가 달라지는 사건은 전달 순서를 설계해야 한다. 수신자를 병렬화할 때도 어느 집계 대상 안에서 순서를 지킬지 정해야 하는데, 메시지의 키와 파티션이 이를 구현하는 수단이다.[각주 4][각주 6]
사건 기록을 언제 지우는지가 전달자의 진행 위치와 복구 가능성을 좌우한다. 커밋된 사건을 전달 전에 지우면 이어갈 기록이 사라지고, 전달한 뒤에도 계속 쌓아 두면 저장 비용이 커진다. 삭제 조건은 확인 지점과 복구 기간에 맞춰 정한다.
아웃박스는 상태 변경의 전달을 맡고, 사가는 여러 서비스의 업무 단계를 조정한다. 이벤트 소싱에서는 사건 자체가 업무 상태의 원본이다. 이 구분은 메시지를 보관하는 테이블의 유무가 아니라, 어느 기록을 상태의 근거로 삼는지에 달려 있다.
두 저장 경계 예제
그림은 생산자와 수신자의 로컬 트랜잭션을 각각 묶어 보여준다. 브로커 없이 전달 코드가 수신자를 직접 호출하는 경우라서 sent는 수신자가 처리한 뒤에 표시되며, 브로커를 두면 전달자가 브로커의 수신 확인을 받은 뒤 sent를 표시한다. 아래 PostgreSQL 코드는 그중 생산자의 주문과 사건 기록만 실행한다. 전달자와 수신자 코드는 이 예제의 범위 밖이다.
[SVG 다이어그램]