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:
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}
Un an plus tard, j'ai conclu avec le compromis suivant (pour les lignes de base de données contenant un identificateur unique):
/companies/{id}
et /employees/{id}
).HTTP 307 ("Temporary redirect")
en indiquant l'URI canonique. Cela obligera les clients à répéter l'opération sur l'URI canonique./companies
mais pas /employees
.Cette approche présente les avantages suivants:
/companies/{companyId}/employees/{employeeId}/computers/{computerId}
.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.
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.
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).
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.
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/EmployerContribution
Vous obtenez le montant spécifique versé par enron (ou 0 $) .
Que faire si vous voulez la EmployerContribution
s 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