runtime-role-smoke-20260714.../docs/adr/ADR-002-error-contract.md

132 lines
3.7 KiB
Markdown

# 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<FieldError> details
) {
public record FieldError(String field, Object rejectedValue, String message) {}
}
// GlobalExceptionHandler.java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex, HttpServletRequest request) {
ErrorResponse response = ErrorResponse.of(ex, request.getRequestURI());
return ResponseEntity.status(ex.getHttpStatus()).body(response);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> 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:**
- 오류 응답 클래스 추가 작성 필요
- 기존 예외 처리 코드 마이그레이션 필요