12 KiB
title, source_type, status, confidence, tags, related_projects, last_reviewed
| title | source_type | status | confidence | tags | related_projects | last_reviewed | ||||
|---|---|---|---|---|---|---|---|---|---|---|
| API Error Envelope 설계 (custom vs ProblemDetail vs rpc.Status) | llm-generated | draft | medium |
|
|
2026-05-22 |
API Error Envelope 설계 (custom vs ProblemDetail vs rpc.Status)
Layer:
wiki/concepts/— 일반 개념. 특정 프로젝트의 결정/구현 사실은wiki/projects/에서 다룬다.
Summary
API error envelope은 실패 응답의 구조 계약이다. 표준 후보는 RFC 7807 ProblemDetail, Google rpc.Status, JSON:API errors, GraphQL errors가 있고, 그 외 대형 서비스의 custom envelope (Stripe / GitHub / 토스페이먼츠 등)이 사실상 진영별 컨벤션으로 자리잡았다. 설계 결정의 핵심 축은 (a) 성공/실패 응답의 대칭 여부, (b) code · category · retryable 같은 운영 메타데이터의 1급 필드 승격 여부, (c) 표준 lock-in과 client SDK 호환성의 trade-off다.
Standard (공식 정의)
RFC 7807 ProblemDetail (실패 전용 평면)
IETF 표준. application/problem+json media type. 필드: type (URI), title, status, detail, instance. 모든 필드 optional이고 확장은 top-level에 임의 필드 추가로 한다. RFC 9457로 obsolete되었지만 의미상 호환이며, Spring 6+는 ProblemDetail 클래스로 기본 지원한다. 성공 응답에는 적용되지 않고 실패 전용 평면 shape이다.
Google rpc.Status (gRPC, typed details)
Google AIP-193. code (정수, google.rpc.Code enum), message, details: Any[]. details는 google.protobuf.Any로 packing되며 표준 detail 타입(ErrorInfo, LocalizedMessage, Help, RetryInfo, QuotaFailure, BadRequest)을 포함한다. RetryInfo로 retryable + delay까지 표준화되어 있다. REST/gRPC 양쪽에 동일 모델로 매핑된다.
JSON:API errors (배열)
JSON:API v1.1 spec. top-level에 errors: [] array 필수. 각 error 객체는 id, links, status, code, title, detail, source.pointer (JSON Pointer), meta 중 하나 이상을 가진다. source.pointer로 form 필드 단위 오류를 가리킨다.
GraphQL errors (HTTP 200 + errors field)
GraphQL Specification (October 2021) §7.1.2. 응답은 data와 errors를 모두 가질 수 있고, error 객체는 message (required), locations, path, extensions를 가진다. transport는 보통 HTTP 200이고 4xx/5xx는 transport-level 실패에만 사용한다.
진영별 custom envelope (표준 아님)
- Stripe:
{ error.{ type, code, decline_code, message, param, doc_url, ... } }.typeenum이 사실상 category 역할. - GitHub:
{ message, documentation_url, errors[].{ resource, field, code } }. validation 항목별 풀이가 명시적. - 토스페이먼츠:
{ code, message }. 가장 얇은 envelope. retryable/category는codesemantic으로 추론.
이 세 사례는 어떤 IETF/W3C 표준도 따르지 않으며, 각 회사 SDK가 envelope을 흡수하는 전제로 동작한다.
한계 / 주의점
Custom envelope
- 외부 표준이 존재하지 않으므로 client SDK를 직접 작성하거나 envelope 처리 규칙을 client에게 명시적으로 전달해야 한다.
- 성공/실패 대칭,
retryable1급 같은 운영 친화 결정을 자유롭게 둘 수 있지만 그 비용은 "표준 client 라이브러리 0개"다.
RFC 7807 ProblemDetail
- 실패 전용 평면 shape이므로 "성공도 envelope으로 감싸
success: true/false로 분기하고 싶다"는 요구와 구조적으로 충돌한다. code필드가 표준에 없다 —typeURI가 식별자다. 짧은 머신리더블 코드를 원하면 확장 필드를 강제해야 하고, 결국 "표준 위에 사실상 custom 레이어"가 된다.- Spring 6+는 기본 활성이므로, custom envelope을 채택한다는 것은 의식적으로 표준 인프라를 비활성화하는 선택이다.
application/problem+json을 content-negotiation으로 처리하는 client는 흔하지 않다 — 실질 호환성 이득은 명목 수준에 가깝다.
Google rpc.Status
- 본질적으로 gRPC/protobuf 생태계 결합이다. HTTP REST 전용 서비스에 강제하면
Any디코딩 부담이 client에 mismatch로 전가된다. - 표준 detail 타입 카탈로그를 알아야 효용이 발휘되어 학습 곡선이 높다.
- 가벼운 CRUD API에는 과한 표현력이다.
JSON:API errors
errors[]array와source.pointer는 항목 단위 오류 표현에 강하지만,category/retryable이 1급 필드가 아니라meta로 빠진다.- 부분 채택 시 표준성이 사라진다. 완전 채택 시 success response 리소스 객체 구조, sparse fieldsets 등 spec 전체에 lock-in된다.
GraphQL errors
- HTTP 200 +
errorsfield가 transport 규약이라 CDN / proxy / observability 도구의 4xx/5xx 기반 알람·캐시·라우팅과 부조화한다. - partial success가 1급 개념이라 REST envelope과 패러다임 자체가 다르다 — REST 컨텍스트에서 직접 비교해 "GraphQL이 옳다/그르다"라고 말할 수 없다.
흔한 오해
- "Stripe / GitHub / 토스페이먼츠가 그렇게 하니까 industry standard다" — 표준이 아니라 진영별 컨벤션이다. SDK 없이 직접 다루는 client는 거의 없다는 전제 위에서 동작한다.
- "ProblemDetail은 잘못된 설계다" — 실패 전용 use case (예: 외부 노출 API, RFC 9457 client 생태계 활용)에서는 유효한 선택이다.
Project Application
- wiki/projects/ca-tmpl/api-error-envelope-design — ca-tmpl 의사결정 기록 (
verified— envelope record/handler 코드 구현 +./gradlew check로컬 통과). 실제 구현 범위·검증 수준은 project 문서 참조. - raw/project-notes/ca-skeleton-operational-contract — §3 Structured API Response Contract / §5 Exception Ownership Contract / §6 Operational Error Category / §29 Topic 4 (custom envelope 결정 라인업)
- raw/branch-notes/feature-operational-error-observability-foundation — envelope schema SSOT
- raw/branch-notes/feature-boundary-validation-mapping-contract — validation error →
error.details매핑 - raw/branch-notes/feature-business-rule-validation-contract — business invariant → category 매핑
위 branch-note들은 success / error 대칭, error.code · error.category · error.retryable · error.details 분리, meta.requestId / meta.traceId / meta.correlationId 1급 노출, raw exception / SQL / token / body의 응답 leak 금지를 계약으로 둔다.
Claim-backed Knowledge
이 개념 문서의 핵심 설명은 raw source claim 으로 뒷받침되어야 한다. 공식 문서 claim, 회사 사례 claim, 내 프로젝트 decision 을 분리한다.
| Knowledge Point | Supporting Claims | Confidence | Notes |
|---|---|---|---|
RFC 7807 ProblemDetail은 application/problem+json 기반 실패 전용 평면 shape이며 type URI가 식별자다 (code 필드 없음) |
raw/official-docs/problem-detail-rfc-7807, raw/official-docs/spring-problem-detail | high |
공식 표준 (IETF / Spring) — success/error 대칭·머신리더블 code 요구와 구조적으로 충돌 |
Google rpc.Status는 RetryInfo 등 typed detail로 retryable + delay까지 표준화 (REST/gRPC 공통 모델) |
raw/official-docs/google-api-error-format | high |
공식 vendor 문서(AIP-193) — 단 protobuf/Any 결합이라 HTTP REST 전용에는 과한 표현력 |
JSON:API는 errors[] + source.pointer(JSON Pointer)로 항목 단위 오류를 가리키지만 category/retryable이 1급 필드가 아니다 |
raw/official-docs/json-api-errors-spec | high |
공식 표준 — 부분 채택 시 표준성 상실, 완전 채택 시 spec 전체 lock-in |
| Stripe/GitHub/토스페이먼츠 envelope은 IETF/W3C 표준이 아니라 진영별 컨벤션이며 각 사 SDK가 envelope을 흡수하는 전제로 동작한다 | raw/company-tech-blogs/stripe-error-format, raw/company-tech-blogs/github-api-error-format, raw/company-tech-blogs/toss-payments-error-format | medium |
company-case-study — 공식 best practice로 일반화 금지. SDK 부재 client는 거의 없다는 전제 |
내가 설명할 수 있어야 하는 것
- API error envelope의 후보 표준(RFC 7807 / Google
rpc.Status/ JSON:API / GraphQL errors)의 공식 정의와 각자의 식별자 표현 방식은? - 어떤 문제를 해결하는가 — client가 실패를 어떻게 분기·재시도·관측 가능하게 만드는 구조 계약인가?
- 어떤 상황에서는 custom envelope을 쓰면 안 되는가(표준 client 생태계 활용이 우선인 외부 노출 API 등)?
- 공식 표준이 말하지 않는 부분(success/error 대칭,
retryable·category1급화)은 무엇이고 그 비용("표준 client 라이브러리 0개")은 무엇인가? - Stripe/GitHub/토스 사례를 industry standard처럼 일반화하면 안 되는 지점은?
- 내 프로젝트에서는 어떤 branch decision(custom envelope 채택 + ProblemDetail 거부)으로 연결됐는가?
- 이 개념을 코드/운영에서 검증하려면 무엇을 확인해야 하는가(envelope 직렬화, leak 금지, ProblemDetail 비활성 build-time 강제 등)?
Interview Questions
- 왜 RFC 7807 ProblemDetail을 채택하지 않았는지? 표준을 우회한 비용은 무엇이고, 그 대신 무엇을 얻는지?
retryable을 1급 필드로 둔 이유는? client는retryable: true를 받았을 때 어떻게 다르게 동작해야 하는지?- validation error를
error.details에 담을 때 GitHuberrors[].{resource, field, code}또는 JSON:APIsource.pointer와 비교하면 어떤 형식을 택했고, 왜 그렇게 택했는지? error.code와error.category를 분리한 이유는? client 분기는 어느 쪽으로 하라고 가이드하는지?- 응답에 절대 leak하면 안 되는 항목은? exception class name, stack trace, SQL, token, raw body, upstream raw error body 각각이 왜 금지인지 설명할 수 있는지?
Do Not Overclaim
- "내 envelope이 표준이다" / "ca-tmpl envelope이 IETF 표준 envelope이다"라고 말하면 안 된다. 어떤 표준도 success/error 대칭 +
retryable1급 +category1급을 동시에 강제하지 않는다 — 자체 결정일 뿐이다. - "ProblemDetail은 잘못된 설계다"라고 단정하면 안 된다. 실패 전용 평면이라는 그 자체가 결함이 아니며, 외부 표준 client 호환을 우선하는 use case에서는 합리적이다.
- "Stripe / GitHub / 토스가 다 custom이니까 표준은 의미 없다"라고 말하면 안 된다. 그들은 SDK가 envelope을 흡수하는 전제 위에 동작하며, 표준 미준수가 정당화되는 것이 아니라 trade-off가 다른 것뿐이다.
- Google
rpc.Status의RetryInfo.retry_delay보다retryable: boolean이 우월하다고 주장하면 안 된다 — 후자는 단순하지만 actionable한 delay 정보를 잃는다.
Sources
공식 표준
- raw/official-docs/problem-detail-rfc-7807 — RFC 7807 (Problem Details for HTTP APIs)
- raw/official-docs/spring-problem-detail — Spring Framework
ProblemDetail(RFC 9457 기본 지원) - raw/official-docs/google-api-error-format — Google AIP-193,
google.rpc.Status - raw/official-docs/json-api-errors-spec — JSON:API v1.1 Errors
- raw/official-docs/graphql-errors-spec — GraphQL Specification (October 2021) Errors
진영별 사례 (표준 아님)
- raw/company-tech-blogs/stripe-error-format — Stripe custom envelope
- raw/company-tech-blogs/github-api-error-format — GitHub REST API error format
- raw/company-tech-blogs/toss-payments-error-format — 토스페이먼츠
{code, message}
Canonical (프로젝트 결정 사실)
- raw/project-notes/ca-skeleton-operational-contract §3 / §5 / §6 / §29 Topic 4