상속을 사용하여 RESTful API를 모델링하는 방법은 무엇입니까?


87

RESTful API를 통해 노출해야하는 객체 계층 구조가 있는데 내 URL이 어떻게 구조화되어야하고 무엇을 반환해야하는지 잘 모르겠습니다. 모범 사례를 찾을 수 없습니다.

동물에서 물려받은 개와 고양이가 있다고 가정 해 보겠습니다. 나는 개와 고양이에 대한 CRUD 작업이 필요합니다. 나는 또한 일반적인 동물에 대한 수술을 할 수 있기를 원합니다.

내 첫 번째 아이디어는 다음과 같이하는 것이 었습니다.

GET /animals        # get all animals
POST /animals       # create a dog or cat
GET /animals/123    # get animal 123

문제는 / animals 컬렉션이 이제 정확히 동일한 구조 (개와 고양이)를 갖지 않는 개체를 반환하고 가져올 수 있으므로 "일관되지 않음"입니다. 서로 다른 속성을 가진 개체를 반환하는 컬렉션을 갖는 것이 "휴식"으로 간주됩니까?

또 다른 해결책은 다음과 같이 각 구체적인 유형에 대한 URL을 만드는 것입니다.

GET /dogs       # get all dogs
POST /dogs      # create a dog
GET /dogs/123   # get dog 123

GET /cats       # get all cats
POST /cats      # create a cat
GET /cats/123   # get cat 123

그러나 이제는 개와 고양이의 관계가 사라졌습니다. 모든 동물을 회수하려면 개와 고양이 자원을 모두 조회해야합니다. URL의 수도 새로운 동물 하위 유형마다 증가합니다.

또 다른 제안은 다음을 추가하여 두 번째 솔루션을 확장하는 것입니다.

GET /animals    # get common attributes of all animals

이 경우 반환 된 동물은 모든 동물에 공통적 인 속성 만 포함하고 개별 및 고양이 별 속성을 삭제합니다. 이렇게하면 세부 사항은 적지 만 모든 동물을 검색 할 수 있습니다. 반환 된 각 객체에는 상세하고 구체적인 버전에 대한 링크가 포함될 수 있습니다.

의견이나 제안이 있으십니까?

답변:


41

내가 제안 할게:

  • 리소스 당 하나의 URI 만 사용
  • 특성 수준에서만 동물 구분

동일한 리소스에 여러 URI를 설정하는 것은 혼란과 예상치 못한 부작용을 일으킬 수 있으므로 결코 좋은 생각이 아닙니다. 따라서 단일 URI는 /animals.

"기본"수준에서 개와 고양이의 전체 컬렉션을 처리하는 다음 과제는 이미 /animalsURI 접근 방식 을 통해 해결되었습니다 .

개와 고양이와 같은 특수 유형을 처리하는 마지막 과제는 미디어 유형 내에서 쿼리 매개 변수와 식별 속성을 조합하여 쉽게 해결할 수 있습니다. 예를 들면 :

GET /animals( Accept : application/vnd.vet-services.animals+json)

{
   "animals":[
      {
         "link":"/animals/3424",
         "type":"dog",
         "name":"Rex"
      },
      {
         "link":"/animals/7829",
         "type":"cat",
         "name":"Mittens"
      }
   ]
}
  • GET /animals -모든 개와 고양이를 얻고 Rex와 Mittens를 모두 반환합니다.
  • GET /animals?type=dog -모든 개를 가져오고 Rex 만 반환합니다.
  • GET /animals?type=cat -모든 고양이를 얻고, 장갑 만

그런 다음 동물을 만들거나 수정할 때 관련된 동물의 유형을 지정하는 것은 호출자에게 있습니다.

미디어 유형: application/vnd.vet-services.animal+json

{
   "type":"dog",
   "name":"Fido"
}

위의 페이로드는 POST또는 PUT요청 으로 보낼 수 있습니다 .

위의 체계는 REST를 통한 OO 상속과 기본적으로 유사한 특성을 제공하며, 주요 수술이나 URI 체계의 변경없이 추가 전문화 (예 : 더 많은 동물 유형)를 추가 할 수있는 기능을 제공합니다.


이것은 REST API를 통한 "캐스팅"과 매우 유사합니다. 또한 C ++ 하위 클래스의 메모리 레이아웃에있는 문제 / 솔루션을 상기시킵니다. 예를 들어 메모리에서 단일 주소를 사용하여 기본 및 하위 클래스를 동시에 나타내는 위치와 방법.
trcarden

10
나는 제안한다 : GET /animals - gets all dogs and cats GET /animals/dogs - gets all dogs GET /animals/cats - gets all cats
dipold

1
원하는 유형을 GET 요청 매개 변수로 지정하는 것 외에도 승인 유형을 사용하여이 작업을 수행 할 수도 있습니다. 즉, GET /animals 수락application/vnd.vet-services.animal.dog+json
BrianT.

