서버 로그에는 오류가 있는데 호출자는 성공으로 봤습니다
분석 처리와 확인 사이에 동일한 Well 좌표가 충돌하는 조건이 발생하면 서버에서는 예외가 기록됐습니다. 그런데 API 응답은 HTTP 200이었고 본문은 비어 있었습니다.
호출 시스템 관점에서 이 응답은 해석할 정보가 없습니다.
- HTTP 상태는 성공이라고 말합니다.
- 응답 본문에는 성공 결과도 실패 사유도 없습니다.
- 재시도해야 하는지, 다음 단계로 진행해야 하는지 판단할 수 없습니다.
내부 로그에 오류가 정확히 남아 있어도 API 응답 규격이 실패를 표현하지 못하면, 호출자는 성공 처리를 계속하거나 별도의 시간 초과와 데이터 불일치로 문제를 뒤늦게 발견합니다.
실패는 HTTP 상태·본문·로그에 남아야 합니다
REST API의 실패는 한 가지 값으로 끝나지 않습니다.
| 신호 | 역할 | 당시 문제 |
|---|---|---|
| HTTP 상태 코드 | 요청 처리의 성공·실패를 전송 계층에서 구분 | 실패인데 200 반환 |
| 오류 본문 | 호출자가 기록·분기할 기계 판독 정보 제공 | 빈 본문 |
| 서버 트랜잭션·상태 | 데이터가 실제로 반영됐는지 보장 | 응답만 보고 판단하기 어려움 |
errorMessage만 본문에 추가하면서 상태를 200으로 유지하면 HTTP 클라이언트, 모니터링과 재시도 정책은 여전히 성공으로 분류할 수 있습니다. 반대로 500만 반환하고 본문을 비우면 호출자는 실패를 알 수는 있지만 어떤 업무 조건이 문제였는지 구분하기 어렵습니다.
호출자가 실패를 놓치지 않게 하려면 HTTP 상태와 본문을 함께 바로잡아야 했습니다.
빈 성공 응답이 만들어진 경로를 좁혔습니다
Well 중복을 검출하는 쿼리부터 예외가 실제 HTTP 응답으로 바뀌기까지의 과정을 따라갔습니다.
아래 순서와 코드는 실제 이름과 값을 드러내지 않도록 핵심 동작만 단순화했습니다.
Well 좌표 검증
→ Service 예외 발생
→ 공통 예외 처리
→ HTTP Status와 오류 본문 생성
→ 호출 시스템 응답 수신서버의 공통 예외 처리 로그에는 오류가 남았지만 최종 응답에서는 상태가 성공으로 유지되고 본문이 비어 있었습니다. 즉, 원인을 “중복 데이터가 생겼다”에서 끝내지 않고 서버에서 감지한 오류가 HTTP 응답에 반영되지 않는 문제로 좁혔습니다.
수정할 때는 좌표 중복 판정, 예외 전달, HTTP 응답 변환을 각각 확인했습니다. 세 단계 중 하나라도 실패를 숨기면 호출자는 여전히 성공으로 오해할 수 있기 때문입니다.
호출자가 분기할 수 있는 오류 응답
기존 연계 시스템의 오류 처리 규칙에서는 이 조건을 서버가 요청을 완료하지 못한 실패로 다뤘습니다. 호출 시스템이 일관되게 판정할 수 있도록 다음 형태로 통합했습니다.
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"errorCode": "GENERALIZED_ERROR_CODE",
"errorMessage": "요청을 처리할 수 없습니다."
}실제 오류 코드와 메시지는 내부 업무 정보를 드러내지 않도록 예시에서 일반화했습니다. 중요한 규칙은 다음 세 가지입니다.
- 성공한 요청만 2xx로 반환
- 처리 실패는 기존 연계 규칙에 맞는 비2xx 상태로 반환
- 호출자가 저장·분기할 수 있는
errorCode와 사람이 확인할errorMessage를 함께 제공
응답을 만드는 코드도 각 서비스의 catch 블록에 흩어놓지 않고 공통 예외 처리기로 모았습니다. 서비스는 실패를 숨기지 않고 전달하고, 예외 처리기는 이를 HTTP 오류 응답으로 바꿉니다.
HTTP 상태와 오류 본문을 함께 바꿨습니다
500 응답은 실패 전달을 바로잡지만 데이터 정합성을 자동으로 보장하지 않습니다. 예외가 발생하기 전에 일부 데이터가 저장됐다면 HTTP 응답과 별개로 트랜잭션 롤백을 확인해야 합니다.
좌표 충돌에 500을 쓰는 것이 모든 API의 정답은 아닙니다. 업무 충돌로 정의한다면 409나 422를 선택할 수도 있습니다. 여기서는 기존 연계 규칙에 따라 500을 사용했습니다. 중요한 건 숫자 하나보다 실패를 2xx로 숨기지 않고, 호출자가 해석할 오류 본문을 함께 제공하는 일이었습니다.
따라서 검증에서는 다음을 함께 봤습니다.
- Well 좌표 충돌이 실제 실패 조건으로 판정되는지
- 응답 상태가 200이 아니라 500인지
- 본문에 표준
errorCode와errorMessage가 존재하는지 - 정상 요청의 기존 성공 응답이 바뀌지 않는지
- 실패 경로에서 호출자가 빈 성공으로 오인할 응답이 남지 않는지
좌표 충돌과 정상 요청을 나눠 응답 상태·본문·기존 성공 결과를 확인했고, 호출 시스템에서도 실패로 분기되는지 대조했습니다.
호출자 관점에서 검증했습니다
서버 단위 테스트에서 예외 발생만 확인하면 “예외를 잘 던졌다”는 사실까지만 알 수 있습니다. 문제는 그 이후 만들어진 HTTP 응답에 있었으므로, 호출자가 실제로 받은 상태 코드와 본문까지 검증해야 했습니다.
검증 전
서버: ERROR 로그
호출자: 200 + 빈 본문 → 성공인지 실패인지 판정 불가
검증 후
서버: 오류 기록
호출자: 500 + errorCode + errorMessage → 실패 분기 가능API 제공자 로그와 호출자 응답을 같은 테스트 케이스에서 비교해야 중간 계층이 상태나 본문을 다시 바꾸는 문제를 놓치지 않습니다.
호출자가 오류 응답으로 결정하는 것
연계 시스템은 오류 응답을 보고 재시도하거나 사용자에게 실패를 알립니다. 오류 응답은 다음 행동을 결정하는 입력입니다.
- 성공이면 다음 분석·저장 단계로 진행
- 실패면 해당 요청을 보류하고 원인을 기록
- 재시도 가능한 조건이면 정해진 정책에 따라 재요청
- 사람이 확인해야 하면 오류 코드와 메시지를 운영 화면이나 로그에 남김
200과 빈 본문은 이 모든 분기를 막습니다. 서버 안에서 예외가 발생했다는 사실보다 더 중요한 것은 실패가 호출자에게 손실 없이 전달되는지 여부입니다.
이번 변경 범위는 Well 좌표 충돌이 200 + 빈 본문으로 끝나던 경로를 500 + 표준 오류 본문으로 바꾸는 것이었습니다. 좌표 판정부터 예외 전달, 공통 응답 처리와 호출자 분기까지 이어서 맞춰야 실패가 서버 밖으로 제대로 전달됩니다.