개발 slecs

디바이스 API 잘못된 요청을 500 대신 400으로 응답하도록 수정

목차

/device 엔드포인트에서 잘못된 요청이 들어왔을 때 500이 터지던 걸 400으로 바로잡은 작업이다.


왜 이게 문제였나

HTTP 상태 코드는 클라이언트와 서버 간의 신호 체계다. 500은 "서버가 뭔가 잘못됨"이고, 400은 "요청 자체가 잘못됨"이다. 이 둘은 책임 소재부터 다르다.

클라이언트가 platform 값을 누락하거나 푸시 시각 포맷을 엉뚱하게 넘겼을 때, 서버가 그걸 제대로 검증하지 않고 내부 로직까지 흘려보내면 결국 NullPointerException이나 파싱 오류로 500이 난다. 이건 사실 서버 잘못이 아닌데 서버 잘못처럼 보이는 상황이다. 모니터링 알람도 울리고, 팀원들이 "서버 터진 거야?" 하고 슬랙에 달려온다. 실제론 클라이언트 코드가 잘못된 값을 보낸 건데.

더 큰 문제는 클라이언트 개발자 입장이다. 500을 받으면 "내 잘못인가, 서버 잘못인가"를 판단하기 어렵다. 반면 400이 명확한 메시지와 함께 오면 "아, 내가 보낸 요청이 문제구나"를 즉시 알 수 있다. API 개발에서 에러 코드 정확성은 단순한 컨벤션이 아니라 협업 품질의 문제다.


변경 내용: DeviceRequest.java 검증 추가

변경된 파일은 DeviceRequest.java 하나다. DTO 레이어에서 검증을 잡는 전형적인 패턴이다.

// Before — 검증 없음, 런타임에 NPE or 파싱 오류 → 500
public class DeviceRequest {
    private String platform;
    private String pushTime; // "HH:mm" 포맷이어야 하는데 강제 없음
}
// After — Bean Validation 으로 DTO 진입 시점에서 차단
public class DeviceRequest {

    @NotBlank(message = "platform은 필수입니다")
    @Pattern(regexp = "^(ios|android)$", message = "platform은 ios 또는 android만 허용됩니다")
    private String platform;

    @NotBlank(message = "pushTime은 필수입니다")
    @Pattern(regexp = "^([01]\\d|2[0-3]):[0-5]\\d$", message = "pushTime은 HH:mm 형식이어야 합니다")
    private String pushTime;
}

컨트롤러에 @Valid가 붙어 있으면 MethodArgumentNotValidException이 발생하고, 전역 @ExceptionHandler가 이걸 400으로 응답하는 구조다. DTO 한 파일 수정으로 해결되는 핀포인트 수정이었지만, 영향 범위는 꽤 넓다.


상태 코드 분류 정리

상황 올바른 코드 잘못된 코드
필수 필드 누락 400 Bad Request 500 Internal Server Error
잘못된 포맷 (예: pushTime) 400 Bad Request 500 Internal Server Error
존재하지 않는 device 404 Not Found 500 Internal Server Error
처리 중 예상치 못한 오류 500 Internal Server Error

500은 진짜 서버 내부 오류를 위해 아껴둬야 한다. 그래야 알람이 울렸을 때 "이거 진짜 봐야 해"라는 신뢰가 생긴다. 500 노이즈가 많아지면 팀 전체가 알람 피로에 빠진다.


회고

솔직히 이런 류의 버그는 초반에 API 설계할 때 검증 레이어를 꼼꼼히 잡지 않아서 생긴다. 빠르게 기능을 붙이다 보면 DTO에 @Valid 달고 어노테이션 챙기는 게 "나중에 해도 되는 것"으로 밀린다. 그러다 QA 중에, 혹은 클라이언트 팀에서 "500 뜨는데요?" 피드백을 받고 나서야 고치게 된다.

팀장 입장에서 코드 리뷰 시 체크포인트로 추가해둘 만한 항목이다. DTO 들어올 때 검증 어노테이션 있는지, 컨트롤러에 @Valid 달려 있는지, 전역 예외 핸들러가 올바른 코드 돌려주는지. 세 줄짜리 체크리스트인데 500 노이즈를 꽤 줄여준다.

/device처럼 클라이언트가 직접 값을 조립해서 보내는 엔드포인트일수록 입력 검증이 중요하다. 특히 platform 같은 enum 성격의 값이나 pushTime 같은 포맷이 엄격한 값은 DTO 레이어에서 막지 않으면 반드시 어딘가에서 터진다. 그 터지는 지점이 늦을수록 디버깅도 어렵다.

끝.


🛒 이 글과 어울리는 추천 상품

*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.

댓글 0

첫 댓글 달아줘.