원화 입금 승인 취소 요청이 중복 생성되어 하나를 지워야 했고 관리 화면에서 삭제를 눌렀다. 아무 일도 안 일어났고 목록을 새로고침해도 그대로 남아 있었다.
Table of contents
Open Table of contents
증상 — 에러 없이 안 지워진 건
오류 메시지도 없고 화면에 아무 변화도 없었다. 실패했다는 신호가 어디에도 없으니 눌린 것인지도 확실하지 않았다.
이런 상태에서는 화면만 봐서는 다음에 무엇을 볼지가 안 정해진다. 그래서 브라우저 개발자 도구의 네트워크 탭으로 화면이 실제로 무엇을 보내는지부터 확인했다.
원인 — 라벨과 실제 동작의 차이
화면은 삭제라고 적혀 있는데 실제로는 POST /api/deposit-cancels/{id}/revoke 를 부르고 있었다. 요청 본문에는 force: false 가 실려 있었다.
DepositCancelHandler 를 보니 !force && status == COMPLETED 이면 아무것도 하지 않고 그대로 반환했다. 이 취소 요청의 상태가 COMPLETED 였으므로 그 분기로 빠진 것이었다.
판단 기준 — 조용한 실패는 코드를 봐야 한다
같은 결과라도 완료 상태는 철회할 수 없다는 메시지가 뜨면 사용자가 이유를 안다. 조용히 200 을 돌려주면 아무 단서 없이 안 된 상태만 남는다.
화면에 단서가 없는 실패는 코드를 열기 전까지 원인이 안 나오므로 조사 비용이 통째로 사람에게 넘어온다. 조용한 실패를 만나면 화면을 더 보지 않고 바로 코드로 간다.
조치 — 강제 플래그와 자동 보정
force: true 를 주면 다른 작업 유형으로 동작하는데 옛 화면에서는 그것을 켤 방법이 없었다. 두 가지 방법이 있었다. 화면에 강제 철회 버튼을 추가해 배포하는 것과, curl -X POST 로 API 를 직접 부르는 것이다. 뒤쪽을 골랐다. 이유는 중복 건이 하나뿐이고 배포는 다음 주 정기 일정에 묶여 있어서다. 대신 그 호출을 누가 언제 했는지가 화면 이력에 안 남는 것을 감수했고, 그래서 audit_logs 에 손으로 한 줄을 넣었다.
완료된 취소 요청이면 잔고에 반영됐을 수 있어서 강제로 철회하면 잔고가 어긋날지 확인했다. 철회 시 balance_adjustment 이벤트가 ActiveMQ 로 자동 발행되고 있어서 수동 보정이 필요 없었고 그것을 확인한 덕에 이중 보정을 피했다.
소프트 삭제가 바꾼 실행 판단
엔티티를 보니 삭제가 행을 지우는 것이 아니라 deleted_at 에 시각을 남기는 방식이었다. 조회에서만 빠지고 데이터는 남아 있으므로 되돌릴 수 있다.
되돌릴 수 있는 작업과 없는 작업은 같은 위험도로 다룰 필요가 없다. 복구가 가능하다는 확인 하나가 실행할지 말지의 판단을 바꿨다.
정리
- 화면의 삭제가
revokeAPI 호출일 수 있다 - 버튼 라벨이 실제 동작을 말해 주지 않는다
- 조건에 안 맞으면 조용히 아무것도 안 하고 오류도 없다
- 조용한 실패는 화면에 단서가 없어 코드를 봐야 한다
force플래그를 화면에서 못 켜면 API 를 직접 부르고 이력을 남긴다- 부수 처리가
balance_adjustment이벤트로 자동되는지 확인해 이중 보정을 막는다 - 삭제가
deleted_at소프트 삭제면 되돌릴 수 있다 - 복구 가능 여부가 실행 판단을 바꾼다