Как смоделировать RESTful API с наследованием?


у меня есть иерархия объектов, которую мне нужно выставить через RESTful API, и я не уверен, как мои URL-адреса должны быть структурированы и что они должны возвращать. Я не мог найти никаких лучших практик.

допустим, у меня есть собаки и кошки, унаследованные от животных. Мне нужны грязные операции на собаках и кошках; я также хочу иметь возможность делать операции на животных в целом.

моя первая идея была сделать что-то вроде этого:

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

дело в том, что /животные коллекция теперь "непоследовательна", так как она может возвращать и принимать объекты, которые не имеют точно такой же структуры (собаки и кошки). Считается ли "RESTful" иметь коллекцию, возвращающую объекты, которые имеют разные атрибуты?

другим решением было бы создать 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

в этом случае возвращенные животные будут содержать только атрибуты, общие для всех животных, отбрасывая атрибуты, характерные для собак и кошек. Это позволяет извлекать всех животных, хотя и с меньшим количеством деталей. Каждый возвращаемый объект может содержать ссылку на детальную, конкретную версию.

какие-либо замечания или предложения?

5 73

5 ответов:

я предлагаю:

  • используя только один URI на ресурс
  • дифференциация между животными исключительно на уровне атрибутов

настройка нескольких URI для одного и того же ресурса никогда не является хорошей идеей, потому что это может вызвать путаницу и неожиданные побочные эффекты. Учитывая это, ваш единственный URI должен быть основан на общей схеме, такой как /animals.

следующая задача борьбы со всей коллекцией собак и кошек на "базовый" уровень уже решен в силу /animals подход URI.

окончательная проблема работы со специализированными типами, такими как собаки и кошки, может быть легко решена с помощью комбинации параметров запроса и идентификационных атрибутов в вашем типе носителя. Например:

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 - заводит всех собак и кошек, вернет и Рекса и варежки
  • GET /animals?type=dog - заводит всех собак, бы только Верните Рекса
  • GET /animals?type=cat - заводит всех кошек, бы только варежки

затем при создании или изменении животных вызывающий должен указать тип вовлеченного животного:

Тип Носителя: application/vnd.vet-services.animal+json

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

вышеуказанная полезная нагрузка может быть отправлена с POST или PUT запрос.

приведенная выше схема дает вам основные аналогичные характеристики, такие как наследование OO через REST, и с возможностью добавления дальнейшие специализации (т. е. больше типов животных) без серьезной операции или каких-либо изменений в вашей схеме URI.

Я бы пошел на / животных, возвращающих список как собак, так и рыб, и что еще:

<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.

клиенты всегда могут полагаться на наличие элемента" name " (общий атрибут). Но в зависимости от атрибута" тип " будут и другие элементы как часть представления животного.

нет ничего по своей сути спокойного или беспокойного в возвращении такого списка-REST не предписывает любой конкретный формат представления данных. Все это говорит о том, что данные должны иметь некоторые представление и формат для этого представления определяется типом носителя (который в HTTP является заголовком типа контента).

подумайте о ваших случаях использования - вам нужно показать список смешанных животных? Ну, тогда верните список смешанных данных о животных. Вам нужен только список собак? Ну, составьте такой список.

есть ли у вас / животные?тип=собака или собаки не имеет значения что касается REST, который не предписывает никаких форматов URL - это оставлено в качестве детали реализации за пределами области REST. REST только утверждает, что ресурсы должны иметь идентификаторы - неважно, какой формат.

вы должны добавить некоторые гиперссылки СМИ, чтобы приблизиться к RESTful API. Например, добавив ссылки на детали животного:

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

добавляя гиперссылку на носитель, вы уменьшаете связь между клиентом и сервером - в приведенном выше случае вы берете на себя бремя URL построение вдали от клиента и пусть сервер решает, как построить URL-адреса (которые он по определению является единственным авторитетом).

на этот вопрос можно лучше ответить при поддержке недавнего улучшения, представленного в последней версии OpenAPI.

было возможно объединить схемы с использованием ключевых слов, таких как oneOf, allOf, anyOf и получить полезную нагрузку сообщения, проверенную с JSON schema 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 (для объединения типа и одного из его подтипов). Ниже приведен пример определения для поста метод.

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

но теперь отношения между собаками и кошками теряется.

действительно, но имейте в виду, что URI просто никогда не отражает отношения между объектами.

Я знаю, что это старый вопрос, но мне интересно исследовать дальнейшие вопросы по моделированию наследования RESTful

Я всегда могу сказать, что собака-это животное и курица тоже, но курица делает яйца, а собака-млекопитающее, поэтому она не может. API, как

получить животных/: animalID / яйца

не является последовательным, поскольку указывает, что все подтипы животных могут иметь яйца (как следствие замены Лисков). Был бы запасной вариант, если бы все млекопитающие ответили с '0' на этот запрос, но что делать, если я также включить метод POST? Должен ли я бояться, что завтра в моих блинах будут собачьи яйца?

единственный способ справиться с этими сценариями-предоставить "суперресурс", который объединяет все подресурсы, разделяемые между всеми возможными "производными ресурсами", а затем специализацию для каждого производного ресурса, который нуждается в нем, точно так же, как когда мы опускаем объект в ООП

GET / animals/: animalID / sons GET / hens/: animalID / яйца ДОЛЖНОСТЬ / куры/: animalID / яйца

недостатком здесь является то, что кто-то может передать идентификатор собаки для ссылки на экземпляр коллекции hens, но собака не является курицей, поэтому было бы правильно, если бы ответ был 404 или 400 с сообщением причины

я ошибаюсь?