외부 시스템의 옵션 수정 엔드포인트를 다루고 있었다. 응답 계약을 확인하다 상태 코드가 200인데 실패인 경우를 봤다.
Table of contents
Open Table of contents
상황 — 200인데 실패
응답이 이렇게 온다.
HTTP 200
{ "resultCode": "500", ... }
HTTP 200 인데 resultCode 에는 500 이 들어 있다.
[상태 코드] 전송·처리 자체는 됐다
[바디 코드] 업무적으로 됐나
상태 코드 와 바디 코드 가 서로 다른 것을 말하고 있었다.
원인 — 두 층으로 나뉜 결과
이 방식 자체는 특별한 것이 아니고 많은 시스템이 이렇게 한다. 문제는 부르는 쪽에서 잊기 쉽다는 점이다.
if ($response->successful()) { // 200이면 참
// 성공 처리
}
successful() 만 보면 업무 실패가 성공으로 기록된다.
if (!$response->successful()) {
throw new \Exception('전송 실패');
}
$body = $response->json();
if ($body['resultCode'] !== '0') {
throw new \Exception("업무 실패: {$body['resultCode']}");
}
successful() 과 resultCode 를 두 단계로 나눠 봐야 한다.
HTTP 200 이면 대개 성공이라 이 실수는 평소에 안 드러난다. 실패가 생기는 날에만 그것이 성공으로 남는다.
필드를 빼면 호출이 두 번이 됐다
규격을 더 읽다가 다른 것이 나왔다. 특정 필드를 생략하면 서버 동작 자체가 바뀐다.
[필드 포함] 바로 수정 호출
[필드 생략] 선행 조회 → 값을 얻음 → 수정 호출
생략하면 상대 쪽에서 선행 조회 와 수정으로 호출이 두 번이 된다.
그 필드는 원래 값을 유지해야 하는 것이라 누군가는 현재 값을 알아야 한다. 부르는 쪽이 모르면 서버가 대신 조회하는 편의를 준 것으로 보인다.
[한 번 호출] 실패 지점 하나
[두 번 호출] 선행 조회 실패 + 수정 실패
편의를 얻는 대신 실패 지점이 늘고 그 실패가 다른 모양으로 온다.
선행 조회 파싱 실패 → 상위 서비스 불가 오류
상위 서비스 불가 처럼 우리가 보낸 것과 무관해 보이는 오류가 돌아오는 셈이다.
제약 — 겹칠 때만 나는 오류
오류 코드 하나가 특이했다.
404 = 필드 생략 + 그 하위 항목이 그 상위에 속하지 않음
404 는 두 조건이 동시에 성립할 때만 난다.
[필드 포함] 선행 조회 없음 → 소속을 확인 안 함
[필드 생략] 선행 조회 → 그 안에서 못 찾음 → 404
소속 검사가 선행 조회 의 부수 효과라서 그렇다.
각 조건을 따로 시험하면 둘 다 통과하므로 조합을 적어 두지 않으면 그 경우는 운영에서 처음 만나게 된다.
정리하면 필드를 빼는 쪽이 느리고 검사가 많아서 우리는 값을 알고 있으면 넣는 쪽으로 갔다.
비교 — 우리가 정한 것과 남이 정한 것
응답 경로가 하나 더 있었다.
422 = 프레임워크의 기본 역직렬화 실패
요청이 우리 코드에 닿기 전에 프레임워크가 422 를 자기 형식으로 돌려보내는 경우다.
[우리 검증] 400 + 우리가 정한 JSON 형식
[프레임워크] 422 + 라이브러리가 정한 형식
부르는 쪽에서는 같은 엔드포인트인데 경우에 따라 다른 모양이 온다.
이 422 응답의 본문 문자열을 테스트로 고정할지가 판단이었다.
[고정] 회귀를 잡음. 라이브러리 올리면 깨짐
[안 고정] 버전 올려도 안 깨짐. 형식 변화를 못 잡음
안 고정하기로 했는데 422 의 본문은 우리가 정한 것이 아니기 때문이다.
우리가 안 정한 것을 우리 테스트로 묶으면 라이브러리를 올릴 때마다 무의미하게 깨진다. 422 가 나온다는 것은 확인하되 본문 문자열은 보지 않는 선에서 검증을 멈추기로 했다.
조치 — 조건별 응답 표를 남겼다
확인한 것들을 한 표로 모았다.
200 + resultCode 0 성공
200 + resultCode 그 외 업무 실패
404 필드 생략 + 소속 불일치
422 역직렬화 실패 (프레임워크 경로)
502 선행 조회 실패
200 두 줄만 봐도 상태 코드만으로는 구분되지 않는다.
이 표가 없으면 502 를 받은 사람이 엉뚱한 데를 본다. 502를 보고 상대 서버가 죽었다고 판단해 그쪽에 문의하는데 실제로는 우리가 필드를 빼서 선행 조회가 실패한 것일 수 있다.
어떤 조건이 어떤 코드를 만드는지를 적어 두면 그 왕복이 없어지고 새로 붙이는 사람이 상대에게 물어보는 일도 준다.
정리
- 200이 성공을 뜻하지 않고 본문의 코드를 봐야 한다
- 상태 코드와 업무 결과는 두 층이고 둘 다 확인한다
- 200이면 대개 성공이라 이 실수는 평소에 안 드러난다
- 필드를 생략하면 서버가 선행 조회를 넣어 호출이 두 번이 된다
- 편의를 얻는 대신 실패 지점이 늘어난다
- 무관해 보이는 오류가 우리 요청 때문에 날 수 있다
- 어떤 오류는 두 조건이 겹칠 때만 난다
- 조건을 따로 시험하면 그 조합은 통과한다
- 프레임워크 경로는 우리 검증 계약과 별개다
- 남이 정한 응답 형식은 테스트로 고정하지 않는다
- 조건별 응답 표를 남기면 엉뚱한 데 묻는 일이 준다