ROCA (Resource-oriented Client Architecture) désigne un ensemble de pratiques d’architectures webs visant à construire une application au plus proche des standards du Web et surtout, de sa philosophie.
Nous avons derrière nous un bon nombre de frameworks webs qui ont cherché à proposer une vision purement technique sans prendre en compte la base même du Web. Le protocole HTTP, l’utilisation du Javascript, le support simple du navigateur, des URLs propres… pour certains le plus important était de pouvoir « faire du web » à n’importe quel prix.
Grâce à Sadek Drobi de Zenexity, je me suis plongé dans plusieurs articles autour de ce sujet, donc je vous donne ici une synthèse. Si vous construisez une application Web, c’est pour vous. A lire et à mettre en oeuvre.
Si vous êtes intéressé par ce sujet, nous parlerons d’Hypermedia API et d’HATEOAS dans d’autres articles prochainement.
ROCA (Resource-oriented Client Architecture)
ROCA est un terme donné à un ensemble de pratiques d’architecture web. En suivant les principes et les choix proposés par cette spécification ouverte, vous bénéficiez des connaissances de ceux qui ont déjà construit une application web.
Tout d’abord, c’est l’inverse des applications dîtes « single-page ». ROCA est basé au contraire sur une observation et une bonne compréhension du Web. ROCA n’est pas associé à un framework ou à un langage particulier. C’est donc un livre de recettes et de préceptes, que vous pouvez découvrir en participant à la communauté sur le site ROCA-style.org. Oui moi aussi j’ai pensé « Gangman style… » 🙂
L’idée de regrouper sous un même terme plusieurs pratiques est arrivée en mars 2012. La volonté des auteurs (Stefan Tikov, Till Schulte-Coerne entre autres) est aussi de pouvoir définir sous un seul terme ce qu’ils ont l’habitude d’utiliser et de construire.
ROCA en détail :
Je vous propose une traduction libre du document original pour comprendre. Bien entendu je vous encourage à lire la version en Anglais et à participer au projet.
La spécification se découpe en 2 parties. Une partie serveur et une partie cliente.
Côté serveur
- Côté serveur, votre application doit adhérer aux principes REST. Elle expose des ressources compréhensibles pour un humain, devant un navigateur. Chaque ressource a son unique URI. L’ensemble des informations pour charger une ressource est compris dans la requête. Votre application suit les méthodes HTTP (GET,POST,PUT,DELETE,HEAD) et respecte la sémantique de chacun de ces verbes. L’état des resources est tenu côté serveur [rest].
- Toute la logique de l’application est du côté serveur [application-logic].
- Le client interagit avec le serveur à travers des requêtes HTTP RESTful [http].
- Un utilisateur doit être en mesure de sauvegarder un lien vers une information, en copiant par exemple une adresse dans la barre de navigation de son navigateur, puis de la copier dans un email ou autre. Les liens sont correctement exposés par votre application [link].
- Il doit être possible d’utiliser la logique du serveur soit à travers un navigateur web, soit à travers un client web simple comme curl ou wget [non-browser].
- Les resources peuvent être représentées avec différents formats (HTML, JSON, XML…) [should-formats].
- Toutes les communications authentifiées s’appuient sur l’authentification HTTP Basic par dessus du SSL (ou avec un support de certificat client). De manière alternative, parce que certains navigateurs sont limités dans leur support de la gestion de l’authentification, une authentification sur la base d’un formulaire en conjonction avec un cookie peut être utilisé. Si les cookies sont utilisés, ils doivent alors inclure tous les états/informations nécessaires au serveur pour les utiliser. Il faut aussi pouvoir supporter différents mécanismes d’authentification. C’est le cas de l’API V3 de Github par exemple [auth].
- Les cookies ne sont utilisés que pour de l’authentification ou pour suivre les utilisateurs [cookies]
- Il ne doit pas y avoir d’état type session en dehors de ce qui est nécessaire pour des validations simples et de l’authentification.[session]
- L’application doit fonctionner parfaitement sans efforts particuliers de programmation avec un navigateur. Pour cela, le support du bouton « Précédent », « Recharger » ou « Suivant » doit être parfaitement opérationnel. Par exemple le rafraichissement d’une page ne doit pas renvoyer la page d’authentification, ou faire apparaître un message d’avertissement demandant si l’on souhaite soumettre un formulaire à nouveau. Si cela arrive, c’est que l’application n’est pas correctement implémentée [browser-controls].
Côté client
- Le serveur retourne du texte structure au format HTML qui doit être indépendant de la mise en page et du comportement du client [posh]
- L’accès aux informations et aux fonctionnalités doit être possible pour des outils d’aide à l’accessibilité comme des lecteurs d’écran [accessibility]
- CSS est utilisé pour formater et mettre en page l’information. Cela doit être fait selon les principes de l’amélioration progressive (progressive enhancement), afin que des navigateurs webs plus anciens puissent utiliser votre application.
- Sur le même principe de progressive enhancement, JavaScript doit être utilisé de manière non intrusive (unobtrusively). L’application doit être utilisable même si le javascript est déasctivé (même si c’est au prix d’une perte partielle de fonctionnalités) [unobtrusive-javascript]
- Une fonctionnalité ne doit pas être implémentée de manière redondante du côté client et du côté serveur. C’est de là que découle un principe fort : pas de logique du côté client [no-duplication]
- Le code du serveur peut ne pas connaître les structures HTML que le client génère (en dehors de ce qui est CSS) ou inversement. Exception faite cependant du code que le serveur génère pour le client pour son initialisation [know-structure]
- L’ensemble du code javascript et css doit être statique. Il ne doit pas être généré dynamiquement par le serveur selon un formulaire spécifique à la ressource demandée. Cependant cela n’interdit pas les systèmes de transtypage comme CoffeeScript ou LESS car en général le code respectif est pré-compilé sur le serveur [static-assets].
- Toute modification de l’état de routage dynamique ou d’une URI déclenchée par du JavaSCript côté client doit utiliser l’API HTML5 History. [historyapi]
Comprendre ROCA
Roy T.Fielding, qui a posé les principes de REST, explique dans un article de son blog que les API Web doivent être pilotées par le support des liens hypertextes. Cela veut dire qu’il est important de construire une application dont le contenu peut se découvrir, comme un site web. Si votre client doit connaître précisément les méthodes à appeler sur votre interface, alors vous n’avez pas construit une application type REST. Vous avez simplement construit un wrapper Web, mais pas une application REST. Pour comprendre ce point, imaginez vous entrain de surfer sur un site. Pour quelles raisons serait-il nécessaire de consulter une documentation pour accéder à telle ou telle sous-partie de votre site ? Il faut donc prévoir une navigation, des liens entre vos données, bref faire en sorte de coder vraiment une pure application Web.
Le terme utilisée est celui d’Hypermedia API. Parler de Web services veut dire que vous avez exposé des méthodes sous la forme d’un service web. Alors que définir un ensemble d’APIs complètement construite sur les liens hypertextes est vraiment l’idée d’une approche REST.
Cela veut dire que vous devez d’abord avoir des URIs qui ne forcent pas de couplage entre le client et le serveur. Il est nécessaire de pouvoir changer votre adressage sans que cela impacte le client. GitHub par exemple utilise les URI Templates (RFC 6570) pour exposer dynamiquement au client le moyen d’interagir avec les services. Ce principe permet ensuite à Github de changer à tout moment le schéma d’URIs sans que cela n’impacte le client.
Il est donc indispensable de définir uniquement vos ressources via les URIs, en supprimant des verbes d’actions comme on a souvent l’habitude de faire. Il est important de séparer l’identification de l’interaction. Je ne dois pas mélanger la façon dont j’interagis avec une ressource, avec son identification.
Ensuite votre application doit exposer et tirer partie des liens hyper textes. Qu’est-ce que l’hyper texte ? Il s’agit du principe de liaison qui permet d’établir une relation entre 2 documents, sans que le deuxième document n’ait besoin de connaître le premier. Cela ouvre la possibilité à un utilisateur de prendre un chemin, en suivant un lien de redirection. Ce n’est pas restreint à une page HTML. Un message JSON retourné par votre API Web peut très bien contenir des liens hypertextes, afin de permettre à un client web de suivre et de visiter d’autres ressources dans votre application. Tout ceci est bien dans l’esprit d’une approche où l’on expose des documents, plutôt que dans l’approche où l’on expose des méthodes et un contrat. Tout ce qui est RPC, SOAP et compagnie n’a rien à faire dans cette catégorie et n’a rien à voir avec ce qui est REST.
Une pure API RESTful doit ressembler finalement à un site web. Lorsque vous souhaitez exposer une interface d’interrogation, un formulaire envoyé par votre API permet d’exposer à un instant T la façon de requêter votre application. C’est intéressant car cela veut dire que votre client web n’a pas besoin de connaître à l’avance les paramètres ou même la ressource à appeler. Finalement les interactions possibles doivent être exposées par votre API lorsque le client l’utilise. Comme le dit Roy T.Fiedling, votre navigateur web ne fait pas la distinction entre un site bancaire ou un wiki. Pour quelles raisons alors serait-il envisageable d’exposer des ressources différemment ?
Aujourd’hui force est de constater que cela demande un travail, à relativiser avec vos besoins.
Références
Untangled, RESTs API must be hypertext-driven, article de référence de Roy T. Fielding, co-fondateur du serveur HTTP Apache, auteur de la thèse sur REST
Github Developer API : exemple complet et intéressant
The H Factor est un article qui tente de définir le niveau d’hypermedia
0 no like
c’est l’inverse des applications dîtes « single-page »
Oui mais seulement à cause du point [unobtrusive-javascript], non? Est-ce justifié? En quoi les architectures single page sont moins respectueuses de l’esprit web?
Auparavant, j’étais complètement en phase avec la vision ROCA. J’ai changé mon fusil d’épaule après avoir découvert Angular.js et je trouve cela plus « logique » de gérer l’intégralité de la couche présentation côté navigateur (même si cela représente techniquement des avantages ET des inconvénients).
Ce principe permet ensuite à Github de changer à tout moment le schéma d’URIs sans que cela n’impacte le client.
Je ne suis pas sûr de comprendre le raisonnement. Imaginons qu’on envoie une requête GET /node/123 (sur une application de blog). Le serveur va alors renvoyer un message de type:
{ "title": "Mon super billet", "content": "bla bla", "<b>comments_url</b>": "/node/123/comments" }
Donc lorsque le serveur décide de changer l’uri d’accès aux commentaires d’un billet qui devient alors /node/:id/nodeComments , l’implémentation du client n’a pas besoin d’être modifiée car celui-ci ce base déjà sur le paramètre « comments_url ». C’est bien cela?
Mais, quelle est la situation qui pousse le fournisseur à changer l’URI tout en gardant le même nom pour le paramètre de l’url (en l’occurence comments_url)?
Si on prend l’exemple de github, pour avoir les organisations d’un user, la doc indique : GET /users/:user/orgs . Et si on regarde du côté des users, la requête GET /users/:user ne renvoie visiblement aucun lien vers la liste des orgas (en tout cas pas d’après la doc).
Je ne connaissais pas ROCA-style.org, mais je trouve toujours amusant de voir les gens redécouvrir ce qu’est le Web, à quel point c’est simple, et déjà défini depuis 20 ans 🙂 À ce propos, voici un article de référence qu’il convient de mentionner : http://www.w3.org/TR/webarch/ .
Sinon, voilà quelques questions pour continuer ta réflexion : comment parler de resource à l’intérieur d’une resource (sous-ressource) ? Comment faire la différence entre un lien vers une page et un lien vers une donnée ? Si le client interprète le JSON pour faire un choix (par exemple suivre un lien), alors il y a de la logique dans le client, et donc on n’est plus REST ? Comment concilier les données dans du JSON, les données dans une page HTML, les données dans du XML, etc. ? Est-ce que modèle JSON (à opposer au format de sérialisation JSON) est vraiment le plus adapté à ce genre de problématique ? Pourquoi pas XML ou autre chose ?
@montesq par application « single-page » j’entends application qui n’a qu’une seule URI. Avec AngularJS et son routeur, en général tu définis plusieurs URI qui sont ensuite autant d’état fonctionnel pour ton application. Et ça, ce n’est pas du « single-page » car d’un point de vue navigateur, c’est autant d’URL différentes. Ensuite, et c’est l’intérêt d’utiliser un framework JS, tu peux faire ton application avec, et je suis complètement d’accord avec toi, la logique et la présentation.
Concernant le changement des URI, il faut penser au fonctionnement d’un site Web. Lorsque je change le routage d’une sous-page, tu peux continuer à surfer sur mon site. Charge à moi de te rediriger vers la nouvelle ressource (HTTP 301 et 303) lorsque je déplace celle-ci.
J’approuve parfaitement ce modèle, à une exception :
Ne pas avoir de duplication de logique entre les côtés client et serveur ne me paraît pas compatible avec la réalisation d’application à l’interface utilisateur très riche et au chargement asynchrone.
Je vais donner un exemple concret puis en tirer des conclusions plus générales. J’ai écrit dernièrement une application servant à fusionner des flux iCalendar : elle permet de générer un seul flux iCalendar à partir de plusieurs flux.
Côté serveur, quand un client me demande le contenu d’un flux composite je dois retrouver toutes les sources dont il dépend, et comme mon modèle est un graphe (un flux composite peut dépendre d’un autre flux composite) je dois faire un tri topologique dessus pour trouver tous les flux sources en bout de chaîne.
L’interface Web, extrêmement rudimentaire, affiche la liste des flux sources et des flux composites. Lorsque l’utilisateur survole un flux composite, l’interface graphique révèle, en affichant en surbrillance, les flux sources dont le flux composite dépend. Je me retrouve donc à effectuer le même calcul de tri topologique sur ma structure de graphe pour retrouver les sources d’un flux composite.
En fait, je pourrais faire les calculs uniquement côté client, mais ça ne serait pas scalable : il faudrait prévoir toutes les opérations qu’un client est susceptible d’effectuer pour lui envoyer toutes les données permettant la mise à jour de l’interface utilisateur sans calcul supplémentaire côté client. Dans mon cas ça serait jouable car en général un utilisateur ne possède pas des dizaines de calendriers. Mais pour d’autres applications ça peut être plus pénible. Si, on n’envoie pas toutes les données la première fois, cela signifie qu’on les envoie au fur et à mesure, selon les besoins, donc qu’on effectue une requête HTTP, qui génère de la latence, à chaque action de l’utilisateur… L’opportunité d’effectuer des calculs côté client me permet d’être plus paresseux (au sens lazy evaluation), car cela me permet d’envoyer juste ce qu’il faut comme données pour l’affichage initial, après avoir effectué juste ce qui était nécessaire comme calcul avec le CPU de mon serveur, et, si le client effectue une action qui nécessite un calcul supplémentaire, ce calcul précis peut être effectué localement, à la demande.
Un autre avantage de dupliquer la logique côté client et serveur, qui n’est pas encore exploité aujourd’hui mais qui, je pense, devrait le devenir de plus en plus à l’avenir, c’est la possibilité de réaliser des applications fonctionnant aussi bien hors ligne qu’en ligne. J’imagine que vous utilisez (et appréciez) tous des applications mobile natives (non Web) qui fonctionnent aussi bien hors ligne qu’en ligne. Avoir le même confort avec des applications Web nécessite de dupliquer la logique côté client.
L’approche est un peu utopique.
Je bosse sur une Single-Page Application en Backbone (et oui on appelle encore ça SPA a ma connaissance même si il y a un routeur comme dans Angular) et je vois pas comment je pourrais utiliser cette approche.
« Les ressources peuvent être représentées en différents formats (JSON, XML) ». OK mais c’est un peu contradictoire avec « Le serveur retourne du texte structure au format HTML ».
Dans le cadre d’une SPA, même avec des pseudo URIs REST je me demande comment tu fais pour ne pas mettre de logique coté client. Une appli destop like qui tourne dans le browser sans duplication de code je vois pas comment faire, que ça soit du JS ou du Flex ou autre, sans même parler des problématiques plus complexes comme celles dont parle Julien avec le mode offline, les contraintes de perf de la vraie vie…
La moindre des choses serait que la vue soit au moins consciente du domaine qu’elle manipule. Sinon comment peut-tu recevoir un json et savoir quel affichage utiliser?
L’approche est sans doute une bonne idée pour faire un backoffice mais pour une application front riche ça me semble légèrement compromis, ou alors montrez moi un poc 🙂
Je partage aussi votre point de vue sur la logique (je rappelle que l’article original est une traduction, pas mon point de vue personnel). Je pense que l’idée est d’éviter de copier-coller aveuglément la logique fonctionnelle d’une application serveur entre le client et le serveur. On est dans un mode typiquement « client-serveur ». Lorsque nous envisageons une application « déconnectée » du serveur, je pense que nous sortons du cadre d’une application Web. Cela nous rapproche plus d’une application classique. Les bonnes pratiques regroupées sous le terme ROCA ont au moins le mérite de donner un nom commun à un ensemble de techniques et de choix d’architecture. Quelque part cela rappelle la sortie du terme « AJAX ». Nous faisions avant cela du XmlHttpRequest et compagnie… mais sans avoir un terme plus simple pour exprimer l’approche différente que nous faisions. Et un beau matin, quelqu’un a placé le mot « ajax » dans un article. Cela a ensuite donné un élan et a facilité la communication autour du développement d’une application web.
Gangnam* style 😛 (détail très important s’il en est !)
Il s’agit d’un quartier de Séoul et pas d’un mec qui fait partie d’un gang.
Hello,
Petite question à propos du point:
Il doit être possible d’utiliser la logique du serveur soit à travers un navigateur web, soit à travers un client web simple comme curl ou wget [non-browser].
Comment se prémunir des attaques CSRF?