REST 중첩 자원에 대한 모범 사례는 무엇입니까?


301

내가 말할 수있는 한, 개별 자원마다 하나의 표준 경로 있어야 합니다. 다음 예제에서 좋은 URL 패턴은 무엇입니까?

나머지 회사를 예로 들어 보겠습니다. 이 가상의 예에서 각 회사 0 개 이상의 부서를 소유 하고 각 부서 0 개 이상의 직원을 소유 합니다.

관련 회사 없는 부서 는 존재할 수 없습니다 .

관련 부서 없는 직원 은 존재할 수 없습니다 .

이제 리소스 패턴을 자연스럽게 표현했습니다.

  • /companies 회사 모음 -새 회사에 허용됩니다. 전체 컬렉션을 얻으십시오.
  • /companies/{companyId}개별 회사. GET, PUT 및 DELETE 허용
  • /companies/{companyId}/departments새 항목에 대해 POST를 승인합니다. (회사 내에 부서를 만듭니다.)
  • /companies/{companyId}/departments/{departmentId}/
  • /companies/{companyId}/departments/{departmentId}/employees
  • /companies/{companyId}/departments/{departmentId}/employees/{empId}

제약 조건이 주어지면 각 섹션에서 조금 깊게 중첩되면 이것이 의미가 있다고 생각합니다.

그러나 GET모든 회사의 모든 직원 을 나열하려면 ( ) 어려움이 따릅니다.

이에 대한 리소스 패턴은 /employees(모든 직원의 컬렉션)에 가장 밀접하게 매핑됩니다.

그렇다면 /employees/{empId}동일한 리소스를 얻는 두 개의 URI가 있기 때문에 내가 가져야한다는 것을 의미합니까 ?

또는 전체 스키마를 평탄화해야하지만 직원이 중첩 된 최상위 개체임을 의미합니다.

기본 수준 /employees/?company={companyId}&department={deptId}에서 가장 깊게 중첩 된 패턴과 동일한 직원보기를 반환합니다.

다른 리소스 가 리소스를 소유 하지만 별도로 쿼리 할 수 있는 URL 패턴에 대한 모범 사례는 무엇입니까 ?


1
답변은 관련이있을 수 있지만 stackoverflow.com/questions/7104578/…에 설명 된 것과 거의 oppsite 문제 입니다. 두 가지 질문 모두 소유권에 관한 것이지만이 예는 최상위 개체가 소유 한 개체가 아님을 나타냅니다.
Wes

1
정확히 내가 궁금했던 것. 주어진 유스 케이스의 경우 솔루션이 괜찮은 것처럼 보이지만 관계가 컴포지션이 아닌 집계이면 어떨까요? 모범 사례가 무엇인지 파악하기 위해 여전히 고심하고 있습니다 ... 또한,이 솔루션은 기존 사람이 고용되었거나 사람 개체를 생성하는 등의 관계 생성만을 의미합니까?
Jakob O.

가상의 예에서 사람을 만듭니다. 내가 그 도메인 용어를 사용한 이유는 내 실제 문제를 모방하지만 이해하기 쉬운 예입니다. aggragation 관계에 더 많은 영향을 줄 수있는 관련 질문을 살펴 보셨습니까?
Wes

내 질문을 답변과 질문으로 나누었습니다.
Wes

답변:


152

당신이 한 일은 맞습니다. 일반적으로 동일한 리소스에 대한 많은 URI가있을 수 있습니다. 그렇게하지 말아야 할 규칙은 없습니다.

그리고 일반적으로 항목에 직접 액세스하거나 다른 것의 하위 세트로 액세스해야 할 수도 있으므로 구조가 나에게 적합합니다.

직원이 부서에서 액세스 할 수 있기 때문에 :

company/{companyid}/department/{departmentid}/employees

회사에서도 액세스 할 수 없다는 의미는 아닙니다.

company/{companyid}/employees

그 회사의 직원을 반환합니다. 그것은 소비하는 고객이 필요로하는 것에 달려 있습니다-그것이 당신이 디자인 해야하는 것입니다.

