LabHub
学习 学习路径 课程

멱등성 — 두 번 눌러도 한 번만 결제되게 · 트랜잭션 아웃박스로 주문과 이벤트를 지킨다 · 实验

트랜잭션 아웃박스로 주문과 이벤트를 지킨다

在 LabHub 中继续学习

목표

SQLite 트랜잭션·장애 주입·재전송·소비자 중복 제거를 실제 파일 상태로 검증합니다.

왜 중요한가

정상 요청 한 번의 성공은 경계값과 장애 복구를 보장하지 않습니다. 이 실습은 각 함수의 계약을 작게 구현하거나 테스트한 뒤 실제 실행으로 연결합니다. 코드가 존재하는지나 보고서 문구만 보지 않고 결과·예외·저장 상태를 검사합니다. 앞 단계의 코드를 유지하며 다음 단계로 진행하세요.

단계

1. /root/work/idem-outbox-lab/service.py에서 init_db(path)는 orders(id TEXT PRIMARY KEY, amount INTEGER NOT NULL), outbox(id TEXT PRIMARY KEY, payload TEXT NOT NULL, sent INTEGER NOT NULL DEFAULT 0), consumed(id TEXT PRIMARY KEY), totals(name TEXT PRIMARY KEY, amount INTEGER NOT NULL)를 생성하고 totals에 sales=0을 중복 없이 넣으세요. 첫 준비는 다음 명령으로 합니다.

mkdir -p /root/work/idem-outbox-labcp /opt/fixtures/practice_depth/idem-outbox-lab/* /root/work/idem-outbox-lab/cd /root/work/idem-outbox-lab

2. /root/work/idem-outbox-lab/service.py에서 canonical(order_id, amount)는 비어 있지 않은 문자열 id와 양의 int 금액만 받아 {id, amount}를 sort_keys=True, separators=(',', ':'), ensure_ascii=False인 JSON으로 반환하세요. bool은 금액으로 받지 않으며 잘못된 입력은 ValueError입니다.
3. /root/work/idem-outbox-lab/service.py에서 enqueue(path, order_id, amount, fault=lambda:None)는 BEGIN IMMEDIATE에서 주문 삽입 → fault() → outbox 삽입을 커밋하고 True를 반환하세요. 같은 id·금액 재요청은 False, 금액 충돌은 ValueError입니다. fault가 예외를 내면 양쪽 모두 롤백합니다.
4. /root/work/idem-outbox-lab/service.py에서 pending(path, limit=10)는 sent=0인 (id, payload) 튜플을 id 오름차순으로 최대 limit개 반환하세요. limit는 bool을 제외한 정수 1~100이며 위반 시 ValueError입니다.
5. /root/work/idem-outbox-lab/service.py에서 acknowledge(path, event_id)는 sent=0인 행을 sent=1로 바꾸면 True, 없거나 이미 전송됐으면 False를 반환하세요.
6. /root/work/idem-outbox-lab/service.py에서 dispatch(path, publish, limit=10)는 pending의 각 행을 publish(id, payload)에 전달하고 성공한 뒤 acknowledge하세요. 발행 예외는 전파하고 미전송 상태를 보존합니다. 성공 처리 개수를 반환합니다.
7. /root/work/idem-outbox-lab/service.py에서 consume(path, event_id, payload)는 JSON의 id·금액을 검증하고 event_id 일치를 확인하세요. 같은 트랜잭션에서 consumed에 id를 기록하고 totals의 sales에 금액을 더합니다. 첫 처리 True, 중복 False이며 잘못된 값은 ValueError입니다.
8. /root/work/idem-outbox-lab/service.py에서 total(path)는 sales 합계를 반환하세요. publish가 consume을 성공시킨 직후 ConnectionError를 내게 한 다음 dispatch를 다시 실행해 보세요. 이벤트는 두 번 전달되어도 합계는 한 번만 증가하고 outbox는 최종 sent=1이어야 합니다.

참고

8个步骤

  1. 주문·이벤트·소비 기록을 분리한다
  2. 검증된 이벤트 본문을 만든다
  3. 두 쓰기 사이의 장애를 롤백한다
  4. 미전송 이벤트를 제한된 배치로 읽는다
  5. 발행 확인을 멱등하게 기록한다
  6. 발행 성공 뒤에만 완료 표시한다
  7. 소비 기록과 효과를 원자적으로 묶는다
  8. 전달 두 번·효과 한 번을 재현한다