API 필드 제거와 배포 순서: 필드를 지우면 읽는 쪽이 터진다
API에서 필드를 제거하면 그 값을 읽던 쪽이 깨진다. 부분 수정 DTO의 null 덮어쓰기, 항상 200을 주는 API가 버그를 숨기는 것, 프론트/백엔드 배포 순서를 정하는 기준.
목차
주문 화면을 단순하게 만드는 작업이었다. 주문할 때 받던 정보 일부(받는 사람 이름, 연락처, 상세주소)를 빼고 나중 단계인 배송지 확인으로 옮기는 일.
화면에서 입력칸 지우고 API에서 필드 빼면 끝날 줄 알았다.
깨진 곳이 셋이었는데, 셋 다 지운 자리가 아니었다
전부 그 값을 읽던 자리였다. 그리고 셋 다 조용히 깨졌다.
1. 대조할 기준이 없으면 전부 실패로 판정된다
배송 대행사에서 받은 주소와 주문할 때 받은 주소를 비교해서, 같으면 통과시키는 함수가 있었다.
// 대행사 값 없음 → 검사 안 함
// 대행사 값 있음 + 저장값 없음 → 불일치 ← 여기
// 둘 다 있음 → 정리해서 비교
private boolean isMismatch(String externalValue, String storedValue) {
if (!hasText(externalValue)) return false;
if (!hasText(storedValue)) return true; // 대조 불가 = 실패
return !normalize(externalValue).equals(normalize(storedValue));
}
"대조할 기준이 없으면 실패"는 안전한 설계다. 확인 안 되는 걸 통과시키는 것보단 낫다.
문제는 주문에서 그 기준값을 안 받게 되면 신규 주문 전부가 이 분기를 탄다는 거다. 통과율 0%.
게다가 화면에 안 보였다. 고객에게는 보류 상태를 "확인 중"으로 보여주고 있었으니까. 그래서 증상이 "영원히 확인 중 + 배송 무기한 보류"다. 에러 로그도 안 남는다. 정상 동작으로 기록되니까.
2. 항상 200을 주는 API는 진짜 실패도 숨긴다
주문 조회가 이메일과 연락처로 찾고 있었다. 연락처를 안 받게 됐으니 조회가 영구 실패한다.
근데 이 API는 주문 존재 여부를 숨기려고 항상 성공을 반환한다. 없는 이메일을 넣어도 "메일을 보냈습니다"가 뜬다. 남의 주문이 있는지 떠보는 걸 막는 장치다.
방어는 제대로 동작했다. 실제 버그까지 같이 숨겼을 뿐이다.
클라이언트는 성공을 받고, 서버는 200을 남기고, 메일만 영영 안 온다.
3. 폼에 입력칸이 없는 필드가 요청 객체에 남아 있으면 지워진다
관리자 주문 수정 요청 객체에 연락처 필드가 있었다. 근데 수정 폼에는 연락처 입력칸이 없다 — 다른 화면으로 옮겼으니까.
public record OrderUpdateRequest(
String memo,
String phone, // 폼에 입력칸이 없다 → 항상 null 로 온다
...
) {}
관리자가 메모만 고치고 저장하면 연락처가 지워진다.
부분 수정에서 "필드가 안 왔다"와 "빈 값으로 바꿔달라"가 구분되지 않는 고전적인 문제다. 이번엔 계기가 "폼에서 입력칸을 지웠다"라서 요청 객체를 열어볼 이유가 없었다.
어떻게 고쳤나
1. 대조 기준의 출처를 옮겼다. 판정 로직은 한 줄도 안 고쳤다. 그 함수가 읽는 값을 만드는 지점만 새 입력 단계로 옮겼더니 그대로 동작했다. 읽는 쪽이 아니라 쓰는 쪽을 고치는 게 맞았다.
2. 조회 조건에서 연락처를 뺐다. 여기서 배운 건 고친 방법보다 못 찾을 뻔한 이유 쪽이다. 이 버그는 테스트로도 안 잡힌다 — API는 200을 반환하니 계약상 정상이니까. 코드를 눈으로 읽어서 찾았다.
3. 필드를 요청 객체에서 아예 지웠다. null을 무시하도록 설정을 바꾸는 방법도 있는데, 그러면 "일부러 비우기"가 영영 불가능해진다. 이 필드는 다른 경로에서만 수정하는 게 맞아서 아예 못 건드리게 지웠다.
배포 순서 — 필드를 지울 땐 백엔드가 먼저
같은 문제의 연장이다. 프론트와 백엔드는 동시에 배포되지 않는다. 사이에 몇 분이 뜬다.
| 순서 | 백엔드 | 프론트 | 그 사이 |
|---|---|---|---|
| 백엔드 먼저 | 필드 안 받음 | 아직 보냄 | 정상 — 여분 필드는 무시 |
| 프론트 먼저 | 필수라고 요구 | 안 보냄 | 주문 전부 400 |
여분 필드가 무시되는 건 JSON 파서 설정 덕이다. "모르는 필드가 와도 에러 내지 말고 무시해라"는 설정이 있어야 "받아주되 무시하는" 구간이 생기고, 그 구간이 있어야 두 배포 사이가 안전하다.
남길 것
- 필드를 지우면 쓰는 쪽이 아니라 읽는 쪽이 터진다. 지우기 전에 그 값을 읽는 코드를 전부 찾는다. 검색할 건 필드명이 아니라 그 값이 흘러가 담기는 변수와 컬럼이다.
- "없으면 실패" 판정은 위쪽 데이터가 사라지면 전 건 실패가 된다. 옳은 설계지만, 그 기준값의 출처를 아는 사람만 안전하게 고칠 수 있다.
- 가려놓은 화면은 버그도 같이 가린다. 보류를 "확인 중"으로 보여주거나 항상 200을 주는 코드는 실패를 정상으로 기록한다. 테스트로 안 잡히니 눈으로 읽어야 한다.
- 부분 수정 요청 객체는 폼과 짝이다. 폼에서 입력칸을 지웠으면 요청 객체에서도 지운다.
- 받아주는 쪽을 먼저 넓히고 배포한다. "받아주되 무시" 상태를 먼저 올리면 두 배포 사이가 안전하다.
더 봐야 할 것
- "기준값이 없음"과 "값이 다름"을 다른 상태로 구분하면 이번 같은 사고가 화면에 드러날까
- 가려놓은 응답에 내부용 원인 코드를 따로 남기는 게 일반적인 관행인지
- 부분 수정에서 "안 보냄"과 "비우기"를 구분하는 방법으로 실무에서 뭘 쓰는지