그러나 모든 URL 핸들러가 동일한 백업 코드를 사용하여 요청을 충족시켜 코드를 복제하지 않기를 바랍니다.


11
이것은 RESTful의 정신을 지적하고 있습니다 . 의미있는 자원을 먼저 고려할 때해야하거나하지 말아야 할 규칙은 없습니다 . 그러나 그러한 시나리오에서 코드복제하지 않는 가장 좋은 방법은 무엇인지 궁금합니다 .
abookyun

13
@abookyun 두 경로가 모두 필요한 경우 반복되는 컨트롤러 코드를 서비스 객체로 추상화 할 수 있습니다.
bgcode

이것은 REST와 아무 관련이 없습니다. REST는 ...에 대한 희망 내구성의 URI, 유효가 관심 ... 당신이 당신의 URL의 경로 부분을 구성하는 방법에 대한 모든 상관하지 않는다
redben

이 답변을 추진 하면서 동적 세그먼트가 모두 고유 식별자api 는 여러 동적 세그먼트를 처리 할 필요가 없다고 생각합니다 ( ). API가 각 리소스를 가져 오는 방법을 제공하는 경우 해당 요청을 각각 클라이언트 측 라이브러리 또는 코드를 재사용하는 일회용 엔드 포인트에서 수행 할 수 있습니다. /company/3/department/2/employees/1
최대

