Question

Je me demande comment vous implémenteriez le cas d'utilisation suivant dans REST.Est-il même possible de faire sans compromettre le modèle conceptuel ?

Lisez ou mettez à jour plusieurs ressources dans le cadre d’une seule transaction.Par exemple, transférez 100 $ du compte bancaire de Bob vers le compte de John.

Pour autant que je sache, la seule façon de mettre en œuvre cela est de tricher.Vous pouvez POST sur la ressource associée à John ou à Bob et effectuer l'intégralité de l'opération en utilisant une seule transaction.En ce qui me concerne, cela brise l'architecture REST car vous tunnelisez essentiellement un appel RPC via POST au lieu de réellement opérer sur des ressources individuelles.

Était-ce utile?

La solution

Envisagez un scénario de panier RESTful. Le panier est conceptuellement votre enveloppe de transaction. De la même manière que vous pouvez ajouter plusieurs articles à un panier puis envoyer ce panier pour traiter la commande, vous pouvez ajouter l'entrée de compte de Bob au wrapper de transaction, puis l'entrée de compte de Bill au wrapper. Lorsque toutes les pièces sont en place, vous pouvez alors POST / PUT le wrapper de transaction avec toutes les pièces du composant.

Autres conseils

Il y a quelques cas importants auxquels cette question ne répond pas, ce qui, à mon avis, est dommage, car Google occupe une position élevée dans les termes de recherche: -)

Plus précisément, voici un bon exemple: si vous postez deux fois (car certains cache sont bloqués dans l’intermédiaire), vous ne devez pas transférer le montant deux fois.

Pour ce faire, vous créez une transaction en tant qu’objet. Cela pourrait contenir toutes les données que vous connaissez déjà et mettre la transaction en attente.

POST /transfer/txn
{"source":"john's account", "destination":"bob's account", "amount":10}

{"id":"/transfer/txn/12345", "state":"pending", "source":...}

Une fois que vous avez cette transaction, vous pouvez la commettre, par exemple:

PUT /transfer/txn/12345
{"id":"/transfer/txn/12345", "state":"committed", ...}

{"id":"/transfer/txn/12345", "state":"committed", ...}

Notez que les options de vente multiples n’ont aucune importance à ce stade; même un GET sur le txn renverrait l'état actuel. Plus précisément, le second PUT détecterait que le premier était déjà dans l'état approprié et le renverrait - ou, si vous essayez de le placer dans le & "Rolledback &"; Etat après qu'il soit déjà dans " commit " vous obtiendrez une erreur et la transaction validée en retour.

Tant que vous vous connectez à une base de données unique ou à une base de données avec un moniteur de transactions intégré, ce mécanisme fonctionnera parfaitement. Vous pouvez également introduire des délais d'attente pour les transactions, que vous pouvez même utiliser à l'aide des en-têtes Expires si vous le souhaitez.

En termes REST, les ressources sont des noms sur lesquels on peut agir avec des verbes CRUD (créer/lire/mettre à jour/supprimer).Puisqu'il n'y a pas de verbe « transférer de l'argent », nous devons définir une ressource « transaction » sur laquelle agir avec CRUD.Voici un exemple en HTTP+POX.La première étape consiste à CRÉER (méthode HTTP POST) un nouveau vide transaction:

POST /transaction

Cela renvoie un identifiant de transaction, par ex."1234" et selon l'URL "/transaction/1234".Notez que lancer ce POST plusieurs fois ne créera pas la même transaction avec plusieurs identifiants et évitera également l'introduction d'un état « en attente ».De plus, le POST ne peut pas toujours être idempotent (une exigence REST), il est donc généralement recommandé de minimiser les données dans les POST.

Vous pouvez laisser la génération d'un identifiant de transaction au client.Dans ce cas, vous POST /transaction/1234 pour créer la transaction "1234" et le serveur renverrait une erreur si elle existait déjà.Dans la réponse d'erreur, le serveur pourrait renvoyer un identifiant actuellement inutilisé avec une URL appropriée.Ce n'est pas une bonne idée d'interroger le serveur pour un nouvel identifiant avec une méthode GET, car GET ne devrait jamais modifier l'état du serveur et la création/réservation d'un nouvel identifiant modifierait l'état du serveur.

