Ressources complexes / composites / imbriquées REST [fermé]


177

J'essaie de comprendre la meilleure façon d'aborder les concepts dans une API basée sur REST. Les ressources plates qui ne contiennent pas d'autres ressources ne posent aucun problème. Là où je rencontre des problèmes, ce sont les ressources complexes.

Par exemple, j'ai une ressource pour une bande dessinée. ComicBooka toutes sortes de propriétés sur elle comme author, issue number, date, etc.

Une bande dessinée a également une liste de 1..ncouvertures. Ces couvertures sont des objets complexes. Ils contiennent de nombreuses informations sur la couverture: l'artiste, une date et même une image encodée en base 64 de la couverture.

Pour un GETsur, ComicBookje pourrais simplement retourner la bande dessinée, et toutes les couvertures, y compris leurs images en base64. Ce n'est probablement pas un gros problème pour obtenir une seule bande dessinée. Mais supposons que je crée une application cliente qui souhaite répertorier toutes les bandes dessinées du système dans un tableau.
Le tableau contiendra quelques propriétés de la ComicBookressource, mais nous n'allons certainement pas vouloir afficher toutes les couvertures du tableau. Renvoyer 1000 bandes dessinées, chacune avec plusieurs couvertures, entraînerait une quantité ridiculement grande de données traversant le fil, des données qui ne sont pas nécessaires à l'utilisateur final dans ce cas.

Mon instinct est de créer Coverune ressource et d'avoir ComicBookdes couvertures. Alors maintenant, Coverc'est un URI. GETsur la bande dessinée fonctionne maintenant, au lieu de l'énorme Coverressource, nous renvoyons un URI pour chaque couverture et les clients peuvent récupérer les ressources de couverture à mesure qu'ils en ont besoin.

Maintenant, j'ai un problème avec la création de nouvelles bandes dessinées. Je vais sûrement vouloir créer au moins une couverture lorsque je crée une Comic, en fait c'est probablement une règle commerciale.
Alors maintenant, je suis bloqué, soit je force les clients à appliquer les règles métier en soumettant d'abord un Cover, obtenant l'URI pour cette couverture, puis en POSTentrant un ComicBookavec cet URI dans la liste, ou mon POSTon ComicBookprend une ressource différente de celle qu'elle crache en dehors. Les ressources entrantes pour POSTet GETsont des copies complètes, où les GETs sortants contiennent des références à des ressources dépendantes.

La Coverressource est probablement nécessaire dans tous les cas, car je suis sûr qu'en tant que client, je voudrais aborder la direction des couvertures dans certains cas. Le problème existe donc sous une forme générale quelle que soit la taille de la ressource dépendante. En général, comment gérez-vous des ressources complexes sans forcer le client à simplement «savoir» comment ces ressources sont composées?


l'utilisation de RESTFUL SERVICE DISCOVERY a-t-elle un sens?
treecoder

1
J'essaie d'adhérer à HATEAOS qui, à mon avis, va à l'encontre de l'utilisation de quelque chose comme ça, mais je vais jeter un coup d'œil.
jgerman

Question différente dans le même esprit. Cependant, la propriété est différente de votre solution proposée (celle de la question). stackoverflow.com/questions/20951419/…
Wes

Réponses:


64

@ray, excellente discussion

@jgerman, n'oubliez pas que ce n'est pas parce que c'est REST que les ressources doivent être gravées dans le marbre depuis POST.

Ce que vous choisissez d'inclure dans une représentation donnée d'une ressource dépend de vous.

Votre cas des couvertures référencées séparément est simplement la création d'une ressource parent (bande dessinée) dont les ressources enfants (couvertures) peuvent être référencées. Par exemple, vous souhaiterez peut-être également fournir des références aux auteurs, éditeurs, personnages ou catégories séparément. Vous souhaiterez peut-être créer ces ressources séparément ou avant la bande dessinée qui les référence en tant que ressources enfants. Vous pouvez également souhaiter créer de nouvelles ressources enfants lors de la création de la ressource parent.

Votre cas spécifique des couvertures est légèrement plus complexe dans la mesure où une couverture nécessite vraiment une bande dessinée, et vice versa.

Cependant, si vous considérez un message électronique comme une ressource et l'adresse d'expédition comme une ressource enfant, vous pouvez évidemment toujours référencer l'adresse d'expédition séparément. Par exemple, obtenez tout à partir des adresses. Ou créez un nouveau message avec une ancienne adresse d'expédition. Si le courrier électronique était REST, vous pouviez facilement voir que de nombreuses ressources à références croisées pourraient être disponibles: / reçus-messages, / brouillons-messages, / adresses-d'expéditeur, / adresses-à, / adresses, / sujets, / pièces jointes, / dossiers , / tags, / categories, / labels, et al.

Ce didacticiel fournit un excellent exemple de ressources à références croisées. http://www.peej.co.uk/articles/restfully-delicious.html

