service de repos des ressources clés composite
j'ai rencontré un problème au travail où je ne peux pas trouver d'information sur la norme ou la pratique habituelle pour effectuer des opérations crudes dans un service Web reposant contre une ressource dont la clé primaire est un composite d'autres ID de ressources. Nous utilisons MVC WebApi pour créer les contrôleurs. Par exemple, nous avons trois tables:
Product
: PK=ProductIdPart
: PK=PartIdProductPartAssoc
: PK=(ProductId, PartId)
Un le produit peut avoir plusieurs pièces et une pièce peut être un composant de plusieurs produits. L'association tableau contient également des informations supplémentaires pertinentes pour l'association elle-même que doit être modifiable.
Nous avons ProductsController
et PartsController
classes qui gèrent les opérations habituelles GET/PUT/POST/DELETE en utilisant des modèles de route définis comme: {controller}/{id}/{action}
de telle sorte que l'IRIs suivant fonctionne:
- GET, POST
/api/Products
- retourne tous les produits, crée un nouveau produit - GET, PUT, DELETE
/api/Products/1
- récupère les mises à jour//supprime produit 1 - GET, POST
/api/Parts
- retourne toutes les pièces, crée une nouvelle partie - GET, PUT, DELETE
/api/Parts/2
- récupère les mises à jour//supprime la partie 2 - GET
/api/Products/1/Parts
- obtenir toutes les pièces du produit 1 - GET
/api/Parts/2/Products
- obtenir tous les produits pour lesquels la partie 2 est un composant
ce qui me pose problème, c'est de savoir comment définir l'itinéraire modèle pour les ressources de ProductPartAssoc. À quoi devraient ressembler le modèle de route et L'IRI pour obtenir les données de l'association? En adhérant à la convention, Je m'attendrais à quelque chose comme:
- GET, POST
/api/ProductPartAssoc
- retourne toutes les associations, crée une association - GET, PUT, DELETE
/api/ProductPartAssoc/[1,2]
- récupère les mises à jour//supprime l'association entre le produit 1 et partie 2
mes collègues trouvent cela déplaisant d'un point de vue esthétique et semblent penser il vaudrait mieux ne pas avoir un ProductPartAssocController
ajouter des méthodes supplémentaires à la classe ProductsController
pour gérer les données de l'association:
- GET, PUT, DELETE
/api/Products/1/Parts/2
- obtenir des données pour l'association entre le produit 1 et la partie 2 plutôt que des données pour la partie 2 en tant que membre de la partie 1, ce qui serait conventionnellement le cas basé sur d'autres exemples tels que/Book/5/Chapter/3
que j'ai vu ailleurs. - N'affichez aucune idée de ce à quoi ils s'attendent de la part de L'IRI. Malheureusement, ils sont les décideurs.
à la fin de la journée, je suppose que ce que je cherche, c'est soit une validation, soit une direction que je peux indiquer et dire "Voyez, c'est ce que font les autres."
Quelle est la pratique habituelle pour traiter les ressources identifiées par des clés composites?
2 réponses
moi aussi, j'aime l'esthétique de /api/Products/1/Parts/2
. Vous pouvez également avoir plusieurs routes aller à la même action, de sorte que vous pouvez doubler et aussi offrir /api/Parts/2/Products/1
comme URL alternative pour la même ressource.
quant à POST, vous connaissez déjà la clé composite. Alors pourquoi ne pas éliminer le besoin de POST et juste utiliser PUT pour la création et les mises à jour? Publier une collection de ressources de l'URL est idéal si votre système génère la clé primaire, mais dans le cas où vous avez un composite de déjà connu clés primaires, pourquoi avez-vous besoin de POST?
cela dit, j'aime aussi l'idée d'avoir séparé ProductPartAssocController
pour contenir les actions de ces URL. Vous devez faire une cartographie de route personnalisée, mais si vous utilisez quelque chose comme AttributeRouting.NET c'est très facile à faire.
Par exemple, nous faisons cela pour la gestion des utilisateurs, des rôles:
PUT, GET, DELETE /api/users/1/roles/2
PUT, GET, DELETE /api/roles/2/users/1
6 URL, mais seulement de 3 actions, le tout dans le GrantsController
(nous appelons le gerund entre les utilisateurs et les rôles "Accorder.)" La classe finit par ressembler à quelque chose comme ça, en utilisant AttributeRouting.NET:
[RoutePrefix("api")]
[Authorize(Roles = RoleName.RoleGrantors)]
public class GrantsController : ApiController
{
[PUT("users/{userId}/roles/{roleId}", ActionPrecedence = 1)]
[PUT("roles/{roleId}/users/{userId}", ActionPrecedence = 2)]
public HttpResponseMessage PutInRole(int userId, int roleId)
{
...
}
[DELETE("users/{userId}/roles/{roleId}", ActionPrecedence = 1)]
[DELETE("roles/{roleId}/users/{userId}", ActionPrecedence = 2)]
public HttpResponseMessage DeleteFromRole(int userId, int roleId)
{
...
}
...etc
}
Cela semble assez intuitif approche de moi. Garder les actions dans un controller séparé permet aussi des controllers plus maigres.
je propose:
- POST
/api/PartsProductsAssoc
: créer un lien entre la pièce et le produit. Inclure les identificateurs de pièce et de produit dans les données POST. - GET, PUT, DELETE
/api/PartsProductsAssoc/<assoc_id>
: lire/mettre à jour/supprimer le lien avec<assoc_id>
(pas d'id de pièce ou de produit, Oui, cela signifie créer une nouvelle colonne dans votre tableau PartsProductsAssoc). - GET
/api/PartsProductsAssoc/Parts/<part_id>/Products
: obtenir la liste des produits associés à la partie donnée. - GET
/api/PartsProductsAssoc/Products/<product_id>/Parts
: obtenir la liste des pièces associés avec le produit.
Raisons de prendre cette approche:
- URI simple, entièrement qualifié pour chaque lien.
- modifier un lien modifie une seule ressource REST.
pour plus d'information, voir https://www.youtube.com/watch?v=hdSrT4yjS1g à 56: 30.