Ensuite, nous MISE À JOUR (Méthode HTTP PUT) la transaction avec toutes les données, en la validant implicitement :

PUT /transaction/1234
<transaction>
  <from>/account/john</from>
  <to>/account/bob</to>
  <amount>100</amount>
</transaction>

Si une transaction portant l'ID "1234" a déjà été PUT, le serveur donne une réponse d'erreur, sinon une réponse OK et une URL pour visualiser la transaction terminée.

Attention :dans /account/john , "john" devrait en réalité être le numéro de compte unique de John.

Excellente question, REST est principalement expliqué à l'aide d'exemples ressemblant à des bases de données, où quelque chose est stocké, mis à jour, récupéré, supprimé. Il existe peu d'exemples comme celui-ci, où le serveur est supposé traiter les données d'une manière ou d'une autre. Je ne pense pas que Roy Fielding en ait inclus dans sa thèse, qui était basée sur http après tout.

Mais il parle de " transfert d'état représentationnel " en tant que machine d'état, avec des liens se déplaçant vers l'état suivant. De cette manière, les documents (les représentations) gardent une trace de l'état du client, au lieu que le serveur soit obligé de le faire. De cette manière, il n’ya pas d’état client, mais seulement en fonction du lien sur lequel vous vous trouvez.

J'ai réfléchi à cette question et il me semble raisonnable que, pour que le serveur traite quelque chose pour vous, le serveur crée automatiquement des ressources connexes, et vous en donne les liens (en fait. , il n’aurait pas besoin de les créer automatiquement: il pourrait simplement vous dire les liens, et il ne les créera que quand et si vous les suivez - création paresseuse). Et pour vous donner également des liens permettant de créer de nouvelles ressources apparentées - une ressource associée a le même URI mais est plus longue (ajoute un suffixe). Par exemple:

  1. Vous envoyez ( POST ) la représentation du concept de transaction avec toutes les informations. Cela ressemble à un appel RPC, mais crée réellement le "ressource de transaction proposée &"; par exemple URI: /transaction Des problèmes surviendront lors de la création de plusieurs de ces ressources, chacune avec un URI différent.
  2. La réponse du serveur indique l'URI de la ressource créée, sa représentation. Cela inclut le lien ( URI ) pour créer la ressource associée de une nouvelle & "ressource de transaction validée "; ". Les autres ressources connexes sont le lien permettant de supprimer la transaction proposée. Ce sont des états dans la machine à états, que le client peut suivre. Logiquement, ils font partie de la ressource créée sur le serveur, au-delà des informations fournies par le client. par exemple, les URI: /transaction/1234/proposed, /transaction/1234/committed
  3. Vous POST envoyez un lien vers la & ressource de transaction validée & "; qui crée cette ressource et modifie l'état du serveur. (les soldes des deux comptes) **. De par sa nature, cette ressource ne peut être créée qu'une seule fois et ne peut pas être mise à jour. Par conséquent, les erreurs commises lors de nombreuses transactions ne peuvent pas se produire.
  4. Vous pouvez obtenir ces deux ressources pour voir quel est leur état. En supposant qu'un POST puisse modifier d'autres ressources, la proposition serait désormais marquée comme & Quot; commit & Quot; (ou peut-être pas du tout disponible).

Ceci est similaire au fonctionnement des pages Web, la dernière page Web indiquant & "êtes-vous sûr de vouloir le faire? &"; Cette page Web finale est elle-même une représentation de l’état de la transaction, qui comprend un lien pour accéder à l’état suivant. Pas seulement des transactions financières; également (par exemple) prévisualiser puis commettre sur wikipedia. Je suppose que la distinction dans REST est que chaque étape de la séquence d’états a un nom explicite (son URI).

Dans les transactions / ventes réelles, il existe souvent des documents physiques différents pour les différentes étapes d'une transaction (proposition, bon de commande, reçu, etc.). Encore plus pour l'achat d'une maison, avec règlement, etc.

