J'ai une hiérarchie d'objets que je dois exposer via une API RESTful et je ne sais pas comment mes URL doivent être structurées et ce qu'elles doivent renvoyer. Je n'ai trouvé aucune meilleure pratique.
Disons que j'ai des chiens et des chats hérités des animaux. J'ai besoin d'opérations CRUD sur les chiens et les chats; Je veux aussi pouvoir faire des opérations sur les animaux en général.
Ma première idée a été de faire quelque chose comme ça:
GET /animals # get all animals
POST /animals # create a dog or cat
GET /animals/123 # get animal 123
Le fait est que la collection/animals est maintenant "incohérente", car elle peut revenir et prendre des objets qui n'ont pas exactement la même structure (chiens et chats). Est-il considéré comme "RESTful" d'avoir une collection renvoyant des objets qui ont des attributs différents?
Une autre solution serait de créer une URL pour chaque type concret, comme ceci:
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
Mais maintenant, la relation entre chiens et chats est perdue. Si l'on souhaite récupérer tous les animaux, les ressources du chien et du chat doivent être interrogées. Le nombre d'URL augmentera également avec chaque nouveau sous-type d'animal.
Une autre suggestion était d'augmenter la deuxième solution en ajoutant ceci:
GET /animals # get common attributes of all animals
Dans ce cas, les animaux retournés ne contiendraient que des attributs communs à tous les animaux, supprimant les attributs spécifiques au chien et au chat. Cela permet de récupérer tous les animaux, mais avec moins de détails. Chaque objet retourné peut contenir un lien vers la version détaillée et concrète.
Des commentaires ou suggestions?
Je voudrais suggerer:
La configuration de plusieurs URI sur la même ressource n'est jamais une bonne idée car elle peut provoquer de la confusion et des effets secondaires inattendus. Étant donné que votre URI unique doit être basé sur un schéma générique comme /animals
.
Le prochain défi de traiter l'ensemble de la collection de chiens et de chats au niveau "de base" est déjà résolu grâce au /animals
Approche URI.
Le dernier défi de traiter avec des types spécialisés comme les chiens et les chats peut être facilement résolu en utilisant une combinaison de paramètres de requête et d'attributs d'identification dans votre type de support. Par exemple:
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
- obtient tous les chiens et chats, renverrait Rex et mitainesGET /animals?type=dog
- obtient tous les chiens, ne retournerait que RexGET /animals?type=cat
- obtient tous les chats, serait seulement mitainesEnsuite, lors de la création ou de la modification des animaux, il incomberait à l'appelant de préciser le type d'animal concerné:
Type de support: application/vnd.vet-services.animal+json
{
"type":"dog",
"name":"Fido"
}
La charge utile ci-dessus peut être envoyée avec une demande POST
ou PUT
.
Le schéma ci-dessus vous offre les caractéristiques de base similaires à OO héritage via REST, et avec la possibilité d'ajouter d'autres spécialisations (c'est-à-dire plus de types d'animaux) sans intervention chirurgicale majeure ni modification de votre schéma d'URI.
J'irais pour/animaux renvoyant une liste de chiens et de poissons et quoi que ce soit d'autre:
<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>
Il devrait être facile d'implémenter un exemple JSON similaire.
Les clients peuvent toujours compter sur l'élément "nom" (un attribut commun). Mais en fonction de l'attribut "type", il y aura d'autres éléments dans le cadre de la représentation animale.
Il n'y a rien de intrinsèquement RESTful ou unRESTful dans le renvoi d'une telle liste - REST ne prescrit aucun format spécifique pour représenter les données. Tout ce qu'il dit, c'est que les données doivent avoir une représentation some et le format de cette représentation est identifié par le type de support (qui en HTTP est l'en-tête Content-Type).
Pensez à vos cas d'utilisation - devez-vous montrer une liste d'animaux mixtes? Eh bien, retournez ensuite une liste de données animales mixtes. Avez-vous besoin d'une liste de chiens uniquement? Eh bien, faites une telle liste.
Que vous fassiez/animals? Type = dog ou/dogs n'a pas d'importance en ce qui concerne REST qui ne prescrit aucun format d'URL - qui est laissé comme détail d'implémentation en dehors de la portée de REST. REST indique seulement que les ressources doivent avoir des identifiants - peu importe le format.
Vous devez ajouter un lien hyper média pour vous rapprocher d'une API RESTful. Par exemple, en ajoutant des références aux détails de l'animal:
<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>
En ajoutant des liens hyper média, vous réduisez le couplage client/serveur - dans le cas ci-dessus, vous éloignez le client de la construction d'URL et laissez le serveur décider comment construire des URL (dont il est par définition la seule autorité).
Cette question peut être mieux résolue grâce à la prise en charge d'une récente amélioration introduite dans la dernière version d'OpenAPI.
Il a été possible de combiner des schémas à l'aide de mots clés tels que oneOf, allOf, anyOf et d'obtenir une charge utile de message validée depuis le schéma JSON v1.0.
https://spacetelescope.github.io/understanding-json-schema/reference/combining.html
Cependant, dans OpenAPI (ancien Swagger), la composition des schémas a été améliorée par le discriminateur de mots clés (v2.0 +) et oneOf (v3.0 +) pour vraiment prendre en charge le polymorphisme.
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaComposition
Votre héritage peut être modélisé à l'aide d'une combinaison de oneOf (pour choisir l'un des sous-types) et allOf (pour combiner le type et l'un de ses sous-types). Voici un exemple de définition de la méthode 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
Je sais que c'est une vieille question, mais je suis intéressé à étudier d'autres problèmes sur une modélisation d'héritage RESTful
Je peux toujours dire qu'un chien est un animal et une poule aussi, mais la poule fait des œufs alors que le chien est un mammifère, donc ça ne peut pas. Une API comme
GET animaux /: animalID/œufs
n'est pas cohérent car indique que tous les sous-types d'animaux peuvent avoir des œufs (en conséquence de la substitution de Liskov). Il y aurait un repli si tous les mammifères répondent avec '0' à cette demande, mais que se passe-t-il si j'active également une méthode POST? Dois-je avoir peur que demain il y ait des œufs de chien dans mes crêpes ?
La seule façon de gérer ces scénarios est de fournir une "super-ressource" qui regroupe toutes les sous-ressources partagées entre toutes les "ressources dérivées" possibles, puis une spécialisation pour chaque ressource dérivée qui en a besoin, tout comme lorsque nous abattons un objet dans oop
GET/animaux /: animalID/fils GET/poules /: animalID/œufs POST/poules /: animalID/œufs
L'inconvénient, ici, est que quelqu'un pourrait passer un identifiant de chien pour référencer une instance de collecte de poules, mais le chien n'est pas une poule, donc ce ne serait pas incorrect si la réponse était 404 ou 400 avec un message de raison
Ai-je tort?
Mais maintenant, la relation entre chiens et chats est perdue.
En effet, mais gardez à l'esprit que l'URI ne reflète tout simplement jamais les relations entre les objets.