22
고양이와 개가 각각 고유 한 특성을 가지고 있다면 어떨까요? POST대부분의 프레임 워크는 json이 좋은 타이핑 정보를 가지고 있지 않기 때문에 모델로 적절하게 역 직렬화하는 방법을 알지 못하기 때문에 작동시 어떻게 처리할까요 ? 예를 들어 포스트 케이스를 어떻게 처리 [{"type":"dog","name":"Fido","playsFetch":true},{"type":"cat","name":"Sparkles","likesToPurr":"sometimes"}하시겠습니까?
LB2

1
개와 고양이가 (대부분) 다른 특성을 가지고 있다면 어떨까요? eg # 1 SMS (to, mask) 대 이메일 (email address, cc, bcc, to, from, isHtml)에 대한 통신 게시 또는 eg # 2 CreditCard (maskedPan, nameOnCard, Expiry) 대. BankAccount (bsb, accountNumber) ... 여전히 단일 API 리소스를 사용 하시겠습니까? 이것은 SOLID 원칙의 단일 책임을 위반하는 것처럼 보이지만 이것이 API 설계에 적용되는지 확실하지 않습니다 ...
Ryan.Bartsch

5

이 질문은 OpenAPI의 최신 버전에 도입 된 최근 개선 사항의 지원을 통해 더 잘 대답 할 수 있습니다.

oneOf, allOf, anyOf와 같은 키워드를 사용하여 스키마를 결합하고 JSON 스키마 v1.0 이후 검증 된 메시지 페이로드를 얻을 수있었습니다.

https://spacetelescope.github.io/understanding-json-schema/reference/combining.html

그러나 OpenAPI (이전 Swagger)에서는 키워드 판별 자 (v2.0 +) 및 oneOf (v3.0 +)를 통해 스키마 구성이 향상되어 다형성을 진정으로 지원합니다.

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaComposition

상속은 oneOf (하위 유형 중 하나를 선택)와 allOf (유형과 하위 유형 중 하나를 결합)의 조합을 사용하여 모델링 할 수 있습니다. 다음은 POST 메서드에 대한 샘플 정의입니다.

paths:
  /animals:
    post:
      requestBody:
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/Dog'
              - $ref: '#/components/schemas/Cat'
              - $ref: '#/components/schemas/Fish'
            discriminator:
              propertyName: animal_type
     responses:
       '201':
         description: Created

components:
  schemas:
    Animal:
      type: object
      required:
        - animal_type
        - name
      properties:
        animal_type:
          type: string
        name:
          type: string
      discriminator:
        property_name: animal_type
    Dog:
      allOf:
        - $ref: "#/components/schemas/Animal"
        - type: object
          properties:
            playsFetch:
              type: string
    Cat:
      allOf:
        - $ref: "#/components/schemas/Animal"
        - type: object
          properties:
            likesToPurr:
              type: string
    Fish:
      allOf:
        - $ref: "#/components/schemas/Animal"
        - type: object
          properties:
            water-type:
              type: string

1
OAS가이를 허용하는 것은 사실입니다. 그러나 Swagger UI ( link )에 표시되는 기능은 지원되지 않으며 , 누구에게도 표시 할 수없는 기능은 제한적으로 사용되는 것 같습니다.
emft

1
@emft, 사실이 아닙니다. 이 답변을 작성하는 현재 Swagger UI는 이미 지원합니다.
Andrejs Cainikovs

감사합니다. 잘 작동합니다! 현재 Swagger UI가이를 완전히 표시하지 않는 것은 사실입니다. 모델은 하단의 스키마 섹션에 표시되고 oneOf 섹션을 참조하는 모든 응답 섹션은 UI에 부분적으로 표시되지만 (스키마 만, 예제 없음) 요청 입력에 대한 예제 본문은 제공되지 않습니다. 이에 대한 github 문제는 3 년 동안 열려 있으므로 그대로 유지 될 가능성이 높습니다. github.com/swagger-api/swagger-ui/issues/3803
Jethro

4

나는 개와 물고기의 목록을 반환하는 / animals를 찾으러 갈 것입니다.

<animals>
  <animal type="dog">
    <name>Fido</name>
    <fur-color>White</fur-color>
  </animal>
  <animal type="fish">
    <name>Wanda</name>
    <water-type>Salt</water-type>
  </animal>
</animals>

유사한 JSON 예제를 쉽게 구현할 수 있어야합니다.

클라이언트는 항상 "이름"요소 (공통 속성)에 의존 할 수 있습니다. 그러나 "type"속성에 따라 동물 표현의 일부로 다른 요소가 있습니다.

이러한 목록을 반환하는 데 본질적으로 RESTful 또는 unRESTful은 없습니다. REST는 데이터를 나타내는 특정 형식을 규정하지 않습니다. 데이터에 어떤 표현이 있어야 하며 해당 표현의 형식은 미디어 유형 (HTTP에서 Content-Type 헤더)에 의해 식별됩니다.

사용 사례에 대해 생각해보십시오. 혼합 동물 목록을 표시해야합니까? 그럼 혼합 동물 데이터 목록을 반환합니다. 개 목록 만 필요합니까? 글쎄, 그런 목록을 만드십시오.

/ animals? type = dog 또는 / dogs를 수행하는지 여부는 URL 형식을 규정하지 않는 REST와 관련이 없습니다. 이는 REST 범위 밖의 구현 세부 사항으로 남겨집니다. REST는 리소스에 식별자가 있어야한다고 만 명시합니다. 형식은 신경 쓰지 마세요.

RESTful API에 더 가까워 지려면 하이퍼 미디어 링크를 추가해야합니다. 예를 들어 동물 세부 사항에 대한 참조를 추가하면 다음과 같습니다.

<animals>
  <animal type="dog" href="https://stackoverflow.com/animals/123">
    <name>Fido</name>
    <fur-color>White</fur-color>
  </animal>
  <animal type="fish" href="https://stackoverflow.com/animals/321">
    <name>Wanda</name>
    <water-type>Salt</water-type>
  </animal>
</animals>

하이퍼 미디어 링크를 추가하면 클라이언트 / 서버 커플 링을 줄일 수 있습니다. 위의 경우 URL 구성의 부담을 클라이언트에서 제거하고 서버가 URL 구성 방법 (정의에 따라 유일한 권한 임)을 결정하도록합니다.


1

그러나 이제는 개와 고양이의 관계가 사라졌습니다.

사실, 그러나 URI는 객체 간의 관계를 결코 반영하지 않는다는 것을 명심하십시오.


1

이것은 오래된 질문이라는 것을 알고 있지만 RESTful 상속 모델링에 대한 추가 문제를 조사하는 데 관심이 있습니다.

나는 항상 개는 동물이고 암탉이라고 말할 수 있습니다. 그러나 암탉은 알을 만들고 개는 포유류이므로 그렇게 할 수 없습니다. 같은 API

GET animals / : animalID / eggs

동물의 모든 아형이 알을 가질 수 있음을 나타 내기 때문에 일관성이 없습니다 (Liskov 치환의 결과로). 모든 포유류가이 요청에 '0'으로 응답하면 대체가있을 수 있지만 POST 메서드도 활성화하면 어떻게됩니까? 내일 크레페에 개 알이 있을까 두려울 까?

이러한 시나리오를 처리하는 유일한 방법은 가능한 모든 '파생 리소스'간에 공유되는 모든 하위 리소스를 집계하는 '수퍼 리소스'를 제공 한 다음이를 필요로하는 각 파생 리소스에 대한 전문화를 제공하는 것입니다. 죄송합니다

GET / animals / : animalID / sons GET / hens / : animalID / eggs POST / hens / : animalID / eggs

여기서 단점은 누군가가 개 ID를 전달하여 암탉 수집의 인스턴스를 참조 할 수 있지만 개는 암탉이 아니므로 이유 메시지와 함께 응답이 404 또는 400이면 정확하지 않을 것입니다.

내가 잘못?


1
URI 구조를 너무 강조하고 있다고 생각합니다. "animals / : animalID / eggs"에 접근 할 수있는 유일한 방법은 HATEOAS를 이용하는 것입니다. 따라서 먼저 "animals / : animalID"를 통해 동물을 요청한 다음 알을 가질 수있는 동물의 경우 "animals / : animalID / eggs"에 대한 링크가 있으며 그렇지 않은 경우에는 링크가 없습니다. 동물에서 알로의 연결 고리가됩니다. 누군가가 알을 가질 수없는 동물의 알에 도달하면 적절한 HTTP 상태 코드를 반환합니다 (예를 들어 찾을 수 없거나 금지됨)
wired_in

0

네, 틀 렸습니다. 또한 관계는 예를 들어이 다형성 방식으로 OpenAPI 사양에 따라 모델링 될 수 있습니다.

Chicken:
  type: object
  discriminator:
    propertyName: typeInformation
  allOf:
    - $ref:'#components/schemas/Chicken'
    - type: object
      properties:
        eggs:
          type: array
          items:
            $ref:'#/components/schemas/Egg'
          name:
            type: string

...


추가 의견 : API 경로에 초점을 맞추는 GET chicken/eggs 것도 컨트롤러에 대한 일반적인 OpenAPI 코드 생성기를 사용하여 작동해야하지만 아직 확인하지 않았습니다. 누군가 시도 할 수 있습니까?
Andreas Gaus
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.