web-dev-qa-db-fra.com

Conception RESTful: quand utiliser des sous-ressources?

Quand faut-il concevoir des hiérarchies de ressources, quand faut-il utiliser des sous-ressources?

J'avais l'habitude de croire que lorsqu'une ressource ne peut exister sans une autre, elle doit être représentée comme une sous-ressource. J'ai récemment rencontré ce contre-exemple:

  • Un employé est identifiable de manière unique dans toutes les entreprises.
  • Le contrôle d'accès et le cycle de vie d'un employé dépendent de l'entreprise.

J'ai modélisé cela comme suit: /companies/{companyName}/employee/{employeeId}

Remarquez, je n'ai pas besoin de rechercher l'entreprise pour localiser l'employé, alors devrais-je? Si je le fais, je paie un prix pour rechercher des informations dont je n’ai pas besoin. Si je ne le fais pas, cette URL renvoie par erreur HTTP 200:

/companies/{nonExistingName}/employee/{existingId}

  1. Comment est-ce que je devrais représenter le fait qu'une ressource à appartient à une autre?
  2. Comment devrais-je représenter le fait qu'une ressource ne peut pas être identifiée sans une autre?
  3. Quelles sont les relations entre les sous-ressources et non sur les modèles?
50
Gili

Un an plus tard, j'ai conclu avec le compromis suivant (pour les lignes de base de données contenant un identificateur unique):

  1. Attribuez à toutes les ressources un URI canonique à la racine (par exemple, /companies/{id} et /employees/{id}).
  2. Si une ressource ne peut pas exister sans une autre, elle devrait être représentée comme sa sous-ressource; Cependant, traitez l'opération comme une requête de moteur de recherche. Cela signifie que, au lieu d'effectuer l'opération immédiatement, il suffit de renvoyer HTTP 307 ("Temporary redirect") en indiquant l'URI canonique. Cela obligera les clients à répéter l'opération sur l'URI canonique.
  3. Votre document de spécification ne doit exposer que les ressources racine correspondant à votre modèle conceptuel (ne dépend pas des détails de la mise en œuvre). Les détails de l'implémentation peuvent changer (vos lignes ne sont peut-être plus identifiables de manière unique), mais votre modèle conceptuel reste intact. Dans l'exemple ci-dessus, vous indiqueriez aux clients /companies mais pas /employees.

Cette approche présente les avantages suivants:

  1. Cela élimine le besoin de faire des recherches inutiles dans la base de données.
  2. Il réduit le nombre de contrôles de validité à un par demande. Tout au plus, je dois vérifier si un employé appartient à une entreprise, mais je ne dois plus faire deux vérifications de validation pour /companies/{companyId}/employees/{employeeId}/computers/{computerId}.
  3. Cela a un impact mitigé sur l'évolutivité de la base de données. D'une part, vous réduisez les conflits de verrous en verrouillant moins de tables, pendant une période plus courte. Par contre, vous augmentez le nombre de blocages possibles, car chaque ressource racine doit utiliser un ordre de verrouillage différent. Je ne sais pas s'il s'agit d'un gain ou d'une perte net, mais je suis rassuré par le fait que les blocages de bases de données ne peuvent pas être évités de toute façon et que les règles de verrouillage résultantes sont plus simples à comprendre et à mettre en œuvre. En cas de doute, optez pour la simplicité.
  4. Notre modèle conceptuel reste intact. En veillant à ce que le document de spécification n'expose que notre modèle conceptuel, nous sommes libres de supprimer les adresses URI contenant les détails de la mise en œuvre à l'avenir, sans casser les clients existants. Souvenez-vous que rien ne vous empêche d’exposer les détails d’implémentation dans des URI intermédiaires tant que votre spécification déclare leur structure non définie.
14
Gili

C'est problématique car il n'est plus évident qu'un utilisateur appartienne à une entreprise particulière.

Parfois, cela peut mettre en évidence un problème avec votre modèle de domaine. Pourquoi un utilisateur appartient-il à une entreprise? Si je change de société, suis-je une nouvelle personne? Et si je travaille pour deux entreprises? Suis-je deux personnes différentes?

Si la réponse est oui, pourquoi ne pas utiliser un identifiant unique pour accéder à un utilisateur?

par exemple. Nom d'utilisateur:

company/foo/user/bar

