J'ai l'interface suivante dans ma couche métier
public interface IUserService
{
void CreateUser(User user);
List<User> FindUsersByName(string searchedString);
User GetUserById(int userId);
User GetUserByCredentials(string login, string password);
void UpdateUser(User user);
void UpdateUserPassword(int userId, string oldPassword, string newPassword);
}
Maintenant, je veux fournir une API Web pour cette interface. Comme vous pouvez le voir, cette interface a plusieurs méthodes get
qui retournent un élément GetUserById
et GetUserByCredentials
, elle a également plusieurs méthodes de mise à jour UpdateUser
et UpdateUserPassword
, à l'avenir, je pourrais vouloir ajouter une méthode get supplémentaire qui renvoie une collection, comme, par exemple, GetAllUsers
.
La solution évidente était d'encapsuler cette fonctionnalité dans un contrôleur. Donc ce que j'ai fait en premier, dans WebApiConfig
j'ai changé la configuration des routes en
config.Routes.MapHttpRoute(
name: "DefaultApi",
//as you can see I added {action} to the path so that, it will be possible to differentiate between different get/put requests
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
J'ai ensuite créé un UsersController
qui ressemble à ceci
public class UsersController : ApiController
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
// POST api/users/createuser
[HttpPost]
public IHttpActionResult CreateUser(User user)
{
//some code
}
// GET api/users/getuserbyid?id=1
[HttpGet]
public IHttpActionResult GetUserById(int id)
{
//some code
}
// GET api/users/getuserbycredentials?login=log&password=pass
[HttpGet]
public IHttpActionResult GetUserByCredentials(string login, string password)
{
//some code
}
// GET api/users/findusersbyname?searchedString=jack
[HttpGet]
public IHttpActionResult FindUsersByName(string searchedString)
{
//some code
}
// PUT api/users/updateuser
[HttpPut]
public IHttpActionResult UpdateUser(UserBase user)
{
//some code
}
// PUT api/users/updateuserpassword?userId=1&oldPassword=123&newPassword=1234
[HttpPut]
public IHttpActionResult UpdateUserPassword(int userId, string oldPassword, string newPassword)
{
//some code
}
}
Comme vous pouvez le voir dans le code ci-dessus, j'ai des URI différents pour chaque méthode d'action, par exemple pour GetUserById
- api/users/getuserbyid?id=1
, pour GetUserByCredentials
- api/users/getuserbycredentials?login=log&password=pass
etc. Cette solution fonctionne bien jusqu'à présent, mais le problème est que, pour autant que je sache, vous ne pouvez pas avoir plusieurs obtentions selon REST, cette solution respecte-t-elle toujours les contraintes d'un service RESTful? Et sinon, comment puis-je le rendre vraiment reposant? L'idée de diviser cette interface en différents contrôleurs me semble un peu étrange, car à l'avenir, je voudrai peut-être ajouter de nouvelles méthodes à mon interface, comme, GetUsersByGender
, GetUsersByDateOfBirthday
et ainsi de suite (si je vais créer un nouveau contrôleur à chaque fois, cela ne me convient pas)
Je sais que vous ne pouvez pas avoir plusieurs prises selon REST
Pas vraiment. RESTE et la modélisation API sont des sujets différents. REST Les API sont censées être une stratégie d'intégration, basée sur les prémisses introduites par Fielding sur son dissertation sur les styles architecturaux distribués. Ces prémisses n'ont rien à voir avec la façon dont les API sont modélisés, le nombre de ressources, d'URI et de sémantique que nous fournissons.
Par exemple:
/api/users-living-in-courscant
/api/users-not-living-in-courscant
/api/users?q=living:coruscant
/api/users?q=id:12345
/api/user/12345
/api/me
Certains des URI ci-dessus peuvent faire référence aux mêmes ressources, la principale différence (et le point clé) réside dans leur sémantique respective.
cette solution répond-elle toujours aux contraintes d'un service RESTful?
À mon avis, votre approche est plus proche d'un service Web de type RPC que d'un API REST. Jetez un œil à l'article de Martin Fowler sur Richardson Maturity Model .
Si nous lisons attentivement l'article de Martin, nous constatons que Martin n'introduit pas de techniques de modélisation API ni de meilleures pratiques. Il met l'accent sur comment faire la communication client-serveur correctement selon la sémantique HTTP. Comment représenter et découvrir les ressources.
Mais, il ne mentionne pas comment identifier ces ressources. Il ne mentionne pas comment façonner les URI.
Et sinon, comment puis-je le rendre vraiment reposant?
Si rendre l'API totalement RESTful est le problème, je suggère de lire d'abord la dissertation Fielding. Une fois assimilé le sens de REST, je chercherais de la documentation liée à la modélisation d'API. Dès que le dernier a convenu avec le premier, vous devriez être sur la bonne voie.
Voici 2 liens pour commencer à travailler:
Conventions de dénomination des ressources Assez basique. Certaines des conventions présentées ici sont largement acceptées comme des "bonnes pratiques".
modélisation API Ici, nous nous plongerons dans la modélisation API.
Les liens ci-dessus suivent un ordre délibéré. Je pense que c'est un ordre naturel qui va des bases aux concepts avancés. À partir du sol.
J'ai souligné les conventions Word intentionnellement. Celles-ci peuvent être rejetées ou interprétées si vous les jugez inadéquates à vos besoins.
Si vous êtes intéressé par le sujet, j'ai trouvé les livres suivants perspicaces.
API Design Rulebook : Il est principalement axé sur la modélisation d'API. Vous trouverez une brève introduction à l'architecture Web et aux principes REST.
Reste dans la pratique : Je considère que celui-ci est plus avancé. Plutôt concentré sur les avantages de REST en tant qu'intégration que sur la modélisation d'API.
REST ne limite pas le nombre de méthodes. Vous pouvez en utiliser autant que vous le souhaitez. L'API et votre implémentation sont 2 choses différentes. Si vous voulez que votre API suive l'architecture REST alors il s'agit d'échanger des ressources. La façon dont vous gérez votre code dépend de vous.
Votre code concerne les utilisateurs. Mais l'URI ressemble à des actions, pas à des ressources. Considérez un schéma comme celui-ci:
GET /users - returns the collection of users
GET /users/{id} - return the user with a specific ID
POST /users - add a user to the collection
searching based on criteria could look like this:
GET /users?country={country-code}&city={city}
Cela expose les utilisateurs à des ressources et utilise les méthodes du protocole HTTP pour identifier l'action à effectuer sur ces ressources.
Les gens ont des idées différentes sur ce qui fait d'une API une REST. La plupart des API que j'ai lues ou vues dans des exemples sont limitées. Elles ne sont pas exposées à l'échelle Web avec un grand nombre de implémentations client.
De plus, l'utilisation de types MIME explicites pour identifier les ressources transférées et d'une manière structurée de définir les liens entre les ressources n'est souvent pas incluse.
Alors, pensez à ce que vous voulez que votre API soit, une 'vraie' REST, ou quelque chose de plus simple qui utilise des URI logiques, des méthodes HTTP et des codes de réponse HTTP pour communiquer.
pour autant que je sache, vous ne pouvez pas avoir plusieurs prises selon REST
Non, pas vraiment. Ce que vous ne pouvez pas avoir, c'est l'état. Par exemple, si vous avez une API telle que:
POST /set-current-user/[id]
GET /user-info
GET /user-avatar
POST /change-password
ce qui signifie que pour obtenir la photo de profil de l'utilisateur, vous devez d'abord appeler set-current-user
, vous n'êtes pas RESTful.
En dehors de cela, vous êtes libre d'avoir autant d'actions GET ou non-GET que nécessaire dans votre contrôleur, car parmi six contraintes architecturales , il n'y a pas de contrainte qui vous dit de ne pas avoir plus d'une action par contrôleur.
Aussi, je ne peux pas éviter de mettre en évidence l'un de vos exemples:
GET api/users/getuserbycredentials?login=log&password=pass
S'il s'agit de l'itinéraire que vous avez utilisé, ne le faites pas. Même avec HTTPS (et vous avez pour utiliser HTTPS, puisque vous manipulez des données utilisateur sensibles ici), cela a un énorme défaut de sécurité dans l'envoi de mots de passe en clair texte directement dans les journaux du serveur. Même si vous êtes absolument sûr que les journaux sont chiffrés et que le trafic du serveur d'applications vers les journaux est effectué avec un protocole sécurisé, le seul fait de stocker le texte brut des mots de passe des utilisateurs est terrible en termes de sécurité. Ne fais pas ça. Jamais.