Il s'agit du modèle le plus courant pour les données générées automatiquement. Par exemple, vous ne publiez pas d'URI, d'ID ou de date de création pour la nouvelle ressource, car ils sont générés par le serveur. Et pourtant, vous pouvez récupérer l'URI, l'ID ou la date de création lorsque vous récupérez la nouvelle ressource.

Un exemple dans votre cas de données binaires. Par exemple, vous souhaitez publier des données binaires en tant que ressources enfants. Lorsque vous obtenez la ressource parent, vous pouvez représenter ces ressources enfants comme les mêmes données binaires ou comme des URI qui représentent les données binaires.

Les formulaires et paramètres sont déjà différents des représentations HTML des ressources. Publier un paramètre binaire / fichier qui aboutit à une URL n'est pas une extension.

Lorsque vous obtenez le formulaire pour une nouvelle ressource (/ comic-books / new), ou obtenez le formulaire pour modifier une ressource (/ comic-books / 0 / edit), vous demandez une représentation spécifique au formulaire de la ressource. Si vous le publiez dans la collection de ressources avec le type de contenu "application / x-www-form-urlencoded" ou "multipart / form-data", vous demandez au serveur d'enregistrer cette représentation de type. Le serveur peut répondre avec la représentation HTML qui a été enregistrée, ou autre.

Vous pouvez également autoriser la publication d'une représentation HTML, XML ou JSON dans la collection de ressources, à des fins d'API ou similaire.

Il est également possible de représenter vos ressources et votre flux de travail tels que vous les décrivez, en tenant compte des couvertures postées après la bande dessinée, mais nécessitant que les bandes dessinées aient une couverture. Exemple comme suit.

  • Permet la création de couverture différée
  • Permet la création de bandes dessinées avec la couverture requise
  • Permet aux couvertures d'être référencées
  • Permet plusieurs couvertures
  • Créer un brouillon de bande dessinée
  • Créer des brouillons de couvertures de bandes dessinées
  • Publier un brouillon de bande dessinée

GET / comic-books
=> 200 OK, obtenez toutes les bandes dessinées.

GET / comic-books / 0
=> 200 OK, Obtenez une bande dessinée (id: 0) avec des couvertures (/ couvertures / 1, / couvertures / 2).

GET / comic-books / 0 / cover
=> 200 OK, Obtenez des couvertures pour la bande dessinée (id: 0).

GET / couvertures
=> 200 OK, obtenez toutes les couvertures.

GET / couvertures / 1
=> 200 OK, obtenez une couverture (id: 1) avec une bande dessinée (/ comic-books / 0).

GET / comic-books / new
=> 200 OK, obtenez le formulaire pour créer une bande dessinée (formulaire: POST / draft-comic-books).

POST / draft-comic-books
title = foo
author = boo
publisher = goo
published = 2011-01-01
=> 302 Trouvé, Emplacement: / draft-comic-books / 3, Redirection vers le brouillon de bande dessinée (id: 3) avec couvertures (binaire).

GET / draft-comic-books / 3
=> 200 OK, Obtenez un brouillon de bande dessinée (id: 3) avec des couvertures.

GET / draft-comic-books / 3 / cover
=> 200 OK, Obtenez des couvertures pour le projet de bande dessinée (/ draft-comic-book / 3).

GET / draft-comic-books / 3 / cover / new
=> 200 OK, Obtenez le formulaire pour créer une couverture pour le projet de bande dessinée (/ draft-comic-book / 3) (formulaire: POST / draft-comic-books / 3 / couvertures).

POST / draft-comic-books / 3 / covers
cover_type = front
cover_data = (binary)
=> 302 Found, Location: / draft-comic-books / 3 / covers, Redirection vers une nouvelle couverture pour le projet de bande dessinée (/ draft-comic -livre / 3 / couvertures / 1).

GET / draft-comic-books / 3 / publish
=> 200 OK, Obtenez le formulaire pour publier un brouillon de bande dessinée (id: 3) (formulaire: POST / published-comic-books).

POST / published-comic-books
title = foo
author = boo
publisher = goo
published = 2011-01-01
cover_type = front
cover_data = (binary)
=> 302 Trouvé, Emplacement: / comic-books / 3, Redirection vers la bande dessinée publiée (id: 3) avec couvertures.


