🤔 RFC 9457? 그거 먹는 건가요?🚀 마법의 주문, ProblemDetail🛠️ 실무 치트키: 유효성 검사 에러도 한 방에!✨ 이제 우리 그만 DTO 만들어요Reference
새로운 프로젝트에 투입되거나, 옆 팀과 API를 연동할 때 겪는 익숙한 시나리오가 있죠.
"에러 응답은 errorCode랑 message 필드로 내려가요." (옆 팀 문서를 보니...) "여기는 code랑 error_message로 오네... 아, 저쪽은 status랑 detail...?"
팀마다, 프로젝트마다 다른 에러 응답 형식. 솔직히 이거 맞추는 거, 은근히 스트레스받고 시간 낭비잖아요. 우리만 쓰는 커스텀
ErrorResponse DTO를 또 만들고, 문서화하고...그런데 이 지긋지긋한 '우리 팀 에러 스펙' 놀이를 끝내줄 국제 표준 구원투수가 있다면 어떠세요? 바로 RFC 9457입니다. 그리고 스프링 부트 3 이상을 쓴다면, 우린 이미 그 구원투수를 쓸 준비가 되어있습니다.
🤔 RFC 9457? 그거 먹는 건가요?
어렵게 들리지만, 별거 아닙니다. 전 세계 어디를 가도 똑같은 모양의 충전기를 꽂을 수 있다면 얼마나 편할까요? RFC 9457이 바로 API 에러계의 국제 표준 충전 어댑터 같은 겁니다.
"HTTP API에서 에러가 나면, 이 JSON 형식에 맞춰서 정보를 주자!"고 전 세계 개발자들이 약속한 거예요. 덕분에 클라이언트는 어떤 서버든 일관된 방식으로 에러를 해석할 수 있게 되죠.
🚀 마법의 주문, ProblemDetail
자, 그럼 우리를 얼마나 편하게 해주는지 코드로 직접 보시죠. 우리에게 익숙한, 하지만 솔직히 좀 번거로운 그 코드부터...
👎 The Old Way (우리가 하던 대로)
// 1. 매번 만들던 ErrorResponse DTO public class ErrorResponse { private String code; private String message; // ... } // 2. ResponseEntity로 감싸고, 상태 코드 지정하고... @RestControllerAdvice public class OldExceptionHandler { @ExceptionHandler(UserNotFoundException.class) public ResponseEntity<ErrorResponse> handle() { ErrorResponse response = new ErrorResponse("USER_NOT_FOUND", "사용자를 찾을 수 없어요."); return new ResponseEntity<>(response, HttpStatus.NOT_FOUND); } }
이제 스프링 3+의 마법,
ProblemDetail을 써볼까요?👍 The New Way (요즘 개발자처럼)
import org.springframework.http.ProblemDetail; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(UserNotFoundException.class) public ProblemDetail handleUserNotFoundException(UserNotFoundException ex) { ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage()); problemDetail.setTitle("사용자 조회 실패"); // 에러 문서 링크도 달아주는 센스! problemDetail.setType(URI.create("/docs/errors/user-not-found")); return problemDetail; } }
어때요?
ErrorResponse DTO 파일 하나가 그냥 사라졌죠? ResponseEntity로 감싸던 코드도 없어졌고요. 마음이 편안해지지 않나요? 😊🛠️ 실무 치트키: 유효성 검사 에러도 한 방에!
좋아요, 간단한 건 알겠는데... 실무에서 제일 골치 아픈 유효성 검사(
@Valid) 에러는 어떻게 하냐고요? ProblemDetail의 진짜 힘은 여기서 나옵니다.import org.springframework.web.bind.MethodArgumentNotValidException; import java.util.HashMap; import java.util.Map; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ProblemDetail handleValidationExceptions(MethodArgumentNotValidException ex) { ProblemDetail problemDetail = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST); problemDetail.setTitle("입력값 유효성 검사 실패"); problemDetail.setDetail("어떤 값이 왜 잘못됐는지 아래를 확인해주세요."); // '어떤 필드가', '왜' 잘못됐는지 쏙쏙 뽑아내기 Map<String, String> errors = new HashMap<>(); ex.getBindingResult().getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage()) ); // 'invalid-params' 라는 이름으로 커스텀 필드 추가! 이게 치트키입니다. problemDetail.setProperty("invalid-params", errors); return problemDetail; } }
이걸 받은 프론트엔드 개발자는 쌍수를 들고 환영할 겁니다.
✅ 프론트엔드 개발자가 받게 될 응답
{ "title": "입력값 유효성 검사 실패", "status": 400, "detail": "어떤 값이 왜 잘못됐는지 아래를 확인해주세요.", "instance": "/api/users", "invalid-params": { "email": "올바른 이메일 형식이 아니에요.", "age": "나이는 18세 이상이어야 해요." } }
더 이상 "서버 에러났어요! 뭐가 문제예요?" 라는 슬랙 메시지를 받을 일이 줄어드는 거죠. 클라이언트가 스스로 문제를 파악하고 해결할 수 있는, 아주 친절한 API가 된 겁니다.
✨ 이제 우리 그만 DTO 만들어요
이제 우리 그만
ErrorResponseDtoV2, ApiExceptionResponse 같은 클래스 만드는 데 시간 쓰지 말자고요. 대신 국제 표준을 받아들이고, 더 중요한 비즈니스 로직에 집중합시다.ProblemDetail은 단순한 기능이 아니라, 동료 개발자를 배려하고, 더 성숙한 시스템을 만드는 철학입니다.오늘 바로 여러분의 프로젝트에 적용해보세요. 해피 코딩! 🚀