(où bar est mon nom d'utilisateur unique dans cet espace de noms de société spécifique)

Si la réponse est non, alors pourquoi ne suis-je pas un utilisateur (personne) seul, et la collection company/users pointe-t-elle simplement vers moi: <link rel="user" uri="/user/1" /> (remarque: l'employé semble être plus approprié)

En dehors de votre exemple spécifique, je pense que les relations ressources-sous-ressources sont plus appropriées lorsqu'il s'agit de use plutôt que de property (et c'est pourquoi vous vous débattez avec la redondance d'identifier une entreprise pour une utilisateur qui identifie implicitement une entreprise).

Ce que je veux dire par là, c'est que users est en fait une sous-ressource d'une ressource d'entreprise, car use consiste à définir la relation entre une entreprise et ses employés - une autre façon de le dire est qu'il faut définir entreprise avant de pouvoir commencer à embaucher des employés. De même, un utilisateur (personne) doit être défini (né) avant de pouvoir les recruter.

17
Doug Moscrop

Votre règle pour décider si une ressource doit être modélisée en tant que sous-ressource est valide. Votre problème ne provient pas d'un mauvais modèle conceptuel, mais vous laissez une fuite de votre modèle de base de données dans votre modèle REST. 

D'un point de vue conceptuel, un employee s'il ne peut exister que dans une relation company est modélisé comme un composition . Le employee ne peut donc être identifié que via le company. Maintenant, les bases de données entrent en jeu et toutes les lignes employee reçoivent un identifiant unique.

Mon conseil est de ne pas laisser le modèle de base de données fuir dans votre modèle de conception, car vous exposez les problèmes d'infrastructure à votre API. Par exemple, que se passe-t-il lorsque vous décidez de passer à une base de données orientée document telle que MongoDB, dans laquelle vous pouvez modéliser vos employés dans le cadre du document d’entreprise et ne plus avoir cet identifiant unique artificiel? Souhaitez-vous changer votre API?

Pour répondre à vos questions supplémentaires

Comment devrais-je représenter le fait qu'une ressource appartient à une autre?

Composition via des sous-ressources, d'autres associations via des liens URL.

Comment devrais-je représenter le fait qu'une ressource ne peut être identifiée sans une autre?

Utilisez les deux valeurs id dans l'URL de votre ressource et veillez à ne pas laisser votre base de données s'infiltrer dans votre API en vérifiant si la "combinaison" existe.

Quelles sont les relations entre les sous-ressources et non sur les modèles?

Les sous-ressources conviennent bien aux compositions, mais plus généralement au modèle, une ressource ne peut pas exister sans la ressource parent et appartient toujours à une ressource parent. Votre règle when a resource could not exist without another, it should be represented as its sub-resource est un bon guide pour cette décision.

6
saintedlama

si une sous-ressource est uniquement identifiable sans son entité propriétaire, il ne s'agit pas d'une sous-ressource et devrait avoir son propre espace de nom (c'est-à-dire/users/{utilisateur} plutôt que /companies/{** : jamais jamais utilise la clé primaire de la base de données de votre entité comme identifiant de ressource. c'est l'erreur la plus commune où les détails de la mise en œuvre fuient vers le monde extérieur. vous devez toujours avoir une clé professionnelle naturelle (comme un nom d'utilisateur ou un numéro d'entreprise, plutôt que un identifiant utilisateur ou un identifiant d'entreprise). l'unicité d'une telle clé peut être renforcée par une contrainte unique si vous le souhaitez, mais la clé primaire d'une entité ne doit jamais quitter la couche de persistance de votre application, ou du moins ne doit jamais être un argument pour un service méthode. Si vous respectez cette règle, vous ne rencontrerez aucun problème pour distinguer les compositions (/ sociétés/{société}/utilisateurs/{utilisateur}) des associations (/ utilisateurs/{utilisateur}), car si votre sous-ressource n'a pas une clé métier naturelle, qui l'identifie dans un contexte global, vous pouvez être absolument certain qu'il s'agit d'une sous-ressource dépendante (ou vous devez d'abord créer une clé métier pour la rendre identifiable de manière globale).

4
Kai

C'est une façon de résoudre cette situation:

/ companies/{companyName}/employee/{employeeId} -> renvoie des données sur un employé, doit également inclure les données de la personne.

/ person/{peopleId} -> renvoie des données sur la personne

Parler d'employé n'a pas de sens sans parler aussi de l'entreprise, mais parler de la personne a du sens même sans entreprise et même s'il est embauché par plusieurs entreprises. L'existence d'une personne est indépendante de la question de savoir s'il a été embauché par une entreprise, mais l'existence d'un emploi dépend de l'entreprise.

1
Lie Ryan

Le problème semble être quand il n'y a pas spécifique mais un employé appartient techniquement à une entreprise ou à une organisation, autrement on pourrait les appeler des clochards ou des politiciens. Être employé implique une relation entreprise/organisation quelque part mais pas une relation spécifique. Les employés peuvent également travailler pour plus d'une entreprise/organisation. Lorsque le contexte spécifique de la société est requis, vos œuvres originales /companies/{companyName}/users/{id}

Disons que vous voulez connaître la EmployerContribution de votre ira/rsp/pension que vous utiliseriez: /companies/enron/users/fred/EmployerContributionVous obtenez le montant spécifique versé par enron (ou 0 $) .

Que faire si vous voulez la EmployerContributions de toutes les sociétés pour lesquelles fred a travaillé (ed)?
Vous n'avez pas besoin d'une entreprise concrète pour que cela ait un sens. /companies/any/employee/fred/EmployerContribution 

Où "tout" est évidemment une abstraction ou un espace réservé lorsque l'entreprise de l'employé importe peu, mais le fait d'être un employé l'est tout autant. Vous devez intercepter le gestionnaire 'company "pour empêcher une recherche de base de données (vous ne savez pas pourquoi la société ne sera pas mise en cache? Combien peut-il y en avoir?) 

Vous pouvez même changer l'abstraction pour représenter quelque chose comme toutes les entreprises pour lesquelles Fred a été employé au cours des 10 dernières années /companies/last10years/employee/fred/EmployerContribution

0
bhatt