Je suis totalement novice dans ce domaine, et j'essaye de l'apprendre rapidement. J'ai trouvé cela extrêmement utile. Cependant, dans les autres blogs, etc. que j'ai lus aujourd'hui, l'utilisation de GET pour effectuer une opération (en particulier une opération qui n'est pas idempotente) serait désapprouvée. Alors ne devrait-il pas être POST / draft-comic-books / 3 / publier?
Gary McGill

3
@GaryMcGill Dans son exemple, / draft-comic-books / 3 / publish ne renvoie qu'un formulaire HTML (ne modifie aucune donnée).
Olivier Lalonde

@Olivier a raison. Le mot publier est là pour désigner ce que fait le formulaire. Cependant, comme vous souhaitez conserver les verbes confinés aux méthodes HTTP, vous devez publier sur une ressource pour les bandes dessinées publiées. ... S'il s'agissait d'un site Web, vous pourriez avoir besoin d'un URI pour que le formulaire publie quelque chose. ... Bien que, si l'action de publication n'était qu'un simple bouton sur la page de la bande dessinée, ce formulaire à un seul bouton pouvait publier directement dans l'URI / published-comic-books.
Alex

@Alex, dans la demande POST, je retournerais plutôt un 201 Created, avec l'URL de la nouvelle ressource en tant que Location dans les en-têtes de réponse.
ismriv

2
@Stephane, les redirections simplifient tout simplement pour les contrôleurs. Même pour une API, il est plus simple de demander au contrôleur de création de renvoyer l'emplacement du nouveau contenu, puis de laisser le contrôleur d'exposition gérer l'affichage du nouveau contenu. Cependant, il est plus agréable / plus simple pour le client de l'API d'obtenir simplement le contenu et de ne pas se soucier des redirections.
Alex le

45

Traiter les couvertures comme des ressources est définitivement dans l'esprit de REST, en particulier HATEOAS. Alors oui, une GETdemande de http://example.com/comic-books/1vous donnerait une représentation du livre 1, avec des propriétés comprenant un ensemble d'URI pour les couvertures. Jusqu'ici tout va bien.

Votre question est de savoir comment gérer la création de bandes dessinées. Si votre règle commerciale était qu'un livre aurait 0 couverture ou plus , vous n'avez aucun problème:

POST http://example.com/comic-books

avec des données de bande dessinée sans couverture créera une nouvelle bande dessinée et renverra l'identifiant généré par le serveur (disons qu'il revient en tant que 8), et maintenant vous pouvez y ajouter des couvertures comme ceci:

POST http://example.com/comic-books/8/covers

avec le couvercle dans le corps de l'entité.

Vous avez maintenant une bonne question: ce qui se passe si votre règle commerciale stipule qu'il doit toujours y avoir au moins une couverture. Voici quelques choix, dont le premier que vous avez identifié dans votre question:

  1. Forcez d'abord la création d'une couverture, en faisant maintenant essentiellement la couverture une ressource non dépendante, ou vous placez la couverture initiale dans le corps de l'entité du POST qui crée la bande dessinée. Comme vous le dites, cela signifie que la représentation que vous POSTER pour créer sera différente de la représentation que vous OBTENEZ.

  2. Définissez la notion de couverture principale, initiale, préférée ou autrement désignée. Il s'agit probablement d'un piratage de modélisation, et si vous le faisiez, ce serait comme peaufiner votre modèle d'objet (votre modèle conceptuel ou commercial) afin de l'adapter à une technologie. Pas une bonne idée.

Vous devez peser ces deux choix contre le simple fait d'autoriser les bandes dessinées sans couverture.

Lequel des trois choix devriez-vous faire? Ne sachant pas trop sur votre situation, mais répondez à la question générale des ressources dépendantes 1..N, je dirais:

  • Si vous pouvez utiliser 0..N pour votre couche de service RESTful, tant mieux. Peut-être qu'une couche entre votre SOA RESTful peut gérer la contrainte métier supplémentaire si au moins une est requise. (Je ne sais pas à quoi cela ressemblerait, mais cela pourrait valoir la peine d'être exploré ... les utilisateurs finaux ne voient généralement pas la SOA de toute façon.)

  • Si vous devez simplement modéliser une contrainte 1..N, alors demandez-vous si les couvertures peuvent simplement être des ressources partageables, en d'autres termes, elles peuvent exister sur des choses autres que les bandes dessinées. Maintenant, ce ne sont pas des ressources dépendantes et vous pouvez les créer d'abord et fournir des URI dans votre POST qui crée des bandes dessinées.

  • Si vous avez besoin de 1..N et que les couvertures restent dépendantes, relâchez simplement votre instinct pour conserver les mêmes représentations dans POST et GET, ou faites-les de la même manière.

Le dernier élément est expliqué comme suit:

<comic-book>
  <name>...</name>
  <edition>...</edition>
  <cover-image>...BASE64...</cover-image>
  <cover-image>...BASE64...</cover-image>
  <cover>...URI...</cover>
  <cover>...URI...</cover>
</comic-book>

Lorsque vous POSTEZ, vous autorisez les uris existants si vous les avez (empruntés à d'autres livres) mais vous insérez également une ou plusieurs images initiales. Si vous créez un livre et que votre entité n'a pas d'image de couverture initiale, renvoyez une réponse 409 ou similaire. Sur GET, vous pouvez renvoyer des URI.

Donc, fondamentalement, vous autorisez les représentations POST et GET à "être identiques", mais vous choisissez simplement de ne pas "utiliser" l'image de couverture sur GET ni de couverture sur POST. J'espère que cela a du sens.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.