web-dev-qa-db-fra.com

Quelles sont les meilleures pratiques pour REST ressources imbriquées?

Autant que je sache, chaque ressource individuelle devrait avoir n seul canonique chemin. Ainsi, dans l'exemple suivant, quels seraient les bons modèles d'URL?

Prenons pour exemple une représentation des entreprises au repos. Dans cet exemple hypothétique, chaque société possède 0 ou plusieurs départements et chaque département possède 0 ou plusieurs employés.

Un département ne peut pas exister sans une entreprise associée.

Un employé ne peut pas exister sans département associé.

Maintenant, je trouverais la représentation naturelle des schémas de ressources.

  • /companies Ensemble de sociétés - Accepte une vente pour une nouvelle société. Obtenez pour la collection entière.
  • /companies/{companyId} Une entreprise individuelle. Accepte GET, PUT et DELETE
  • /companies/{companyId}/departments Accepte POST pour un nouvel élément. (Crée un département au sein de l'entreprise.)
  • /companies/{companyId}/departments/{departmentId}/
  • /companies/{companyId}/departments/{departmentId}/employees
  • /companies/{companyId}/departments/{departmentId}/employees/{empId}

Compte tenu des contraintes, dans chacune des sections, j’estime que cela a du sens si elle est un peu imbriquée.

Cependant, ma difficulté vient si je veux lister (GET) tous les employés de toutes les entreprises.

Le modèle de ressources correspondant à ce serait plus proche de /employees (La collection de tous les employés)

Est-ce que cela signifie que je devrais avoir /employees/{empId} également parce que si c'est le cas, il y a deux adresses URI pour obtenir la même ressource?

Ou peut-être que tout le schéma devrait être aplati, mais cela signifierait que les employés sont un objet de niveau supérieur imbriqué.

Au niveau de base, /employees/?company={companyId}&department={deptId} renvoie la même vue des employés que le modèle le plus profondément imbriqué.

Quelle est la meilleure pratique pour les modèles d'URL où les ressources sont possédées par d'autres ressources mais doivent pouvoir être interrogées séparément?


PDATE: Voir ma réponse ci-dessous pour voir ce que j'ai fait.

261
Wes

Ce que vous avez fait est correct. En général, la même ressource peut contenir plusieurs adresses URI. Aucune règle ne vous interdit de le faire.

Et en règle générale, vous devrez peut-être accéder à des éléments directement ou en tant que sous-ensemble de quelque chose d'autre. Votre structure est donc logique pour moi.

Juste parce que les employés sont accessibles sous un ministère:

company/{companyid}/department/{departmentid}/employees

Cela ne veut pas dire qu'ils ne peuvent pas être accessibles sous la même entreprise:

company/{companyid}/employees

Ce qui retournerait des employés pour cette entreprise. Cela dépend de ce dont votre client consommateur a besoin - c’est ce que vous devriez concevoir.

Mais j'espère que tous les gestionnaires d'URL utilisent le même code de sauvegarde pour satisfaire les demandes afin que vous ne dupliquiez pas le code.

134
jeremyh

J'ai essayé les deux stratégies de conception - noeuds finaux imbriqués et non imbriqués. J'ai trouvé ça:

  1. si la ressource imbriquée a une clé primaire et que vous n'avez pas sa clé primaire parent, la structure imbriquée vous oblige à l'obtenir, même si le système ne l'exige pas réellement.

  2. les points de terminaison imbriqués nécessitent généralement des points de terminaison redondants. En d'autres termes, vous aurez le plus souvent besoin du point de terminaison/employés supplémentaire afin de pouvoir obtenir une liste des employés de tous les départements. Si vous avez/employés, qu'est-ce que/sociétés/départements/employés vous achètent exactement?

  3. les points d'extrémité d'imbrication n'évoluent pas aussi bien. Par exemple. vous n'aurez peut-être pas besoin de rechercher des employés maintenant, mais plus tard, et si vous avez une structure imbriquée, vous n'avez pas d'autre choix que d'ajouter un autre point de terminaison. Avec une conception non imbriquée, vous ajoutez simplement plus de paramètres, ce qui est plus simple.

  4. parfois, une ressource peut avoir plusieurs types de parents. Il en résulte que plusieurs systèmes d'extrémité renvoient tous la même ressource.

  5. les noeuds finaux redondants rendent les documents plus difficiles à écrire et à apprendre à utiliser l’API.

En résumé, la conception non imbriquée semble permettre un schéma de point final plus souple et plus simple.

147
Patc

J'ai déplacé ce que j'ai fait de la question à une réponse où davantage de personnes sont susceptibles de la voir.

Ce que j’ai fait est d’avoir les points de terminaison de création au point de terminaison imbriqué. Le point de terminaison canonique pour la modification ou l’interrogation d’un élément est pas à la ressource imbriquée.

Donc, dans cet exemple (énumérant simplement les points finaux qui changent une ressource)

  • POST/companies/ crée une nouvelle société renvoie un lien vers la société créée.
  • POST/companies/{companyId}/departments lorsqu'un département est créé, le nouveau département renvoie un lien vers /departments/{departmentId}
  • PUT/departments/{departmentId} modifie un département
  • POST/departments/{deparmentId}/employees crée un nouvel employé renvoie un lien vers /employees/{employeeId}

Il existe donc des ressources de niveau racine pour chacune des collections. Cependant, le créer est dans l'objet posséder.

70
Wes

J'ai lu toutes les réponses ci-dessus mais je n'ai pas de stratégie commune. J'ai trouvé un bon article sur Meilleures pratiques pour les API de conception de Microsoft Documents . Je pense que vous devriez vous référer.

Dans des systèmes plus complexes, il peut être tentant de fournir des adresses URI permettant à un client de naviguer à travers plusieurs niveaux de relations, tels que /customers/1/orders/99/products. Cependant, ce niveau de complexité peut être difficile à maintenir et inflexible si les relations entre ressources changer à l'avenir. Au lieu de cela, essayez de garder les URI relativement simples . Une fois qu'une application a une référence à une ressource, il devrait être possible d'utiliser cette référence pour rechercher des éléments liés à cette ressource. La requête précédente peut être remplacée par l'URI /customers/1/orders pour rechercher toutes les commandes du client 1, puis /orders/99/products pour rechercher les produits de cette commande.

.

Conseil

Évitez d’exiger des adresses URI de ressources plus complexes que collection/item/collection.

20
Long Nguyen

L’apparence de vos URL n’a rien à voir avec REST. Tout va. C'est en fait un "détail d'implémentation". Tellement juste comment vous nommez vos variables. Tout ce qu'ils doivent être est unique et durable.

Ne perdez pas trop de temps là-dessus, faites simplement un choix et respectez-le/soyez cohérent. Par exemple, si vous utilisez des hiérarchies, vous le faites pour toutes vos ressources. Si vous utilisez des paramètres de requête, etc., tout comme les conventions de dénomination dans votre code.

Pourquoi Pour autant que je sache, une API "RESTful" doit être consultable (vous savez ... "Hypermedia en tant que moteur de l'état de l'application"), par conséquent, un client d'API ne se soucie pas de la nature de vos URL tant qu'elles sont valide (il n'y a pas de référencement, pas d'humain qui a besoin de lire ces "urls amicales", sauf peut-être pour le débogage ...)

Comment agréable/compréhensible une URL est dans un REST API est seulement intéressant pour vous en tant que développeur API, pas le client de l'API, tout comme le nom d'une variable dans votre code soit.

Le plus important est que votre client API sache interpréter votre type de média. Par exemple, il sait que:

  • votre type de média a une propriété de liens qui répertorie les liens disponibles/connexes.
  • Chaque lien est identifié par une relation (tout comme les navigateurs savent que link [rel = "stylesheet"] signifie qu'il s'agit d'une feuille de style ou rel = favico est un lien vers un favicon ...)
  • et il sait ce que signifient ces relations ("sociétés" désigne une liste de sociétés, "recherche" désigne une URL modèle permettant d'effectuer une recherche sur une liste de ressources, "départements" désigne les départements de la ressource actuelle).

Vous trouverez ci-dessous un exemple d'échange HTTP (les corps sont en yaml puisqu'il est plus facile d'écrire):

Requête

GET / HTTP/1.1
Host: api.acme.io
Accept: text/yaml, text/acme-mediatype+yaml

Réponse: une liste de liens vers les ressources principales (entreprises, personnes, peu importe ...)

HTTP/1.1 200 OK
Date: Tue, 05 Apr 2016 15:04:00 GMT
Last-Modified: Tue, 05 Apr 2016 00:00:00 GMT
Content-Type: text/acme-mediatype+yaml

# body: this is your API's entrypoint (like a homepage)  
links:
  # could be some random path https://api.acme.local/modskmklmkdsml
  # the only thing the API client cares about is the key (or rel) "companies"
  companies: https://api.acme.local/companies
  people: https://api.acme.local/people

Requête: lien vers des entreprises (en utilisant body.links.companies de la réponse précédente)

GET /companies HTTP/1.1
Host: api.acme.local
Accept: text/yaml, text/acme-mediatype+yaml

Réponse: une liste partielle des sociétés (sous éléments), la ressource contient des liens connexes, tels que des liens pour obtenir les deux sociétés suivantes (body.links. next) un autre lien (basé sur un modèle) vers la recherche (body.links.search)

HTTP/1.1 200 OK
Date: Tue, 05 Apr 2016 15:06:00 GMT
Last-Modified: Tue, 05 Apr 2016 00:00:00 GMT
Content-Type: text/acme-mediatype+yaml

# body: representation of a list of companies
links:
  # link to the next page
  next: https://api.acme.local/companies?page=2
  # templated link for search
  search: https://api.acme.local/companies?query={query} 
# you could provide available actions related to this resource
actions:
  add:
    href: https://api.acme.local/companies
    method: POST
items:
  - name: company1
    links:
      self: https://api.acme.local/companies/8er13eo
      # and here is the link to departments
      # again the client only cares about the key department
      department: https://api.acme.local/companies/8er13eo/departments
  - name: company2
    links:
      self: https://api.acme.local/companies/9r13d4l
      # or could be in some other location ! 
      department: https://api2.acme.local/departments?company=8er13eo

Donc, comme vous voyez si vous allez dans les liens/relations, la façon dont vous structurez la partie chemin de vos URL n'a aucune valeur pour votre client API. Et si vous communiquez la structure de vos URL à votre client sous forme de documentation, vous ne faites pas REST (ou du moins pas le niveau 3 selon " modèle de maturité de Richardson ")

10
redben

Je suis en désaccord avec ce genre de chemin

GET /companies/{companyId}/departments

Si vous souhaitez obtenir des ministères, je pense qu'il est préférable d'utiliser une ressource/départements

GET /departments?companyId=123

Je suppose que vous avez une table companies et une table departments puis les classes pour les mapper dans le langage de programmation que vous utilisez. Je suppose également que les départements peuvent être rattachés à des entités autres que des sociétés. Une ressource/départements est donc simple, il est pratique de mapper des ressources sur des tables et vous n'avez pas besoin d'autant de points de terminaison, car vous pouvez les réutiliser.

GET /departments?companyId=123

pour tout type de recherche, par exemple

GET /departments?name=xxx
GET /departments?companyId=123&name=xxx
etc.

Si vous voulez créer un département, le

POST /departments

la ressource doit être utilisée et le corps de la demande doit contenir l'ID de l'entreprise (si le service ne peut être lié qu'à une seule entreprise).

8
Maxime Laval

Rails fournit une solution à ceci: imbrication peu profonde .

Je pense que c'est un avantage, car lorsque vous traitez directement avec une ressource connue, vous n'avez pas besoin d'utiliser des routes imbriquées, comme cela a été discuté dans d'autres réponses ici.

1
partydrone