새 관리자 API 로 코인을 등록해야 하는데 형식을 몰라서 GET /api/coins/{id} 로 기존 것을 조회해 템플릿으로 쓰려고 했다. 값만 바꿔 POST /api/coins 로 보냈더니 필수 필드가 없다는 400 이 돌아왔다.
Table of contents
Open Table of contents
원인 — 조회 응답과 등록 요청의 형식 차이
두 형식을 나란히 놓고 보니 같은 것을 다른 이름으로 부르고 있었다. 체인은 응답에는 chainId 로 있고 등록에서는 chain 코드 문자열을 받으며 networkFee 와 network_fee 처럼 표기도 각각 달랐다.
지갑 설정은 이름이 아니라 구조 자체가 달랐다. 조회 결과를 그대로 등록 입력으로 쓸 수 있다는 전제가 성립하지 않았다.
검증 — 검증과 처리가 다른 필드를 본다
더 이상한 것은 CoinCreateValidator 와 실제 CoinService.create() 가 서로 다른 필드를 본다는 점이었다. 검증은 network_fee 를 필수로 요구하고 처리는 networkFee 를 읽고 있었다.
그러면 양쪽 이름을 다 보내야 검증을 통과하면서 동작한다. 하나만 보내면 검증에서 막히거나 값이 빈 채로 처리되고 뒤쪽은 오류도 안 난다.
검증 재사용이 남긴 흔적
git log 를 보니 원래 옛 콘솔용 검증 클래스가 있었고 리브랜딩으로 새 API 가 생기면서 처리 코드만 새로 쓴 것으로 보였다. 두 화면이 다른 필드명을 쓰는데 검증만 공유한 셈이다.
검증 클래스 안에 옛 콘솔의 중첩 구조 wallet.config 를 참조하는 코드도 남아 있었다. 새 요청에는 그런 구조가 없으므로 그 부분은 항상 빈 값으로 검증을 통과한다.
판단 기준 — 실제 성공한 요청이라는 권위
두 가지 방법이 있었다. 각 계층이 요구하는 것을 코드에서 읽어 합집합으로 조립하는 것과, 실제로 들어온 요청을 본문까지 기록하는 request_logs 테이블에서 성공한 요청 하나를 찾는 것이다. 뒤쪽을 골랐다. 이유는 검증과 처리가 다르고 지갑 설정 구조는 문서에도 없어서 합집합은 불필요한 필드까지 보내게 되어서다. 대신 그 테이블에 개인정보가 섞여 있어 조회 권한을 따로 받아야 하는 것을 감수했다.
성공한 클라이언트의 요청 본문 하나가 여러 계층에서 추론한 것보다 확실한 답이다.
주의 — 여기 있다와 확인했다의 차이
여기까지 알아내고 정리했지만 request_logs 를 실제로 조회하지는 않았다. 권한 승인이 늦어졌고 작업 범위가 다른 곳에 있어 이 부분은 다음으로 미뤘다.
그래서 형식을 어디서 확인할 수 있는지를 적으면서 이번에 조회하지 않았으므로 미검증이라고 함께 적었다. 나중에 이 메모를 보는 사람이 확인된 것으로 오해하면 같은 자리에서 다시 막히기 때문이다.
정리
- 조회 응답과 등록 요청의 필드명과 구조가 다를 수 있다
- 그러면 응답을 템플릿으로 재사용할 수 없다
CoinCreateValidator와CoinService가 서로 다른 필드를 볼 수 있다- 검증만 재사용한 흔적이 그런 어긋남을 만든다
- 코드에서 정답을 조립하면 불필요한 것까지 보내게 된다
request_logs의 실제로 성공한 요청 본문이 가장 권위 있는 답이다- 그 기록이 어디에 남는지를 먼저 찾는다
- 여기 있다와 확인했다는 다르므로 안 봤으면 미검증이라 적는다