web-dev-qa-db-fra.com

REST Ressources complexes / composites / imbriquées

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ù j'ai des problèmes sont les ressources complexes.

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

Une bande dessinée contient également une liste de 1..n couvre. Ces couvertures sont des objets complexes. Ils contiennent beaucoup d'informations sur la couverture: l'artiste, une date et même une image codée en base 64 de la couverture.

Pour un GET sur ComicBook, je pourrais simplement renvoyer 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 construise une application cliente qui souhaite répertorier toutes les bandes dessinées du système dans un tableau.
La table contiendra quelques propriétés de la ressource ComicBook, mais nous n'allons certainement pas vouloir afficher toutes les couvertures de la table. Le retour de 1000 bandes dessinées, chacune avec plusieurs couvertures, produirait une quantité ridiculement grande de données sur le réseau, données qui ne sont pas nécessaires pour l'utilisateur final dans ce cas.

Mon instinct est de faire de Cover une ressource et d'avoir ComicBook des couvertures. Alors maintenant, Cover est un URI. GET sur une bande dessinée fonctionne maintenant, au lieu de l'énorme ressource Cover, nous renvoyons un URI pour chaque couverture et les clients peuvent récupérer les ressources de la couverture à leur guise.

Maintenant, j'ai un problème avec la création de nouvelles BD. Je vais sûrement vouloir créer au moins une couverture lorsque je crée un Comic, en fait c'est probablement une règle de gestion.
Alors, maintenant que je suis coincé, je force les clients à appliquer des règles métier en soumettant d’abord un Cover, en obtenant l’URI de cette couverture, puis POSTing un ComicBook avec cet URI dans la liste, ou mon POST sur ComicBook récupère une ressource d'aspect différent de celle qu'elle génère. Les ressources entrantes pour POST et GET sont des copies complètes, les GETs sortantes contenant des références à des ressources dépendantes.

La ressource Cover est probablement nécessaire dans tous les cas, car je suis sûr que, en tant que client, je voudrais aborder la question de la direction 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érer des ressources complexes sans obliger le client à simplement "savoir" comment ces ressources sont composées?

168
jgerman

@ray, excellente discussion

@jgerman, n'oubliez pas que ce n'est pas parce que c'est REST, que les ressources doivent être figées dans la pierre de 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 parente (bande dessinée) dont les ressources enfants (couvertures) peuvent être référencées. Par exemple, vous pouvez également souhaiter fournir des références aux auteurs, aux éditeurs, aux personnages ou aux 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 créer de nouvelles ressources enfants lors de la création de la ressource parent.

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

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

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

C'est le 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 ceux-ci 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 sous la forme des mêmes données binaires ou sous la forme d'URI représentant les données binaires.

Les formulaires et les 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 un effort.

Lorsque vous obtenez le formulaire pour une nouvelle ressource (/ comic-books/new) ou le formulaire pour modifier une ressource (/ comic-books/0/edit), vous demandez une représentation de la ressource spécifique aux formulaires. Si vous l'envoyez dans la collection de ressources avec le type de contenu "application/x-www-form-urlencoded" ou "multipart/form-data", vous demandez au serveur de sauvegarder 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 déclaration 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 comme vous le décrivez, en tenant compte des couvertures postérieures à la bande dessinée, mais en exigeant que les bandes dessinées aient une couverture. Exemple comme suit.

  • Permet la création de couverture retardé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 couvertures de bande dessinée
  • Publier un projet de bande dessinée

GET/BD
=> 200 OK, récupérez toutes les bandes dessinées.

GET/livres de bandes dessinées/0
=> 200 OK, Obtenez une bande dessinée (id: 0) avec couvertures (/ covers/1,/covers/2).

GET/livres de bandes dessinées/0/couvertures
=> 200 OK, Obtenez des couvertures pour une bande dessinée (id: 0).

GET/couvre
=> 200 OK, Obtenez toutes les couvertures.

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

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

POST/comics-books
title = foo
auteur = boo
éditeur = goo
publié = 2011-01-01
=> 302 trouvés, Emplacement:/draft-comic-books/3, Redirection vers un brouillon (id: 3) avec couvertures (binaire).

GET/draft-comic-books/3
=> 200 OK, obtenez un brouillon (id: 3) avec des couvertures.

GET/draft-comic-books/3/covers
=> 200 OK, Obtenez les couvertures pour le brouillon (/ draft-comic-book/3).

GET/draft-comic-books/3/covers/nouveau
=> 200 OK, Obtenez un formulaire pour créer une couverture pour un brouillon (/ draft-comic-book/3) (forme: POST/draft-comic-books/3/couvre).

POST/comics-books/3/couvertures
cover_type = front
cover_data = (binaire)
=> 302 trouvés, Emplacement:/draft-comic-books/3/covers, Rediriger vers une nouvelle couverture pour le brouillon (/ draft-comic-book/3/covers/1).

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

POST/BD publiés
title = foo
auteur = boo
éditeur = goo
publié = 2011-01-01
cover_type = front
cover_data = (binaire)
=> 302 trouvés, Emplacement:/comic-books/3, Redirection vers une bande dessinée publiée (id: 3) avec couvertures.

62
Alex

Traiter les couvertures comme des ressources est clairement dans l’esprit de REST, en particulier HATEOAS. Alors oui, une requête GET à http://example.com/comic-books/1 vous 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 d'affaires était qu'un livre aurait ou plus, alors vous n'avez aucun problème:

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

avec les données de bande dessinée sans couverture créera une nouvelle bande dessinée et retournera l'id généré par le serveur (disons qu'il revient sous la forme de 8), et vous pouvez maintenant y ajouter des couvertures comme ceci:

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

avec la couverture dans le corps de l'entité.

Maintenant, vous avez une bonne question: que se passera-t-il si votre règle d'entreprise stipule qu'il doit toujours y avoir au moins une couverture? Voici quelques choix, dont vous avez identifié le premier dans votre question:

  1. Commencez par forcer la création d’une couverture en faisant maintenant de la couverture une ressource non dépendante, ou placez la couverture initiale dans le corps de l’entité du POST qui crée la bande dessinée. Ceci, comme vous le dites signifie que la représentation que vous POST créer sera différente de la représentation que vous obtenez).

  2. Définissez la notion de couverture primaire, initiale, privilégiée ou autrement désignée. Il s'agit probablement d'un bidouillage de modélisation. Si vous agissiez de la sorte, ce serait un peu comme peaufiner votre modèle d'objet (votre modèle conceptuel ou votre modèle d'entreprise) afin de l'adapter à une technologie. Pas une bonne idée.

Vous devriez peser ces deux choix par rapport à l’autorisation des bandes dessinées sans couverture.

Lequel des trois choix devriez-vous prendre? 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 RESTful SOA peut gérer la contrainte métier supplémentaire si au moins une est requise. (Vous ne savez pas à quoi cela pourrait ressembler, mais cela pourrait valoir la peine d'être examiné ... les utilisateurs finaux ne le font pas. t généralement voir le SOA quand même.)

  • Si vous devez simplement modéliser une contrainte 1..N, demandez-vous si les couvertures peuvent être simplement des ressources partageables, autrement dit, elles pourraient exister pour des éléments 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 représentations dans POST et GET identiques) ou associez-les à la même chose.

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 POST 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 de couverture initiale, image, renvoyer une réponse 409 ou similaire.Sous GET, vous pouvez renvoyer des URI.

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

43
Ray Toal