💡 Key Takeaways
- Resource Naming: The Foundation That Everyone Gets Wrong
- HTTP Methods and Status Codes: Speak the Language Correctly
- Versioning: Future-Proofing Without the Pain
- Pagination, Filtering, and Sorting: Handling Large Datasets
3년 전, 저는 한 스타트업이 API 설계가 기본적으로 매우 잘못되어 그들의 코드베이스의 절반을 다시 작성해야 하는 새로운 기능마다 $2.3 백만의 자금을 소모하는 것을 보았습니다. 저는 사라 첸이며, 지난 12년 동안 세 개의 유니콘 스타트업에서 수석 API 아키텍트로 일하면서 매달 470억 건 이상의 요청을 처리하는 시스템을 설계해왔습니다. 제가 배운 것은 REST API 설계가 엄격한 규칙을 따르는 것이 아니라, 기술적 우수성 또는 기술적 부채로 축적되는 신중한 선택을 만드는 것이라는 것입니다.
💡 주요 내용
- 리소스 이름 지정: 모두가 잘못 아는 기초
- HTTP 메서드 및 상태 코드: 올바른 언어 사용하기
- 버전 관리: 고통 없이 미래 보장하기
- 페이지네이션, 필터링 및 정렬: 대용량 데이터셋 처리하기
REST가 사실상 표준이 된 이후로 경관은 극적으로 변화했습니다. 2026년에는 수천 개의 API 호출을 만드는 엣지 컴퓨팅, AI 기반 애플리케이션, 그리고 지구 어디에서나 100ms 미만의 응답 시간을 기대하는 사용자들과 마주하고 있습니다. "REST 원칙을 따르세요"라는 오래된 조언은 더 이상 통하지 않습니다. 현대의 현실을 고려한 실용적이고 검증된 체크리스트가 필요합니다.
이 기사가 그 체크리스트입니다. 저는 하루에 수백만 달러를 처리하는 회사들을 위해 API를 설계할 때 사용하는 정확한 프레임워크를 공유하고 있습니다. 이들은 이론적인 최선의 방법이 아니라, 확장 가능한 API와 제 무게 아래 무너지는 API를 구별하는 패턴입니다.
리소스 이름 지정: 모두가 잘못 아는 기초
사소해 보이지만 하류 문제를 악화시키는 것부터 시작하겠습니다: 리소스 이름 지정. 저는 경력 동안 200개 이상의 API 설계를 검토했으며, 그 중 60%는 일관성이 없거나 혼란스러운 리소스 이름 지정으로 연쇄 문제를 일으켰다고 추정합니다.
핵심 원칙은 다음과 같습니다: 귀하의 URL은 리소스 계층 구조를 설명하는 문장처럼 읽혀야 합니다. 컬렉션에는 복수 명사를 사용하고, 중첩은 얕게 유지하며(최대 2-3 수준), 철저히 일관성을 유지합니다. 제가 현재 회사에 합류했을 때 그들의 API에는 /getUser, /user-list, /users/fetch와 같은 엔드포인트가 있었고—모두 비슷한 일을 하는 것입니다. 우리는 그 혼란을 해결하는 데 3개월을 보냈습니다.
제가 추천하는 패턴:
- 컬렉션:
/api/v1/users,/api/v1/orders,/api/v1/products - 특정 리소스:
/api/v1/users/12345,/api/v1/orders/ord_abc123 - 하위 리소스:
/api/v1/users/12345/orders,/api/v1/orders/ord_abc123/items - 리소스에 대한 작업:
/api/v1/orders/ord_abc123/cancel(POST),/api/v1/users/12345/verify-email(POST)
무엇이 빠졌는지 주목해 보세요? URL 경로의 동사(비CRUD 작업 제외). HTTP 메서드가 바로 동사입니다. GET /users는 "사용자를 가져옵니다." POST /users는 "사용자를 생성합니다." DELETE /users/123는 "사용자 123을 삭제합니다." 이는 단순한 미관상의 문제가 아니라—귀하의 API를 예측 가능하게 만들고 개발자의 인지 부담을 줄입니다.
비CRUD 작업의 경우, 저는 실용적인 접근 방식을 사용합니다. 예, 순수주의자들은 모든 것이 CRUD에 매핑되어야 한다고 말하겠지만, 실제 세계에서는 "주문 취소", "이메일 확인" 또는 "배송비 계산"과 같은 작업이 있습니다. 저는 이를 작업 엔드포인트에 대한 POST 요청으로 표현합니다: POST /orders/123/cancel. 핵심은 일관성입니다—귀하의 패턴을 문서화하고 신념을 가지고 지켜야 합니다.
마지막으로 중요한 세부 사항이 하나 더 있습니다: 다중 단어 리소스에는 케밥 케이스를 사용하세요 (/shipping-addresses, /shippingAddresses 또는 /shipping_addresses가 아닙니다). URL은 많은 맥락에서 대소문자를 구분하지 않으며, 케밥 케이스가 가장 보편적으로 읽기 쉬운 형식입니다. 이 간단한 규칙으로 피할 수 있었던 대소문자 구분 문제로 인해 생산 실패를 목격한 사례가 있습니다.
HTTP 메서드 및 상태 코드: 올바른 언어 사용하기
리소스 이름 지정이 귀하의 API의 어휘라면, HTTP 메서드 및 상태 코드는 그 문법입니다. 그리고 인간 언어에서처럼 잘못된 문법을 사용하면 이해하기 어려워집니다—사람들이 결국 당신이 의미하는 바를 파악할 수 있더라도.
저는 반복적으로 두 가지 공통의 안티 패턴을 목격합니다. 첫 번째, "작동하기 때문에" 모든 것을 위해 POST를 사용하는 API. 두 번째, 실제 상태가 응답 본문에 묻힌 채 모든 응답에 대해 200 OK를 반환하는 API입니다. 이 두 가지 패턴은 캐싱이 어렵고, 디버깅이 어렵고, 표준 도구와 통합이 어려운 API를 만듭니다.
실제 사용 사례를 기반으로 한 메서드별 분석은 다음과 같습니다:
GET: 리소스를 검색합니다. 아이덴포텐트하고 안전해야 합니다(부작용이 없어야 함). 상태를 수정하는 작업에 대해 GET을 절대 사용하지 마세요—나는 "단지 마지막 접근 타임스탬프를 업데이트하는 것"이라도 상관하지 않습니다. 그것은 미들웨어의 용도입니다. GET 요청은 캐시 가능해야 하며, 상태 변경을 혼합하면 캐싱 가정을 깨뜨리게 됩니다. 제가 작업했던 한 시스템에서는 GET 아이덴포텐시를 제대로 구현하고 HTTP 캐싱 헤더를 추가함으로써 데이터베이스 부하가 73% 감소했습니다.
POST: 새로운 리소스를 생성하거나 작업을 트리거합니다. POST는 비아이덴포텐트 작업을 위한 작업 말입니다. 리소스를 생성할 때는 새 리소스를 가리키는 Location 헤더와 함께 201 Created를 반환합니다. 작업을 트리거할 때는 결과를 설명하는 응답 본문과 함께 200 OK 또는 202 Accepted(비동기 작업의 경우)를 반환합니다.
PUT: 전체 리소스를 교체합니다. 여기서 많은 개발자들이 혼란스러워합니다. PUT은 제공된 표현으로 전체 리소스를 교체해야 합니다. 일부 필드만 포함된 PUT 요청을 보내면, 리소스는 이후에 그 필드만 유지해야 합니다(다른 필드는 기본값 또는 null로 설정되어야 함). 실제로는 PUT을 드물게 사용합니다—주로 클라이언트가 진정으로 완전한 상태를 관리하는 리소스에 대해 사용합니다.
PATCH: 리소스를 부분적으로 업데이트합니다. 이는 대부분의 개발자들이 PUT을 원한다고 생각할 때 실제로 원하는 것입니다. PATCH는 변경하고 싶은 필드만 보낼 수 있게 합니다. 저는 요청 형식으로 JSON Patch(RFC 6902) 또는 JSON Merge Patch(RFC 7396)를 주로 사용합니다. 현재 회사에서는 업데이트 작업의 94%가 PUT이 아닌 PATCH를 사용합니다.
DELETE: 리소스를 제거합니다. 성공적으로 처리되면 204 No Content를 반환하고(응답 본문 필요 없음), 삭제에 대한 정보를 반환하는 경우에는 200 OK를 반환합니다. DELETE를 아이덴포텐트하게 만드세요—여러 번 호출해도 한 번 호출한 것과 같은 효과가 있어야 합니다. 리소스가 이미 삭제된 경우에도 204를 반환합니다.
상태 코드에 대해서는 99%의 시나리오를 포괄하는 이 실용적인 하위 집합을 사용합니다:
- 200 OK: 데이터가 반환되는 성공적인 GET, PUT, PATCH 또는 POST
- 201 Created: 리소스를 생성하는 성공적인 POST
- 202 Accepted: 비동기 처리에 대해 수락된 요청
- 204 No Content: 응답 본문이 없는 성공적인 DELETE 또는 업데이트
- 400 Bad Request: 클라이언트가 잘못된 데이터 전송(상세 오류 메시지 포함)
- 401 Unauthorized: 인증이 필요하거나 실패함
- 403 Forbidden: 인증되었지만 이 리소스에 대한 권한이 없음
- 404 Not Found: 리소스가 존재하지 않음
- 409 Conflict: 요청이 현재 상태와 충돌함(예: 중복 이메일)
- 422 Unprocessable Entity: 검증 실패 (검증 오류에 대해 400보다 이 값을 선호함)
- 429 Too Many Requests: 비율 제한 초과
- 500 Internal Server Error: 우리 쪽에서 문제가 발생함
- 503 Service Unavailable: 일시적인 중단 또는 유지 보수
주요 통찰: 상태 코드는 HTTP 수준의 의미를 나타내고, 응답 본문은 애플리케이션 수준의 세부 정보를 전달합니다. 400 응답은 항상 "잘못된 데이터를 보냈습니다"를 의미해야 하지만, 응답 본문은 어떤 필드가 잘못되었는지 정확히 설명합니다.
버전 관리: 고통 없이 미래 보장하기
저는 세 번의 주요 API 버전 마이그레이션을 경험했고, 각 마이그레이션마다 무엇을 하지 말아야 할지에 대한 고통스러운 교훈을 얻었습니다. 최악의 경우는 18개월이 걸린 v1에서 v2로의 마이그레이션으로, 47개의 클라이언트 애플리케이션의 업데이트를 조정해야 했습니다. 그 과정에서 두 개의 주요 고객을 잃었습니다. 그들의 통합 팀이 변경 사항을 따라잡을 수 없었기 때문입니다.
| 접근 방식 | URL 패턴 | 확장성 | 유지 관리 용이성 |
|---|---|---|---|
| RESTful (추천) | /users/{id}/orders | 우수 - 명확한 계층 구조, 캐시 가능 | 높음 - 예측 가능한 패턴 |
| RPC 스타일 (안티 패턴) | /getUser, /fetchOrders | 불리 - 캐싱 없음, 동사 기반 | 낮음 - 불일치 이름 지정 |