LabHub
배우기 러닝패스 코스

멱등성 — 두 번 눌러도 한 번만 결제되게 · 같은 키와 같은 요청은 다르다 · 실습

같은 키와 같은 요청은 다르다

LabHub 에서 이어서 보기

목표

키 범위·본문 정규화·재사용 충돌을 분리해 재전송을 판정합니다.

왜 중요한가

두 고객이 우연히 같은 멱등 키를 사용하자 한 고객에게 다른 고객의 응답이 반환됐다. 다른 요청에서는 같은 키로 금액을 바꿨는데도 이전 성공을 돌려줬다. 멱등 키 자체만 비교하면 요청의 의미와 보안 경계를 놓친다. 키는 테넌트와 작업 범위에 묶고 본문의 의미는 별도 지문으로 검사해야 한다.

단계

1. /root/work/idem-fingerprint-lab/service.py에서 valid_key(value)는 영문·숫자·밑줄·하이픈 1~64자만 그대로 반환하고 다른 입력은 ValueError입니다.

처음 한 번 준비하세요. 기존 파일은 덮어쓰지 않습니다.

mkdir -p /root/work/idem-fingerprint-labtest -e /root/work/idem-fingerprint-lab/service.py || cp /opt/fixtures/ten_labs/idem-fingerprint-lab/service.py /root/work/idem-fingerprint-lab/service.pycd /root/work/idem-fingerprint-lab

2. /root/work/idem-fingerprint-lab/service.py에서 canonical(body)는 dict만 받아 sort_keys=True, separators=(',',':'), ensure_ascii=False, allow_nan=False인 JSON 문자열로 반환합니다. 직렬화 불가능한 값은 ValueError로 통일합니다.

3. /root/work/idem-fingerprint-lab/service.py에서 fingerprint(body)는 canonical(body)의 UTF-8 바이트에 SHA-256을 적용한 64자 hex 문자열입니다.

4. /root/work/idem-fingerprint-lab/service.py에서 scoped_key(tenant, method, path, key)는 tenant와 key를 valid_key로 검증하고, method를 대문자로 바꿉니다. path는 /로 시작하는 문자열이어야 합니다. 네 값을 JSON 배열로 separators=(',',':') 인코딩해 반환합니다.

5. /root/work/idem-fingerprint-lab/service.py에서 classify(record, digest)는 record=None이면 'new', record['fingerprint']==digest면 'replay', 아니면 'conflict'입니다.

6. /root/work/idem-fingerprint-lab/service.py에서 remember(records, key, digest, response)는 새 키에 {fingerprint:digest, response:response의 deepcopy}를 저장합니다. 이미 있으면 ValueError이고 기존 기록은 보존합니다.

7. /root/work/idem-fingerprint-lab/service.py에서 replay(record)는 record['response']의 깊은 사본입니다.

8. /root/work/idem-fingerprint-lab/service.py에서 execute(records, tenant, method, path, key, body, action)는 scope와 지문을 계산합니다. new면 action() 결과를 remember하고 사본 반환, replay면 기존 응답 사본 반환, conflict면 ValueError입니다. action 예외는 전파하고 기록을 남기지 않습니다.

참고

8단계

  1. 키 문법을 검증한다
  2. 객체 순서는 접고 배열 순서는 보존한다
  3. 본문 지문을 계산한다
  4. 테넌트와 작업으로 키를 구분한다
  5. 세 가지 판정을 구분한다
  6. 응답을 저장할 때 복사한다
  7. 응답을 읽을 때도 복사한다
  8. 업무 함수를 한 번만 호출한다