From ce8732abc8783588aecfbe2b6a3a2bcd41f39d87 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Tue, 14 Jul 2026 05:02:54 +0000 Subject: [PATCH] =?UTF-8?q?Spring=20=EA=B2=BD=EA=B3=84=EC=99=80=20?= =?UTF-8?q?=EA=B3=84=EC=95=BD=20=EC=95=84=ED=82=A4=ED=85=8D=EC=B2=98=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98=20(role-ta-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/adr/ADR-002-error-contract.md | 132 +++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 docs/adr/ADR-002-error-contract.md diff --git a/docs/adr/ADR-002-error-contract.md b/docs/adr/ADR-002-error-contract.md new file mode 100644 index 0000000..7f5a43e --- /dev/null +++ b/docs/adr/ADR-002-error-contract.md @@ -0,0 +1,132 @@ +# 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:** +- 오류 응답 클래스 추가 작성 필요 +- 기존 예외 처리 코드 마이그레이션 필요