사용자는 저장 응답을 못 받으면 다시 누를 수 있다. 서버는 첫 요청을 이미 처리했을 수도 있다. 이때 두 번째 주문을 만드는 대신 같은 업무의 결과를 다시 확인할 수 있어야 한다.
기존 사건 ID 중복 방지에서 더 들어가, 이번에는 실제 로컬 트랜잭션에 요청 결과를 보관하는 합성 실험을 만들었다. 외부 결제나 발주를 한 번만 실행한다는 보장을 내세우는 코드는 아니다.
두 번 누른 버튼과 두 개의 업무를 구분했다
화면에서 버튼을 잠시 비활성으로 만들면 실수는 줄일 수 있다. 하지만 새로고침, 네트워크 재시도나 다른 창의 요청까지 그 버튼 하나가 막지는 못한다. 나는 서버가 같은 의도를 알아볼 수 있도록 요청 식별자를 받았다. 식별자의 범위는 같은 회사와 사용자다. 다른 사람의 결과를 단순히 같은 문자열이라는 이유로 돌려주지 않는다.
문서 번호만 요청 키로 쓰지도 않았다. 같은 문서는 나중에 다른 수량으로 다시 고칠 수 있기 때문이다. 요청은 한 번의 의도를 가리키고 문서는 계속 바뀌는 업무 대상을 가리킨다. 이번 합성 코드에서는 키가 허용된 짧은 식별자인지 검사하며 실제 식별자의 생성과 전달은 연결할 클라이언트의 계약으로 남겨 둔다.
같은 키에는 같은 내용이어야 한다
키만 같다고 무조건 이전 결과를 주면 다른 수량의 변경이 성공한 것처럼 보일 수 있다. 그래서 명령의 행동·문서·예상 버전·사유와 수량을 정해진 JSON 표현으로 만들고 SHA-256 지문을 계산했다. 같은 키와 같은 지문이면 이전 확정 결과를 돌려주고, 같은 키에 다른 내용이면 request_key_reused로 거절한다.
지문은 자료가 같은지 비교하기 위한 것이다. 누가 요청했는지 인증하거나 민감한 내용을 암호화하는 기능은 아니다. 실제 서비스의 서버는 먼저 사용자와 회사 범위를 확인해야 한다. 이번 Actor는 신뢰할 테스트 문맥이며 브라우저가 임의로 만든 역할을 인증 결과로 쓰면 안 된다. 결과 재사용에서도 권한 경계를 생략하지 않았다.
문서 변경과 결과 보관 사이를 비우지 않았다
문서를 저장한 뒤 별도 단계에서 요청 결과를 쓰면 둘 사이에서 오류가 날 수 있다. 재시도는 이전 성공을 찾지 못해 다시 문서를 고치게 된다. 이번 SQLite 실험은 문서·사건·후속 대기 기록·성공 요청 결과를 같은 트랜잭션에서 확정한다. 어느 일부만 남지 않는지 강제 실패로 확인했다.
같은 요청 두 개를 별도 연결에서 동시에 실행하는 검사도 만들었다. 쓰기 트랜잭션을 먼저 잡은 요청이 성공을 남기고, 다음 요청은 그 결과를 읽는다. 결과는 둘 다 성공을 가리키지만 replayed가 하나에만 표시되고 실제 사건과 대기 기록은 하나다. 버튼을 두 번 눌렀다는 사실과 업무가 두 번 바뀌었다는 결과를 구별한 것이다.
응답을 잃어도 확정 결과를 다시 읽는다
실험에서는 성공 응답을 받은 뒤 프로그램을 다시 열어 같은 요청을 보냈다. 문서의 예상 버전은 이미 과거 번호지만 같은 요청의 확정 결과를 찾으면 새로 고치지 않고 그 결과를 돌려준다. 이 경우 버전 충돌로만 처리하면 첫 요청이 성공했다는 사실을 사용자가 확인하기 어렵다. 요청의 결과와 문서의 현재 최신 상태도 서로 다른 조회다.
재응답에 담긴 문서는 그 요청이 완료됐을 때의 값이다. 이후 다른 변경이 있었다면 최신 문서는 다시 조회해야 한다. 실패해 아무것도 확정되지 않은 요청은 이 예제에서 성공 결과로 저장하지 않는다. 처리 중 원격 작업을 복구하는 프로토콜이나 요청 키의 만료·보관 정책도 구현하지 않았다. 장기 운영을 연결할 때 별도로 정할 부분이다.
한 번의 로컬 변경과 외부 실행 보장은 다르다
이 패키지는 같은 로컬 DB 안의 변경을 대상으로 한다. 외부 시스템에 문서를 보낸 뒤 응답을 잃으면 그쪽이 받았는지 별도로 확인해야 한다. 네트워크를 포함한 모든 작업을 정확히 한 번 실행한다는 말로 넓히지 않는다. 실제 발송 없이도 어디까지 확정했고 어떤 결과를 다시 읽는지 설명할 수 있어야 한다고 판단했다.
독립 테스트는 동시 중복 요청, 다른 내용의 키 재사용, 프로그램 재개 뒤 결과 재응답, 중간 오류의 전체 되돌림을 확인한다. 요청 기록은 자동으로 만료되지 않으므로 실제 서비스에 붙일 때는 보관과 재사용 정책도 정해야 한다. 공유 코드와 실행 기록에서 이 범위를 읽고 자신의 업무와 저장소에 맞춰 확장할 수 있다.