HTTP 상태 코드만으로 충분한데, success 필드?
서론
RESTful API 기반 사이드 프로젝트를 오랜만에 진행하며, 문득 들었던 질문점이 있었다.
왜 HTTP STATUS CODE가 있는데 버젓이 표준 API들에서는 success 여부를 리턴하는 경우가 많을까?
// 이런 응답을 자주 본다
HTTP/1.1 200 OK
{
"success": false,
"message": "잔액이 부족합니다"
}
HTTP는 이미 200, 400, 500 같은 상태 코드로 성공/실패를 표현할 수 있는데, 왜 응답 본문에 또 success: true/false를 넣을까?
그래서 이것이 단순한 관례인지, success가 기존의 REST API가 표현할 수 있는 HTTP 상태 코드와 다른 개념에서 활용이 필요한 것인지 궁금해졌다.
본론
HTTP 상태 코드 vs success 필드의 차이
먼저 두 개념이 담당하는 역할을 명확히 구분해야 한다.
HTTP 상태 코드: HTTP 통신 계층의 결과
- 요청이 서버에 도달했는가?
- 서버가 응답을 정상적으로 반환했는가?
- 네트워크/프로토콜 수준의 성공/실패
success 필드: 비즈니스 로직 계층의 결과
- 요청한 작업이 실제로 완료되었는가?
- 비즈니스 규칙을 만족했는가?
- 애플리케이션 수준의 성공/실패
RESTful 원칙: HTTP 상태 코드만 사용
사실 RESTful API 설계 원칙에서는 HTTP 상태 코드만으로 충분하다.
// RESTful 방식
HTTP/1.1 200 OK
{
"transaction_id": "TX123",
"amount": 10000
}
HTTP/1.1 400 Bad Request
{
"error_code": "INSUFFICIENT_BALANCE",
"message": "잔액이 부족합니다"
}
HTTP/1.1 403 Forbidden
{
"error_code": "PERMISSION_DENIED",
"message": "권한이 없습니다"
}
HTTP 프로토콜이 제공하는 풍부한 상태 코드(200, 201, 400, 401, 403, 404, 500 등)를 적절히 활용하면 별도의 success 필드 없이도 결과를 명확하게 표현할 수 있다.
그럼 왜 실무에서는 success 필드를 사용할까?
1. 프론트엔드 개발 편의성
// HTTP 상태 코드 방식
try {
const response = await fetch('/api/transfer');
if (!response.ok) {
const error = await response.json();
handleError(error);
return;
}
const data = await response.json();
handleSuccess(data);
} catch (e) {
handleNetworkError(e);
}
// success 필드 방식 (항상 200 반환)
const response = await fetch('/api/transfer');
const data = await response.json();
if (data.success) {
handleSuccess(data);
} else {
handleError(data);
}
모든 응답을 200으로 받으면 예외 처리 없이 일관된 방식으로 파싱할 수 있다. 코드가 간결해진다.
2. 일괄 처리(Batch) API의 필요성
POST /api/batch/users
HTTP/1.1 200 OK
{
"results": [
{"id": 1, "success": true, "email": "user1@example.com"},
{"id": 2, "success": false, "error": "DUPLICATE_EMAIL"},
{"id": 3, "success": true, "email": "user3@example.com"}
]
}
여러 작업을 한 번에 처리하는 배치 API에서는 일부는 성공하고 일부는 실패할 수 있다. 이 경우 HTTP 상태 코드 하나로는 표현이 불가능하다. 각 항목별로 success 필드가 필요하다.
3. 레거시 및 기업 표준
- SOAP, XML-RPC 시절부터 내려온 관습
- 많은 기업이 이미 이 패턴으로 API 표준을 수립
- 기존 시스템과의 호환성 유지
- API Gateway, 모니터링 시스템과의 통합
4. 비즈니스 로직과 HTTP 통신은 다르다
// 거래 검증 API
HTTP/1.1 200 OK
{
"success": true,
"validation_result": "PASSED",
"risk_score": 3.2,
"checks": [
{"name": "limit_check", "success": true},
{"name": "risk_check", "success": true}
]
}
HTTP는 통신이 성공했음을 나타내고, success는 로직이 성공했음을 나타낸다.
혼합 방식: 실용적인 절충안
많은 현대 API들이 사용하는 방식:
// 비즈니스 로직 실패 → 200 + success: false
HTTP/1.1 200 OK
{
"success": false,
"error_code": "INSUFFICIENT_BALANCE",
"message": "잔액이 부족합니다"
}
// 시스템/프로토콜 에러 → 4xx/5xx
HTTP/1.1 401 Unauthorized
{
"error_code": "INVALID_TOKEN",
"message": "인증 토큰이 유효하지 않습니다"
}
HTTP/1.1 500 Internal Server Error
{
"error_code": "DATABASE_ERROR",
"message": "데이터베이스 연결 실패"
}
- 비즈니스 에러: 200 OK +
success: false(예측 가능한 실패) - 시스템 에러: 적절한 4xx/5xx 코드 사용 (예상치 못한 실패)
결론
이론적으로는 HTTP 상태 코드만으로 충분하다. RESTful API 설계 원칙에서도 이를 권장한다.
하지만 실무에서 success 필드를 사용하는 것은:
- 단순한 관례가 아니라
- 프론트엔드 개발 편의성, 배치 처리, 레거시 호환성 등 실용적인 이유가 있다
특히 비즈니스 로직에 의해 성공 / 실패 여부가 시스템 에러와 다르게 나뉘는 경우에는,
success 필드를 운용하는 것이 안전하고 명확한 선택일 수 있다.
결국 "어떤 방식이 더 나은가"는 정답이 없다.
- 새로운 API를 설계한다면? → RESTful 원칙에 따라 HTTP 상태 코드 활용
- 기존 시스템을 유지보수한다면? → 팀의 컨벤션과 기존 구조를 따르기
- 배치 처리나 복잡한 비즈니스 로직이 있다면? → success 필드 고려
중요한 것은 일관성이다. 어떤 방식을 선택하든 팀 전체가 동일한 패턴을 유지하는 것이 가장 중요하다.
'DEV > Web' 카테고리의 다른 글
| Tomcat과 Catalina (0) | 2025.11.12 |
|---|---|
| [HTTP] 상태 코드(Status Code) 설명 (11) | 2023.12.06 |