TL;DR
REST API 리소스 이름은 복수형으로 사용해야 합니다. /pet/{id} 대신 /pets/{id}를 사용하세요. 복수형 이름은 컬렉션을 일관되게 나타내고, HTTP 의미론과 일치하며, 개발자가 리소스에 대해 생각하는 방식과도 부합합니다. 모던 PetstoreAPI는 업계 모범 사례에 따라 API 설계 전반에 걸쳐 복수형 이름을 사용합니다.
서론
REST API를 설계하고 있습니다. ID로 사용자를 가져오는 엔드포인트가 필요합니다. /user/123을 사용해야 할까요, 아니면 /users/123을 사용해야 할까요? 이 질문은 수많은 논쟁, Stack Overflow 스레드, 팀 내 논쟁을 불러일으켰습니다.
답은 명확합니다: 복수형을 사용하세요. 하지만 규칙을 외우는 것보다 그 이유를 이해하는 것이 더 중요합니다. 그 이유는 REST가 작동하는 방식, 컬렉션의 동작 방식, 그리고 개발자가 리소스에 대해 생각하는 방식과 연결됩니다.
오래된 Swagger Petstore는 /pets/{id} 대신 /pet/{id}를 사용하여 이 점을 잘못 다뤘습니다. 이러한 불일치는 수많은 개발자들에게 잘못된 패턴을 가르쳤습니다. 모던 PetstoreAPI는 모든 엔드포인트에 걸쳐 복수형 이름을 일관되게 사용하여 이 문제를 해결합니다.
이 가이드에서는 왜 복수형 이름이 올바른 선택인지, REST 원칙과 어떻게 일치하는지, 그리고 모던 PetstoreAPI를 참조하여 올바르게 구현하는 방법을 배웁니다.
복수형 대 단수형 논쟁
이 논쟁은 두 접근 방식 모두 처음에는 논리적으로 보이기 때문에 존재합니다.
단수형 주장
“/user/123을 요청할 때, 여러 사용자가 아닌 한 명의 사용자를 얻습니다. 단수형이 말이 됩니다.”
이러한 추론은 응답에 초점을 맞춥니다. 단일 리소스를 얻으므로 URL도 단수형이어야 한다는 것입니다.
복수형 주장
“URL은 컬렉션을 나타냅니다. /users는 모든 사용자들의 컬렉션입니다. /users/123은 그 컬렉션의 123번째 항목입니다.”
이러한 추론은 리소스 구조에 초점을 맞춥니다. URL은 컬렉션을 나타내며, 해당 컬렉션 내의 항목에 접근하는 것입니다.
이것이 중요한 이유
당신의 선택은 다음 사항에 영향을 미칩니다:
- API 일관성 - 혼합된 명명법은 개발자들을 혼란스럽게 합니다.
- 정신 모델 - 개발자가 API 구조를 이해하는 방식
- 코드 생성 - 도구는 리소스 이름을 기반으로 클라이언트 코드를 생성합니다.
- 문서 명확성 - 문서는 명명 논리를 설명해야 합니다.
복수형 이름이 승리하는 이유
복수형 리소스 이름은 REST 원칙 및 HTTP 의미론과 일치합니다. 이유는 다음과 같습니다.
1. 컬렉션은 복수형입니다.
REST에서 리소스는 컬렉션입니다. /users에 접근할 때, 사용자 컬렉션에 접근하는 것입니다. /users/123에 접근할 때, 사용자 컬렉션의 123번째 항목에 접근하는 것입니다.
GET /users ← 사용자 컬렉션
GET /users/123 ← 사용자 컬렉션의 123번째 항목
POST /users ← 사용자 컬렉션에 추가
DELETE /users/123 ← 사용자 컬렉션에서 123번째 항목 제거
이 정신 모델은 일관적입니다. 모든 항목에 접근하든 하나의 항목에 접근하든 컬렉션은 항상 /users입니다.
단수형 이름을 사용하면 정신 모델이 깨집니다:
GET /user ← 어떤 사용자?
GET /user/123 ← 이건 말이 됩니다.
POST /user ← 어디에 추가?
2. HTTP 메서드는 컬렉션에 대해 작동합니다.
HTTP 메서드는 컬렉션에 대한 작업을 설명합니다:
GET /users- 컬렉션 검색POST /users- 컬렉션에 추가GET /users/123- 컬렉션에서 123번째 항목 검색PUT /users/123- 컬렉션에서 123번째 항목 교체DELETE /users/123- 컬렉션에서 123번째 항목 제거
컬렉션이 리소스입니다. 개별 항목은 해당 컬렉션의 멤버입니다.
3. 엔드포인트 전반의 일관성
복수형 이름은 일관성을 만듭니다:
GET /pets ← 컬렉션
GET /pets/123 ← 컬렉션 내 항목
GET /orders ← 컬렉션
GET /orders/456 ← 컬렉션 내 항목
단수형 이름은 단수형과 복수형 사이를 오가게 합니다:
GET /pet ← 말이 안 됩니다.
GET /pet/123 ← 말이 됩니다.
GET /pets ← 잠시만, 이제 복수형인가요?
4. 산업 표준
주요 API는 복수형 이름을 사용합니다:
- GitHub API:
/repos,/users,/issues - Stripe API:
/customers,/charges,/subscriptions - Twilio API:
/accounts,/messages,/calls - Google APIs:
/users,/groups,/files
모던 PetstoreAPI는 /pets, /orders, /users와 함께 이 표준을 따릅니다.
컬렉션 정신 모델
컬렉션을 이해하면 더 나은 API를 설계할 수 있습니다.
REST의 컬렉션
컬렉션은 리소스 집합입니다. 펫 스토어 API에서:
/pets- 모든 펫들의 컬렉션/orders- 모든 주문들의 컬렉션/users- 모든 사용자들의 컬렉션
각 컬렉션은 작업을 지원합니다:
GET /pets ← 펫 목록 (필터링, 페이지네이션 포함)
POST /pets ← 새로운 펫 생성
GET /pets/{id} ← 특정 펫 가져오기
PUT /pets/{id} ← 특정 펫 업데이트
DELETE /pets/{id} ← 특정 펫 삭제
하위 컬렉션
컬렉션은 하위 컬렉션을 포함할 수 있습니다:
GET /pets/{id}/photos ← 펫 {id}의 사진 컬렉션
POST /pets/{id}/photos ← 펫 {id}에 사진 추가
GET /pets/{id}/photos/{photoId} ← 특정 사진
패턴은 일관되게 유지됩니다: 컬렉션은 복수형이고, 항목은 ID로 접근됩니다.
모던 PetstoreAPI 예시
모던 PetstoreAPI는 이를 올바르게 구현합니다:
GET /pets
GET /pets/{petId}
GET /pets/{petId}/photos
POST /pets/{petId}/vaccinations
GET /orders
GET /orders/{orderId}
GET /orders/{orderId}/items
모든 컬렉션은 복수형입니다. 모든 항목 접근은 해당 컬렉션 내에서 {id}를 사용합니다.
모던 PetstoreAPI가 복수형 이름을 사용하는 방법
모던 PetstoreAPI의 실제 예시를 살펴보겠습니다.
펫 리소스
GET /pets ← 모든 펫 목록 조회
POST /pets ← 새로운 펫 생성
GET /pets/{petId} ← 특정 펫 가져오기
PUT /pets/{petId} ← 펫 업데이트
DELETE /pets/{petId} ← 펫 삭제
GET /pets?status=AVAILABLE ← 상태별 펫 필터링
주문 리소스
GET /orders ← 모든 주문 목록 조회
POST /orders ← 주문 생성
GET /orders/{orderId} ← 특정 주문 가져오기
PUT /orders/{orderId} ← 주문 업데이트
DELETE /orders/{orderId} ← 주문 취소
사용자 리소스
GET /users ← 사용자 목록 조회
POST /users ← 사용자 생성
GET /users/{userId} ← 특정 사용자 가져오기
PUT /users/{userId} ← 사용자 업데이트
DELETE /users/{userId} ← 사용자 삭제
중첩된 리소스
GET /pets/{petId}/photos ← 펫의 사진
POST /pets/{petId}/photos ← 사진 추가
GET /pets/{petId}/vaccinations ← 펫의 예방접종
POST /pets/{petId}/vaccinations ← 예방접종 기록
GET /orders/{orderId}/items ← 주문 항목
전체 엔드포인트 목록은 전체 REST API 문서를 확인하세요.
단수형에 대한 일반적인 주장 (그리고 왜 틀렸는지)
단수형 이름에 대한 일반적인 주장을 다루어 보겠습니다.
주장 1: “응답이 단수형이다”
주장: “/user/123을 GET하면 한 명의 사용자를 얻습니다. 단수형이 말이 됩니다.”
반론: URL은 응답이 아니라 리소스 위치를 나타냅니다. /users/123은 "사용자 컬렉션의 123번째 항목"을 의미합니다. 응답이 단수형이라고 해서 컬렉션 구조가 바뀌는 것은 아닙니다.
주장 2: “코드에서 더 잘 읽힌다”
주장: “getUser(id)가 getUsers(id)보다 더 잘 읽힙니다.”
반론: 클라이언트 코드의 명명법은 URL 구조와 독립적입니다. 다음처럼 사용할 수 있습니다:
// URL: GET /users/123
function getUser(id) {
return api.get(`/users/${id}`);
}
함수 이름이 URL과 일치할 필요는 없습니다.
주장 3: “단수형이 문법 문제를 피한다”
주장: “‘status’나 ‘information’처럼 명확한 복수형이 없는 리소스는 어떻게 해야 하나요?”
반론: 이것들은 일반적으로 컬렉션이 아닌 싱글턴 리소스입니다. 싱글턴에는 단수형을 사용하세요:
GET /status ← 시스템 상태 (싱글턴)
GET /configuration ← 앱 설정 (싱글턴)
GET /users ← 사용자 컬렉션 (복수형)
주장 4: “내 ORM은 단수형 테이블 이름을 사용한다”
주장: “내 데이터베이스 테이블은 단수형(user, order)이므로, 내 API도 일치해야 합니다.”
반론: API 설계는 데이터베이스 구현 세부 사항을 노출해서는 안 됩니다. REST API는 데이터베이스 테이블이 아닌 리소스를 나타냅니다. 데이터베이스 스키마와 관계없이 컬렉션에는 복수형을 사용하세요.
Apidog로 리소스 명명 테스트
Apidog는 리소스 명명 규칙을 검증하고 테스트하는 데 도움이 됩니다.
모던 PetstoreAPI 가져오기
- 모던 PetstoreAPI OpenAPI 사양 가져오기
- Apidog는 자동으로 리소스 패턴을 감지합니다.
- 일관성을 위해 엔드포인트 명명을 검토합니다.
명명 규칙 테스트 생성
// Apidog 테스트 스크립트
pm.test("리소스 이름은 복수형입니다.", function() {
const path = pm.request.url.getPath();
const segments = path.split('/').filter(s => s);
// 첫 번째 세그먼트가 복수형인지 확인
const resource = segments[0];
pm.expect(resource).to.match(/s$/); // 's'로 끝나는지
});
일관성 검증
Apidog는 다음을 확인할 수 있습니다:
- 모든 컬렉션 엔드포인트가 복수형 이름을 사용합니다.
- 하위 리소스가 동일한 패턴을 따릅니다.
- 동일한 API 내에서 단수형/복수형이 혼합되지 않습니다.
실제 요청으로 테스트
GET https://api.petstoreapi.com/v1/pets
GET https://api.petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24
GET https://api.petstoreapi.com/v1/orders
Apidog는 응답을 검증하고 API 전반에 걸쳐 명명 일관성을 보장하는 데 도움이 됩니다.
예외 상황 및 특수 사례
일부 리소스는 복수형 패턴에 맞지 않습니다.
싱글턴 리소스
하나만 존재하는 리소스는 단수형이어야 합니다:
GET /status ← 시스템 상태
GET /configuration ← 앱 설정
GET /health ← 상태 확인
GET /metrics ← 시스템 메트릭
이것들은 컬렉션이 아니므로 복수형이 적용되지 않습니다.
컨트롤러 리소스
CRUD 작업에 맞지 않는 작업들:
POST /login ← 인증 작업
POST /logout ← 세션 종료
POST /search ← 복합 검색 작업
이것들은 리소스가 아닌 작업을 나타내기 때문에 허용 가능한 예외입니다.
셀 수 없는 명사
일부 명사는 명확한 복수형이 없습니다:
GET /information ← 단수형 (셀 수 없음)
GET /data ← 단수형 (셀 수 없음)
GET /equipment ← 단수형 (셀 수 없음)
셀 수 없는 명사에는 단수형을 사용하지만, 이는 일반적인 API에서는 드뭅니다.
모던 PetstoreAPI 접근 방식
모던 PetstoreAPI는 이러한 경우를 처리합니다:
# 컬렉션 (복수형)
GET /pets
GET /orders
GET /users
# 싱글턴 (단수형)
GET /health
GET /metrics
# 작업 (단수 동사)
POST /login
POST /logout
결론
REST API 리소스 이름은 복수형이어야 합니다. 이는 컬렉션 의미론, HTTP 메서드, 산업 표준과 일치합니다. 모던 PetstoreAPI는 모든 엔드포인트에서 이 패턴을 올바르게 보여줍니다.
주요 요점:
- 컬렉션에는 복수형 이름 사용 (
/pets,/orders,/users) - 개별 항목은 컬렉션 내에서 접근됨 (
/pets/123) - 싱글턴 리소스는 단수형일 수 있음 (
/status,/health) - 완벽함보다 일관성이 중요
- Apidog로 명명 규칙 테스트
복수형 대 단수형 논쟁은 해결되었습니다. 산업 표준을 따르고, 복수형 이름을 사용하며, 개발자가 직관적으로 이해할 수 있는 API를 구축하세요.
다음 단계:
- 명명 일관성을 위해 API 엔드포인트를 검토하세요.
- 참조 패턴은 모던 PetstoreAPI 문서를 확인하세요.
- Apidog를 사용하여 API 설계를 검증하세요.
- 복수형 리소스 이름으로 OpenAPI 사양을 업데이트하세요.
FAQ
기존 API의 이름을 단수형에서 복수형으로 변경해야 하나요?
API가 이미 프로덕션에 있다면 리소스 이름 변경은 파괴적인 변경입니다. 다음을 고려하세요:
- 복수형 이름을 사용하는 새로운 v2 엔드포인트 추가
- 리디렉션을 통한 하위 호환성 유지
- 명명 규칙을 명확하게 문서화
명명 일관성만을 위해 기존 클라이언트를 파괴하지 마세요.
이미 복수형인 리소스는 어떻게 해야 하나요?
리소스 이름이 자연스럽게 복수형인 경우("analytics" 또는 "series"와 같이) 그대로 유지하세요:
GET /analytics ← 이미 복수형
GET /series ← 이미 복수형
GET /species ← 이미 복수형
중첩된 리소스는 어떻게 처리해야 하나요?
두 레벨 모두 복수형으로 유지하세요:
GET /users/{userId}/orders ← 둘 다 복수형
GET /pets/{petId}/vaccinations ← 둘 다 복수형
GET /orders/{orderId}/items ← 둘 다 복수형
우리 팀이 단수형을 선호한다면 어떻게 해야 하나요?
API 내의 일관성이 가장 중요합니다. 팀이 이미 단수형 이름으로 표준화했고 변경하면 혼란을 야기할 경우, 단수형을 고수하세요. 하지만 새로운 API의 경우 복수형을 사용하세요.
GraphQL은 복수형을 사용하나요, 단수형을 사용하나요?
GraphQL은 일반적으로 단일 항목 쿼리에는 단수형을, 목록에는 복수형을 사용합니다:
query {
user(id: "123") { ... } # 단수형
users(limit: 10) { ... } # 복수형
}
GraphQL 쿼리는 하나 또는 여러 개를 반환하는 것을 명시적으로 나타내기 때문에 REST와는 다릅니다.
모던 PetstoreAPI는 이것을 어떻게 처리하나요?
모던 PetstoreAPI는 모든 REST 엔드포인트에 걸쳐 복수형 이름을 일관되게 사용합니다. 전체 예시는 REST API 가이드를 확인하세요.
명명 규칙을 자동으로 테스트할 수 있나요?
네, Apidog는 전체 API에 걸쳐 리소스 명명 패턴을 확인하는 자동화된 테스트를 실행할 수 있습니다. OpenAPI 사양을 가져와 명명 규칙에 대한 테스트 케이스를 생성하세요.
비영어권 API는 어떻게 해야 하나요?
복수형 규칙은 언어와 상관없이 적용됩니다. API가 프랑스어 리소스 이름을 사용한다면 프랑스어 복수형을 사용하세요. 일본어를 사용한다면 일본어 문법 규칙을 따르세요. 원칙(컬렉션은 복수형)은 동일합니다.