1
금지는 없지만 자원에 대한 경로가 하나만있는 것이 더 우아하다고 생각합니다. 모든 정신 모델을 더 단순하게 유지합니다. 또한 중첩이 있으면 URI가 리소스 유형을 변경하지 않는 것이 좋습니다. 예를 들어 /company/*회사 리소스 만 반환하고 리소스 유형을 전혀 변경하지 않아야합니다. 이 중 어느 것도 REST에 의해 지정되지 않으며 일반적으로 잘못 지정되어 있지만 개인적인 취향입니다.
kashif

174

중첩 및 비 중첩 엔드 포인트 디자인 전략을 모두 시도했습니다. 나는 그것을 발견했다 :

  1. 중첩 된 리소스에 기본 키가 있고 부모 기본 키가없는 경우 시스템에 실제로는 필요하지 않더라도 중첩 구조를 통해이를 가져와야합니다.

  2. 중첩 된 엔드 포인트에는 일반적으로 중복 엔드 포인트가 필요합니다. 다시 말해, 부서 전체의 직원 목록을 얻을 수 있도록 추가 / 종업원 엔드 포인트가 더 자주 필요합니다. / 직원이있는 경우 / companies / departments / employees가 정확히 무엇을 구매합니까?

  3. 중첩 끝점은 멋지게 발전하지 않습니다. 예를 들어 지금 직원을 검색 할 필요는 없지만 나중에 직원을 검색 할 수 있으며 내포 구조가있는 경우 다른 엔드 포인트를 추가 할 수밖에 없습니다. 중첩되지 않은 디자인에서는 더 많은 매개 변수를 추가하기 만하면됩니다.

  4. 때때로 자원에는 여러 유형의 부모가있을 수 있습니다. 여러 엔드 포인트가 모두 동일한 자원을 리턴합니다.

  5. 중복 엔드 포인트는 문서를 쓰기 어렵게 만들고 API를 배우기 어렵게합니다.

요컨대, 중첩되지 않은 디자인은보다 유연하고 간단한 엔드 포인트 스키마를 허용하는 것으로 보입니다.


24
이 답변을 보게되어 매우 상쾌했습니다. 저는 "올바른 방법"이라는 가르침을받은 후 몇 개월 동안 중첩 된 엔드 포인트를 사용해 왔습니다. 위에서 언급 한 것과 동일한 결론에 도달했습니다. 중첩되지 않은 디자인으로 훨씬 쉽습니다.
user3344977

6
단점의 일부를 거꾸로 나열하는 것 같습니다. "단일 엔드 포인트에 더 많은 매개 변수를 넣는 것"은 API가 다른 방법이 아닌 문서화 및 학습을 어렵게 만듭니다. ;-)
Drenmi

4
이 답변의 팬이 아닙니다. 중첩 된 리소스를 추가했기 때문에 중복 엔드 포인트를 도입 할 필요가 없습니다. 부모가 실제로 중첩 된 리소스를 소유 한 경우 여러 부모가 동일한 리소스를 반환하는 것도 문제가되지 않습니다. 중첩 된 리소스와 상호 작용하는 방법을 배우기 위해 부모 리소스를 얻는 것은 문제가되지 않습니다. 좋은 검색 가능한 REST API가이를 수행해야합니다.
Scottm

3
@Scottm-내가 찾은 중첩 리소스의 한 가지 단점은 상위 리소스 ID가 올바르지 않거나 일치하지 않으면 잘못된 데이터를 반환 할 수 있다는 것입니다. 권한 부여 문제가 없다고 가정하면 중첩 된 리소스가 실제로 전달 된 상위 리소스의 자식인지 확인하는 것은 API 구현에 달려 있습니다. 이 검사가 코딩되지 않은 경우 API 응답이 올바르지 않아 손상 될 수 있습니다. 당신의 생각은 무엇입니까?
Andy Dufresne

1
최종 자원에 모두 고유 한 ID가있는 경우 중간 상위 ID가 필요하지 않습니다. 예를 들어, 직원을 아이디로 받으려면 GET / companies / departments / employees / {empId}가 있거나 회사 123의 모든 직원을 얻으려면 GET / companies / 123 / departments / employees /가 있습니다 중간 리소스를 통해 필터링 / 생성 / 수정할 수 있으며 내 의견으로는 검색 가능성을 높일 수 있습니다.
PaulG

77

질문에서 내가 한 일을 더 많은 사람들이 볼 수있는 대답으로 옮겼습니다.

내가 한 것은 중첩 된 끝점에 생성 끝점 을 두는 것입니다 . 항목을 수정하거나 쿼리하기위한 표준 끝점 은 중첩 된 리소스아닙니다 .

따라서이 예에서 (자원을 변경하는 엔드 포인트 만 나열)

  • POST /companies/ 새 회사를 만들면 만든 회사에 대한 링크가 반환됩니다.
  • POST /companies/{companyId}/departments 부서를 넣을 때 새 부서가 생성되면 /departments/{departmentId}
  • PUT /departments/{departmentId} 부서를 수정하다
  • POST /departments/{deparmentId}/employees 새 직원을 생성하여 /employees/{employeeId}

따라서 각 컬렉션에 대한 루트 수준 리소스가 있습니다. 그러나 작성소유 오브젝트에 있습니다.


4
나는 같은 유형의 디자인도 생각해 냈습니다. 나는 "그들이 속한 곳"과 같은 것들을 만드는 것이 직관적이지만 여전히 전 세계적으로 그것들을 나열 할 수 있다고 생각합니다. 리소스가 반드시 부모를 갖는 관계가있을 때 더욱 그렇습니다. 그런 다음 전 세계적으로 해당 리소스를 생성한다고해서 명확하지는 않지만 이와 같은 하위 리소스에서 리소스를 작성하는 것은 완벽합니다.
Joakim

나는 당신이 POST의미 를 사용 했다고 생각합니다 PUT.
Gerardo Lima

실제로 아니요 서버의 경우 링크에있는 ID를 반환 할 책임이 있으므로 생성에 사전 할당 된 ID를 사용하지 않습니다. 따라서 POST 작성은 정확합니다 (동일한 구현을 수행 할 수 없음). 그러나 풋은 전체 리소스를 변경하지만 여전히 동일한 위치에서 사용 가능하므로 PUT합니다. PUT과 POST는 다른 문제이며 논쟁의 여지가 있습니다. 예를 들어 stackoverflow.com/questions/630453/put-vs-post-in-rest
Wes

@Wes 심지어 부모 메소드 아래에 동사 메소드를 수정하는 것을 선호합니다. 그러나 전역 리소스에 대한 쿼리 매개 변수 전달이 잘 받아 들여 있습니까? 예 : 검색어 매개 변수 company = company-id가있는 POST / departments
Ayyappa

1
@Mohamad 당신이 다른 방법이 제약을 이해하고 적용하는 것이 더 쉽다고 생각한다면, 자유롭게 대답하십시오. 이 경우 매핑을 명시 적으로 만드는 것입니다. 매개 변수로 작업 할 수는 있지만 실제로 질문입니다. 가장 좋은 방법은 무엇입니까?
Wes

35

위의 답변을 모두 읽었지만 일반적인 전략이없는 것 같습니다. Microsoft Documents의 Design API 모범 사례에 대한 좋은 기사를 찾았습니다 . 나는 당신이 참조해야한다고 생각합니다.

보다 복잡한 시스템에서는 클라이언트가 여러 수준의 관계를 탐색 할 수있는 URI를 제공하려는 유혹이있을 수 있습니다. /customers/1/orders/99/products.그러나 이러한 수준의 복잡성은 유지 관리가 어려울 수 있으며 향후 리소스 간의 관계가 변경 될 경우 융통성이 없습니다. 대신 URI를 비교적 단순하게 유지하십시오 . 응용 프로그램에 리소스에 대한 참조가 있으면이 참조를 사용하여 해당 리소스와 관련된 항목을 찾을 수 있어야합니다. 앞의 조회를 URI /customers/1/orders로 대체하여 고객 1의 모든 주문 /orders/99/products을 찾은 다음 이 주문에서 제품을 찾을 수 있습니다.

.

보다 복잡한 URI를 요구하지 마십시오 collection/item/collection.


3
당신이 제공하는 참조는 복잡한 URI를 만들지 않는다는 점에서 놀랍습니다.
vicco

따라서 사용자를위한 팀을 만들려면 POST / teams (body의 userId) 또는 POST / users / : id / teams
여야합니다.

@coinhndp 안녕하세요, POST / teams를 사용해야하며 액세스 토큰을 승인 한 후 userId를 얻을 수 있습니다. 물건을 만들 때 인증 코드가 필요합니다. 사용중인 프레임 워크를 모르지만 API 컨트롤러에서 userId를 얻을 수 있다고 확신합니다. 예를 들면 다음과 같습니다. ASP.NET API에서 ApiController의 메서드 내에서 RequestContext.Principal을 호출합니다. Spring Secirity에서 SecurityContextHolder.getContext (). getAuthentication (). getPrincipal ()이 도움이 될 것입니다. AWS NodeJS Lambda에서 이는 헤더 객체의 cognito : username입니다.
Long Nguyen

POST / users / : id / teams의 문제점입니다. 위에서 게시 한 Microsoft 문서에서 권장되는 것
같습니다

@coinhndp 팀을 관리자로 만들면 좋습니다. 그러나 일반 사용자로서 왜 경로에 userId가 필요한지 모르겠습니다. user_A와 user_B가 있다고 가정합니다. user_A가 POST / users / user_B / teams를 호출하면 user_A가 user_B에 대한 새 팀을 만들 수 있다고 생각합니다. 따라서이 경우 userId를 전달할 필요가 없으며 userId는 권한 부여 후에 얻을 수 있습니다. 그러나 teams / : id / projects는 예를 들어 팀과 프로젝트 사이의 관계를 만드는 것이 좋습니다.
Long Nguyen

10

URL이 REST와 무관하게 보이는 방식. 무슨 일이든 상관 없습니다 실제로는 "구현 세부 사항"입니다. 변수 이름을 지정하는 것과 같습니다. 그들이 독특하고 내구성이 있어야합니다.

이것에 너무 많은 시간을 낭비하지 말고 선택하고 일관성을 유지하십시오. 예를 들어 계층 구조를 사용하는 경우 모든 리소스에 대해 수행합니다. 코드의 명명 규칙과 같은 쿼리 매개 변수 등을 사용하는 경우.

왜 그래? 내가 "RESTful"API를 탐색 할 수있는 한 ( "애플리케이션 상태 엔진의 하이퍼 미디어") API 클라이언트는 URL의 길이에 대해 신경 쓰지 않습니다. 유효한 (디버그가 없으며 디버깅을위한 경우를 제외하고는 "친숙한 URL"을 읽어야하는 사람이 없습니다 ...)

REST API에 URL이 얼마나 훌륭하고 이해할 수 있는지는 코드의 변수 이름처럼 API 클라이언트가 아니라 API 개발자로서 흥미로울 것입니다.

가장 중요한 것은 API 클라이언트가 미디어 유형을 해석하는 방법을 알고 있다는 것입니다. 예를 들어 다음을 알고 있습니다.

  • 미디어 유형에는 사용 가능한 / 관련 링크를 나열하는 링크 속성이 있습니다.
  • 각 링크는 관계로 식별됩니다 (브라우저가 link [rel = "stylesheet"]가 해당 스타일 시트를 의미하거나 rel = favico가 favicon에 대한 링크임을 의미 함)
  • 이러한 관계의 의미를 알고 있습니다 ( "회사"는 회사 목록을 의미하고 "검색"은 자원 목록을 검색하기위한 템플릿 URL을 의미하고 "부서"는 현재 자원의 부서를 의미)

아래는 HTTP 교환의 예입니다 (작성하기 쉬우므로 본문이 yaml에 있음).

의뢰

GET / HTTP/1.1
Host: api.acme.io
Accept: text/yaml, text/acme-mediatype+yaml

응답 : 주요 자원 (회사, 사람 등 무엇이든)에 대한 링크 목록

HTTP/1.1 200 OK
Date: Tue, 05 Apr 2016 15:04:00 GMT
Last-Modified: Tue, 05 Apr 2016 00:00:00 GMT
Content-Type: text/acme-mediatype+yaml

# body: this is your API's entrypoint (like a homepage)  
links:
  # could be some random path https://api.acme.local/modskmklmkdsml
  # the only thing the API client cares about is the key (or rel) "companies"
  companies: https://api.acme.local/companies
  people: https://api.acme.local/people

요청 : 회사 링크 (이전 응답의 body.links.companies 사용)

GET /companies HTTP/1.1
Host: api.acme.local
Accept: text/yaml, text/acme-mediatype+yaml

응답 : 회사 (항목 아래)의 일부 목록에있는 리소스에는 다음 두 회사 (body.links.next)를 검색하는 다른 링크 (템플릿)를 얻는 링크 (body.links.search)와 같은 관련 링크가 포함되어 있습니다.

HTTP/1.1 200 OK
Date: Tue, 05 Apr 2016 15:06:00 GMT
Last-Modified: Tue, 05 Apr 2016 00:00:00 GMT
Content-Type: text/acme-mediatype+yaml

# body: representation of a list of companies
links:
  # link to the next page
  next: https://api.acme.local/companies?page=2
  # templated link for search
  search: https://api.acme.local/companies?query={query} 
# you could provide available actions related to this resource
actions:
  add:
    href: https://api.acme.local/companies
    method: POST
items:
  - name: company1
    links:
      self: https://api.acme.local/companies/8er13eo
      # and here is the link to departments
      # again the client only cares about the key department
      department: https://api.acme.local/companies/8er13eo/departments
  - name: company2
    links:
      self: https://api.acme.local/companies/9r13d4l
      # or could be in some other location ! 
      department: https://api2.acme.local/departments?company=8er13eo

따라서 URL의 경로 부분을 구성하는 방법으로 링크 / 관계로 이동하면 API 클라이언트에 아무런 가치가 없습니다. 그리고 URL 구조를 문서로 고객에게 전달하는 경우 REST를 수행하지 않는 것입니다 (또는 " Richardson의 성숙도 모델 "에 따른 레벨 3 이상 ).


7
"URL이 REST API에 얼마나 훌륭하고 이해 가능한가는 코드의 변수 이름처럼 API 클라이언트가 아니라 API 개발자로서 흥미 롭습니다." 왜 이것이 흥미롭지 않습니까? 본인 이외의 사람도 API를 사용하는 경우 매우 중요합니다. 이것은 사용자 경험의 일부이므로 API 클라이언트 개발자가 이해하기 쉽다는 것이 매우 중요합니다. 리소스를 명확하게 연결하여 이해하기 쉽게 만드는 것은 물론 보너스입니다 (제공 한 URL의 레벨 3). 명확한 관계로 모든 것이 직관적이고 논리적이어야합니다.
Joakim

1
@Joakim 레벨 3 rest API (응용 프로그램 엔진 상태의 하이퍼 텍스트)를 작성하는 경우, URL의 경로 구조는 클라이언트에게 전혀 관심이 없습니다 (유효한 경우). 당신이 레벨 3을 목표로하지 않는다면, 그렇습니다. 중요하며 추측 가능해야합니다. 그러나 실제 REST는 레벨 3입니다. 좋은 기사 : martinfowler.com/articles/richardsonMaturityModel.html
redben

4
인간에게 친숙하지 않은 API 또는 UI를 만드는 것에 반대합니다. 레벨 3에 관계없이 리소스를 연결하는 것이 좋습니다. 그러나 이렇게하면 "URL 스킴을 변경할 수있게한다"는 현실과 사람들이 API를 사용하는 방식과 접촉하지 않아야합니다. 따라서 나쁜 추천입니다. 그러나 모든 세계에서 최고는 모두 레벨 3 REST에있을 것입니다. 하이퍼 링크를 통합하고 이해하기 쉬운 URL 체계를 사용합니다. 레벨 3은 전자를 배제하지 않으며, 하나는 제 생각에 케어해야합니다. 그래도 좋은 기사 :)
Joakim

물론 유지 관리 및 기타 우려를 위해 관심을 가져야합니다. 내 대답의 요점을 놓친 것 같습니다. URL이 보이는 방식은 많은 생각을 할 가치가 없으며 "선택을하고 고수해야합니다. 내가 대답에서 말했듯이. 그리고 REST API의 경우, 적어도 내 의견으로는, 사용자 친화 성은 URL이 아니며 대부분 미디어 유형에 있습니다. 어쨌든 내 요점을 이해하기를 바랍니다 :)
redben

9

나는 이런 종류의 길에 동의하지 않는다

GET /companies/{companyId}/departments

부서를 원한다면 / departments 리소스를 사용하는 것이 좋습니다

GET /departments?companyId=123

companies테이블과 테이블이 있고 departments사용하는 프로그래밍 언어로 매핑하는 클래스 가 있다고 가정합니다 . 또한 부서를 회사 이외의 다른 엔티티에 연결할 수 있다고 가정하므로 / departments 자원은 간단하며 자원을 테이블에 맵핑하는 것이 편리하며 재사용 할 수 있으므로 많은 엔드 포인트가 필요하지 않습니다.

GET /departments?companyId=123

예를 들어 어떤 종류의 검색이든

GET /departments?name=xxx
GET /departments?companyId=123&name=xxx
etc.

부서를 만들려면

POST /departments

리소스를 사용해야하고 요청 본문에 회사 ID가 포함되어야합니다 (부서가 한 회사에만 연결될 수있는 경우).


1
나에게 이것은 중첩 된 객체가 원자 객체로 이해되는 경우에만 허용되는 접근 방식입니다. 그렇지 않다면, 그것들을 분리하는 것이 실제로 의미가 없습니다.
Simme

부서를 검색 할 수 있기를 원한다면 / departments 엔드 포인트를 사용할지 여부를 말씀 드렸습니다.
Maxime Laval

2
회사를 페치 할 때 지연로드를 통해 부서를 포함시킬 수도 있습니다. 예를 들어 GET /companies/{companyId}?include=departments회사와 부서를 단일 HTTP 요청으로 페치 할 수 있기 때문입니다. 프랙탈은 이것을 정말 잘합니다.
Matthew Daly

1
acls를 설정할 때 아마도 /departments엔드 포인트가 관리자 만 액세스 할 수 있도록 제한하고 각 회사가`/ companies / {companyId} / departments`를 통해서만 자신의 부서에 액세스하게 할 수 있습니다.
Cuzox

@MatthewDaly OData도 $ expand로 훌륭하게 수행합니다
Rob Grant

1

Rails는이를위한 솔루션을 제공합니다 : 얕은 네 스팅 .

알려진 리소스를 직접 처리 할 때 여기에 다른 답변에서 논의 된 것처럼 중첩 된 경로를 사용할 필요가 없기 때문에 이것이 좋습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.