Spring 경계와 계약 아키텍처 정의 (role-ta-001)
This commit is contained in:
parent
a6cb360317
commit
ce8732abc8
1 changed files with 132 additions and 0 deletions
132
docs/adr/ADR-002-error-contract.md
Normal file
132
docs/adr/ADR-002-error-contract.md
Normal file
|
|
@ -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<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:**
|
||||
- 오류 응답 클래스 추가 작성 필요
|
||||
- 기존 예외 처리 코드 마이그레이션 필요
|
||||
Loading…
Add table
Add a link
Reference in a new issue