# ADR-002: 오류 계약(Error Contract) 정의 ## Context REST API에서 일관된 오류 응답 형식을 제공하여: - 클라이언트가 오류를 명확히 이해 가능 - API 버전 간 호환성 유지 - 디버깅 및 모니터링 용이성 확보 를 목적으로 한다. ## Decision ### 1. 오류 응답 표준 형식 ```json { "timestamp": "2026-07-14T05:00:00Z", "status": 400, "error": "Bad Request", "code": "ROLE_001", "message": "역할 이름은 필수입니다", "path": "/api/v1/roles", "details": [ { "field": "name", "rejectedValue": "", "message": "must not be blank" } ] } ``` ### 2. 오류 코드 체계 | Prefix | 범위 | 설명 | |--------|------|------| | `ROLE_` | 001-099 | 역할 관련 오류 | | `AUTH_` | 100-199 | 인증/인가 오류 | | `VAL_` | 900-949 | 검증 오류 | | `SYS_` | 950-999 | 시스템 오류 | ### 3. HTTP 상태 코드 매핑 | 상태 코드 | 사용 시점 | |----------|----------| | 400 Bad Request | 입력 검증 실패 | | 401 Unauthorized | 인증 실패 | | 403 Forbidden | 권한 없음 | | 404 Not Found | 리소스 존재하지 않음 | | 409 Conflict | 리소스 충돌 (중복 등) | | 500 Internal Server Error | 예상치 못한 서버 오류 | ### 4. 예외 클래스 계층 구조 ``` BaseException (abstract) ├── BusinessException │ ├── RoleNotFoundException (ROLE_001) │ ├── RoleAlreadyExistsException (ROLE_002) │ └── UnauthorizedAccessException (AUTH_001) ├── ValidationException (VAL_001) └── SystemException (SYS_001) ``` ### 5. 구현 클래스 ```java // BaseException.java public abstract class BaseException extends RuntimeException { private final String errorCode; private final HttpStatus httpStatus; protected BaseException(String errorCode, HttpStatus httpStatus, String message) { super(message); this.errorCode = errorCode; this.httpStatus = httpStatus; } } // ErrorResponse.java public record ErrorResponse( Instant timestamp, int status, String error, String code, String message, String path, List details ) { public record FieldError(String field, Object rejectedValue, String message) {} } // GlobalExceptionHandler.java @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public ResponseEntity handleBusinessException(BusinessException ex, HttpServletRequest request) { ErrorResponse response = ErrorResponse.of(ex, request.getRequestURI()); return ResponseEntity.status(ex.getHttpStatus()).body(response); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity handleValidationException(MethodArgumentNotValidException ex, HttpServletRequest request) { ErrorResponse response = ErrorResponse.ofValidation(ex, request.getRequestURI()); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } } ``` ## Alternatives ### 대안 1: RFC 7807 Problem Details - `application/problem+json` Content-Type 사용 - 단점: 클라이언트 라이브러리 지원 제한적 - 채택하지 않음 (일반 JSON 응답 채택) ### 대안 2: 단순 오류 메시지만 반환 - 단점: 오류 코드 부재로 클라이언트 처리 어려움 - 채택하지 않음 ## Consequences **Positive:** - 일관된 API 응답으로 클라이언트 개발 편의성 향상 - 오류 코드 기반 로컬라이제이션 가능 - 모니터링 시스템 연동 용이 **Negative:** - 오류 응답 클래스 추가 작성 필요 - 기존 예외 처리 코드 마이그레이션 필요