Как смоделировать 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 ответов:
я предлагаю:
- используя только один 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 с сообщением причины
я ошибаюсь?