OTOH On a l'impression de jouer avec la sémantique. Je suis mal à l'aise avec la nominalisation de la conversion des verbes en noms pour le rendre RESTful, & ", Car il utilise des noms (URI) au lieu de verbes (appels RPC) &" ;. c'est-à-dire le nom " ressource de transaction validée " au lieu du verbe " valider cette transaction " ;. J'imagine que l'un des avantages de la nominalisation est que vous pouvez faire référence à la ressource par son nom, au lieu de devoir la spécifier d'une autre manière (telle que le maintien de l'état de session, de sorte que vous sachiez quelle & "Cette &" Transaction is ...)

Mais la question importante est: quels sont les avantages de cette approche? En quoi ce style REST est-il meilleur que le style RPC? Une technique intéressante pour les pages Web est-elle également utile pour le traitement des informations, au-delà du stockage / récupération / mise à jour / suppression? Je pense que le principal avantage de REST est son évolutivité. un aspect de cela n’a pas besoin de maintenir explicitement l’état du client (mais en le rendant implicite dans l’URI de la ressource, et le suivant en tant que liens dans sa représentation). En ce sens, ça aide. Peut-être que cela aide aussi à la superposition / au traitement en pipeline? Seul un utilisateur examinera sa transaction spécifique. Par conséquent, il n’ya aucun avantage à la mettre en cache afin que d’autres puissent la lire, le gros gain de http.

Si vous restez en arrière pour résumer la discussion ici, il est assez clair que REST n'est pas approprié pour de nombreuses API, en particulier lorsque l'interaction client-serveur est intrinsèquement dynamique, comme c'est le cas pour les transactions non triviales. Pourquoi sauter à travers toutes les étapes suggérées, pour le client et le serveur, afin de suivre de façon pédante un principe qui ne correspond pas au problème? Un meilleur principe consiste à donner au client le moyen le plus simple, le plus naturel et le plus productif de composer avec l’application.

En résumé, si vous effectuez vraiment beaucoup de transactions (types, pas d'instances) dans votre application, vous ne devriez vraiment pas créer d'API RESTful.

Vous devez lancer votre propre " identifiant de transaction " type de gestion tx. Donc, ce serait 4 appels:

http://service/transaction (some sort of tx request)
http://service/bankaccount/bob (give tx id)
http://service/bankaccount/john (give tx id)
http://service/transaction (request to commit)

Vous devez gérer le stockage des actions dans une base de données (si la charge est équilibrée) ou en mémoire, puis gérer les validations, les annulations et les délais.

Ce n’est pas vraiment une journée de repos dans le parc.

Je me suis écarté de ce sujet pendant 10 ans. En revenant, je ne peux pas croire que la religion se fait passer pour une science dans laquelle vous vous aventurez lorsque vous restez sur Google + fiable. La confusion est mythique.

Je diviserais cette vaste question en trois:

  • Services en aval. Tous les services Web que vous développez auront des services en aval que vous utiliserez et pour lesquels vous ne devrez pas ne pas suivre la syntaxe de transaction. Vous devriez essayer de cacher tout cela aux utilisateurs de votre service et vous assurer que toutes les parties de votre opération réussissent ou échouent en tant que groupe, puis renvoyez ce résultat à vos utilisateurs.
  • Vos services. Les clients veulent des résultats sans ambiguïté pour les appels de service Web, et le schéma REST habituel consistant à formuler des demandes POST, PUT ou DELETE directement sur des ressources substantielles me semble un moyen médiocre et facilement amélioré d’offrir cette certitude. Si la fiabilité vous tient à cœur, vous devez identifier les demandes d'action. Cet ID peut être un GUID créé sur le client ou une valeur de départ à partir d'une base de données relationnelle sur le serveur, peu importe. Pour les identifiants générés par le serveur, une requête-réponse est dédiée à l'échange de l'identifiant. Si cette demande échoue ou réussit à moitié, pas de problème, le client ne fait que répéter la demande. Les identifiants inutilisés ne font pas de mal. Ceci est important car il permet à toutes les requêtes suivantes d'être totalement idempotentes, en ce sens que si elles sont répétées n fois, elles retournent le même résultat et ne causent plus rien. Le serveur stocke toutes les réponses par rapport à l'ID d'action et, s'il voit la même demande, il repasse la même réponse. Le traitement est plus complet dans cette google doc . Le doc suggère une implémentation qui, je crois (!), Suit globalement les principes de REST. Les experts me diront sûrement comment cela viole les autres. Ce modèle peut être utilement utilisé pour tout appel non sécurisé à votre service Web, qu’il y ait ou non des transactions en aval impliquées.
  • Intégration de votre service dans " transactions " contrôlée par les services en amont. Dans le contexte des services Web, les transactions ACID complètes ne sont généralement pas considérées comme rentables, mais vous pouvez grandement aider les consommateurs de votre service en fournissant des liens d'annulation et / ou de confirmation dans votre réponse de confirmation, et ainsi atteindre transactions par compensation .

Votre exigence est fondamentale. Ne laissez pas les gens vous dire que votre solution n'est pas casher. Jugez leur architecture à la lumière de leur capacité et de la simplicité à résoudre votre problème.

Tout d’abord, transférer de l’argent n’est rien que vous ne puissiez faire en un seul appel de ressource. L'action que vous voulez faire est d'envoyer de l'argent. Vous ajoutez donc une ressource de transfert d’argent au compte de l’expéditeur.

POST: accounts/alice, new Transfer {target:"BOB", abmount:100, currency:"CHF"}.

Fait. Vous n'avez pas besoin de savoir qu'il s'agit d'une transaction qui doit être atomique, etc. Vous transférez simplement de l'argent, alias. envoyer de l'argent de A à B.

Mais dans les rares cas, voici une solution générale:

Si vous souhaitez effectuer des tâches très complexes impliquant de nombreuses ressources dans un contexte défini, avec de nombreuses restrictions qui franchissent en réalité la barrière quoi / pourquoi (connaissances de l'entreprise / implémentation), vous devez transférer un état. Puisque REST doit être sans état, en tant que client, vous devez transférer l’état.

Si vous transférez l’état, vous devez masquer les informations internes du client. Le client ne doit pas connaître les informations internes requises uniquement par la mise en œuvre, mais ne dispose pas d'informations pertinentes pour l'entreprise. Si ces informations n'ont aucune valeur commerciale, l'état doit être chiffré et une métaphore du type jeton, passe ou autre chose doit être utilisée.

De cette manière, il est possible de transmettre l'état interne et d'utiliser le cryptage et la signature du système pour rester sécurisé et sécurisé. Trouver la bonne abstraction pour le client, c'est pourquoi sa conception et son architecture dépendent des informations d'état qu'il transmet.

La vraie solution:

N'oubliez pas que REST parle de HTTP et que HTTP utilise le concept d'utilisation de cookies. Ces cookies sont souvent oubliés lorsque l’on parle d’API REST, de flux de travail et d’interactions couvrant plusieurs ressources ou demandes.

Rappelez-vous ce qui est écrit dans Wikipedia sur les cookies HTTP:

  

Les cookies ont été conçus pour constituer un mécanisme fiable permettant aux sites Web de mémoriser des informations détaillées (telles que des éléments d'un panier d'achat) ou d'enregistrer l'activité de navigation de l'utilisateur (notamment en cliquant sur des boutons particuliers, en se connectant ou en enregistrant les pages visitées par le destinataire). depuis des mois ou des années).

Donc, fondamentalement, si vous devez transmettre l'état, utilisez un cookie. Il est conçu pour exactement la même raison, il s’agit de HTTP et, par conséquent, il est compatible avec REST:).

La meilleure solution:

Si vous parlez d'un client effectuant un flux de travail impliquant plusieurs demandes, vous parlez généralement de protocole. Chaque forme de protocole est assortie d'un ensemble de conditions préalables pour chaque étape potentielle, telle que l'exécution de l'étape A avant de pouvoir exécuter B.

Cela est naturel, mais exposer le protocole aux clients rend tout plus complexe. Pour éviter cela, il suffit de penser à ce que nous faisons lorsque nous devons faire des interactions complexes et des choses dans le monde réel…. Nous utilisons un agent.

À l'aide de la métaphore de l'agent, vous pouvez fournir une ressource capable d'effectuer toutes les étapes nécessaires à votre place et de stocker les assignations / instructions sur lesquelles elle agit dans sa liste (afin que nous puissions utiliser POST sur l'agent ou une "agence").

Un exemple complexe:

Achat d'une maison:

Vous devez prouver votre crédibilité (par exemple, fournir vos entrées dans le casier judiciaire), vous devez vous assurer des détails financiers, vous devez acheter la maison réelle en utilisant un avocat et un tiers de confiance stockant les fonds, vérifier que la maison appartient maintenant. à vous et ajoutez les achats à votre dossier fiscal, etc. (à titre d'exemple, certaines étapes peuvent être fausses ou quoi que ce soit).

Ces étapes peuvent prendre plusieurs jours, certaines peuvent être effectuées en parallèle, etc.

Pour ce faire, il vous suffit de donner à l'agent la tâche suivante:

POST: agency.com/ { task: "buy house", target:"link:toHouse", credibilities:"IamMe"}.

Fait. L'agence vous renvoie une référence que vous pouvez utiliser pour voir et suivre le statut de ce travail. Le reste est fait automatiquement par les agents de l'agence.

Pensez à un traqueur de bogues par exemple. En gros, vous signalez le bogue et vous pouvez utiliser son identifiant pour vérifier ce qui se passe. Vous pouvez même utiliser un service pour écoutero changements de cette ressource. Mission terminée.

Je pense que dans ce cas, il est tout à fait acceptable de casser la théorie pure de REST dans cette situation. Dans tous les cas, je ne pense pas que REST contienne quoi que ce soit qui empêche de toucher des objets dépendants dans les analyses de rentabilisation qui le nécessitent.

Je pense vraiment que le fait de créer un gestionnaire de transactions personnalisé ne constitue pas une valeur ajoutée supplémentaire. Vous pouvez simplement exploiter la base de données pour le faire.

Vous ne devez pas utiliser de transactions côté serveur dans REST.

L'une des contraintes REST:

  

Apatride

     

Le client & # 8211; la communication avec le serveur est en outre limité par le fait qu'aucun contexte client n'est stocké sur le serveur entre les demandes. Chaque demande émanant d'un client contient toutes les informations nécessaires pour répondre à la demande et tout état de session est conservé dans le client.

La seule méthode RESTful consiste à créer un journal de redondance de transaction et à le placer dans l'état du client. Avec les demandes, le client envoie le journal de reprise et le serveur rétablit la transaction et

  1. annule la transaction mais fournit un nouveau journal de transaction (un peu plus loin)
  2. ou terminez la transaction.

Mais il est peut-être plus simple d'utiliser une technologie basée sur une session de serveur qui prend en charge les transactions côté serveur.

Je pense que ce serait le cas de l’utilisation d’un identifiant unique généré sur le client afin de s’assurer que le hoquet de connexion n’implique pas une duplicité sauvegardée par l’API.

Je pense que l'utilisation d'un champ GUID généré par le client avec l'objet de transfert et le fait de ne pas réinsérer le même GUID constitueraient une solution plus simple au transfert bancaire.

Je ne connais pas de scénarios plus complexes, tels que la réservation de billets d'avion multiples ou les micro-architectures.

J'ai trouvé un article sur le sujet relatant les expériences de traitant de l'atomicité des transactions dans les services RESTful .

Dans le cas simple (sans ressources distribuées), vous pourriez considérer la transaction comme une ressource, où l'acte de la créer atteint l'objectif final.

Donc, pour transférer entre <url-base>/account/a et <url-base>/account/b, vous pouvez poster ce qui suit sur <url-base>/transfer.

<transfer>
    <from><url-base>/account/a</from>
    <to><url-base>/account/b</to>
    <amount>50</amount>
</transfer>

Cela créerait une nouvelle ressource de transfert et renverrait la nouvelle URL du transfert - par exemple <url-base>/transfer/256.

Au moment du dépôt réussi, la « vraie » transaction est effectuée sur le serveur et le montant est retiré d'un compte et ajouté à un autre.

Ceci, cependant, ne couvre pas une transaction distribuée (si, disons, « a » est détenu dans une banque derrière un service et « b » est détenu dans une autre banque derrière un autre service) - autre que pour dire « essayez de formuler tout opérations d'une manière qui ne nécessite pas de transactions distribuées".

Je suppose que vous pouvez inclure le TAN dans l'URL / ressource:

  1. PUT / transaction pour obtenir l'ID (par exemple, " 1 ")
  2. [PUT, GET, POST, peu importe] / 1 / compte / bob
  3. [PUT, GET, POST, peu importe] / 1 / compte / facture
  4. SUPPRIMER / transaction avec l'ID 1

Juste une idée.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top